Tuesday, January 13, 2026

Android java: input events processing (collection)

There 2 ways to handle input events from user input

  1. Immediate: using listener
  2. At process: at the end to process data, usually user fire "process" button 

TimePicker

1. Immediate 

...
        //on view created
        // date picker
        TimePicker myTimePicker = view.findViewById(R.id.my_time_picker);
        myTimePicker.setOnTimeChangedListener(timeChangeListener);
...
    // TimePicker Listeneer
    private final TimePicker.OnTimeChangedListener timeChangeListener =
            new TimePicker.OnTimeChangedListener() {
        @Override
        public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
            // TODO
        }
    };

2.  At process

    // Pull the values exactly as they are right now
    int hour = timePicker.getHour();
    int minute = timePicker.getMinute();

RadioGroup

1. Immediate

...
        //on view created
        // date picker
        RadioGroup radioSelectModeBackup = view.findViewById(R.id.radio_select_mode_backup);
        radioSelectModeBackup.setOnCheckedChangeListener(radioListener);
...
    // RadioGroup Listener
    RadioGroup.OnCheckedChangeListener radioListener = new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(@NonNull RadioGroup radioGroup, int idSelected) {
            if (idSelected==R.id.radio_weekly) {
                // weekly
            } else if (idSelected==R.id.radio_moonthly) {
                // monthly
            }
        }
    };

2. At process 

    // Inside your "Save" or "Submit" button click listener
    int selectedId = radioSelectModeBackup.getCheckedRadioButtonId();

    if (selectedId == R.id.radio_weekly) {
        // Logic for weekly backup
    } else if (selectedId == R.id.radio_moonthly) {
        // Logic for monthly backup
    } else {
        // Nothing is selected (returns -1 if no default is set in XML)
    }

 

 

 

 

Monday, January 12, 2026

Debian 13: using systemd timesync to update ntp client time

Since Debain Jessie, Debian use systemd to synchronize ntp clinet

To list timezone:

# timedatectl list-timezones | grep Jakarta
Asia/Jakarta

To set timezone:

# timedatectl set-timezone Asia/Jakarta

To show

# timedatectl 
               Local time: Mon 2026-01-12 12:44:27 WIB
           Universal time: Mon 2026-01-12 05:44:27 UTC
                 RTC time: Mon 2026-01-12 05:44:27
                Time zone: Asia/Jakarta (WIB, +0700)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

Edit /etc/systemd/timesyncd.conf

...
[Time]
#NTP=
NTP=0.id.pool.ntp.org 1.id.pool.ntp.org 2.id.pool.ntp.org 2.id.pool.ntp.org 3.id.pool.ntp.org
#FallbackNTP=0.debian.pool.ntp.org 1.debian.pool.ntp.org 2.debian.pool.ntp.org 3.debian.pool.ntp.org
...

To restart service

# systemctl restart systemd-timesyncd

you can use old way using ntp client, but you need to remove this package

# timedatectl set-ntp false

Now you can install ntp and edit configuration /etc/ntp.conf.

 

 

Wednesday, January 7, 2026

Java: using build tool gradle wrapper to setup project and run

 

  • java application openjdk 17
  • gradle 8.7

install gradle 

# apt-get install gradle 

create folder project example

$ mkdir example
$ cd example

create init project

wrap gradle version for your project 

$ gradle wrapper --gradle-version 8.7
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (build 17.0.16+8-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.16+8-Debian-1deb12u1, mixed mode, sharing)

init using gradle 8.7  debian 13 uses old 4.4 

$ ./gradlew init \
  --type java-application \
  --dsl groovy \
  --test-framework junit-jupiter \
  --package com.dedetok \
  --project-name example
openjdk version "17.0.16" 2025-07-15
OpenJDK Runtime Environment (build 17.0.16+8-Debian-1deb12u1)
OpenJDK 64-Bit Server VM (build 17.0.16+8-Debian-1deb12u1, mixed mode, sharing)
Downloading https://services.gradle.org/distributions/gradle-8.7-bin.zip
...............................................................................................................................
Unzipping ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7-bin.zip to ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu
Set executable permissions for: ~/.gradle/wrapper/dists/gradle-8.7-bin/bhs2wmbdwecv87pi65oeuq5iu/gradle-8.7/bin/gradle

Welcome to Gradle 8.7!

Here are the highlights of this release:
 - Compiling and testing with Java 22
 - Cacheable Groovy script compilation
 - New methods in lazy collection properties

For more details see https://docs.gradle.org/8.7/release-notes.html

Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details

Enter target Java version (min: 7, default: 21): 17

Select application structure:
  1: Single application project
  2: Application and library project
Enter selection (default: Single application project) [1..2] 1

Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] no


> Task :init
To learn more about Gradle by exploring our Samples at https://docs.gradle.org/8.7/samples/sample_building_java_applications_multi_project.html

BUILD SUCCESSFUL in 5m 12s
1 actionable task: 1 executed

./settings.gradle

plugins {
    // Apply the foojay-resolver plugin to allow automatic download of JDKs
    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
}

rootProject.name = 'example'
include('app')

./app/build.gradle

plugins {
    // Apply the application plugin to add support for building a CLI application in Java.
    id 'application'
}

repositories {
    // Use Maven Central for resolving dependencies.
    mavenCentral()
}

dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava
}

// Apply a specific Java toolchain to ease working on different environments.
java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

application {
    // Define the main class for the application.
    mainClass = 'com.dedetok.App'
}

tasks.named('test') {
    // Use JUnit Platform for unit tests.
    useJUnitPlatform()
}

./gradle/libs.versions.toml

[versions]
guava = "32.1.3-jre"
junit-jupiter = "5.10.1"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }

To show current gradle version

$ ./gradlew --version

------------------------------------------------------------
Gradle 8.7
------------------------------------------------------------

Build time:   2024-03-22 15:52:46 UTC
Revision:     650af14d7653aa949fce5e886e685efc9cf97c10

Kotlin:       1.9.22
Groovy:       3.0.17
Ant:          Apache Ant(TM) version 1.10.13 compiled on January 4 2023
JVM:          17.0.16 (Debian 17.0.16+8-Debian-1deb12u1)
OS:           Linux 6.12.57+deb13-amd64 amd64

Test run

$ ./gradlew run
Path for java installation '/usr/lib/jvm/openjdk-17' (Common Linux Locations) does not contain a java executable

> Task :app:run
Hello World!

BUILD SUCCESSFUL in 473ms
2 actionable tasks: 1 executed, 1 up-to-date

or you can run using style $ ./gradlew :app:run

Now add maridb jcoonect into project, using Traditional Groovy Approach. edit ./app/build.gradle

...
dependencies {
    // Use JUnit Jupiter for testing.
    testImplementation libs.junit.jupiter

    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    // This dependency is used by the application.
    implementation libs.guava

    // mariadb jconnector (single file no need change libs.versions.toml
    implementation 'org.mariadb.jdbc:mariadb-java-client:3.3.3'

    // using libs.versions.toml must editing this file 
    //implementation libs.mariadb.client

}
...

If you wish to useing Version Catalog Approach, use 2nd option for ./app/build.gradle and edit libs.version.toml

[versions]
guava = "32.1.3-jre"
junit-jupiter = "5.10.1"
mariadb = "3.3.3"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
mariadb-client = { group = "org.mariadb.jdbc", name = "mariadb-java-client", version.ref = "mariadb" }

use Version Catalog Approach for complex project and involving multiple developers.

Edit ./app/src/main/java/com/dedetok/App.java

/*
 * This source file was generated by the Gradle 'init' task
 */
package com.dedetok;

import java.sql.*;

public class App {
    public String getGreeting() {
        return "Hello World!";
    }

    public static void main(String[] args) throws SQLException {
        System.out.println(new App().getGreeting());
        
        String dbUrl = "jdbc:mariadb://localhost:3306/mydatabase";
        String dbUName = "myuname";
        String dbPass = "mypass";
        
        Connection conn = DriverManager.getConnection(dbUrl, dbUName, dbPass);

    }
}

To generate single file jar for your project, edit  ./app/build.gradle

...
jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    // change with yours
    manifest {
        attributes "Main-Class": "com.dedetok.App"
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}
...

You can run directly using java command

$ ./gradlew build
...
$ java -jar ./app/build/libs/app.jar
Hello World!

Your jar file location is ./app/build/libs/

Now you can edit  ./app/src/main/java/com/dedetok/App.java finish your project. Use any text editor if you wish.

Tuesday, January 6, 2026

Debian 13: bash to clean up unused gradle in folder $HOME/.gradle

stop any gradle daemon (better run this after you start your Debian)

copy paste this bash and change permission to execute

#!/bin/bash

# Set the cutoff date yyyy-mm-dd
CUTOFF_DATE="2025-08-01"

# Gradle folder
GRADLE_DIR="$HOME/.gradle"

# List of folders to clean
FOLDERS=("android" "build-scan-data" "caches" "daemon" "kotilin-profile" "native" "notifications" "undefined-build" "wrapper" ".tmp")

# 1. Stop Gradle Daemons first so files aren't locked
if [ -f "$GRADLE_DIR/daemon" ]; then
    echo "Stopping Gradle daemons..."
    gradle --stop 2>/dev/null || ./gradlew --stop 2>/dev/null
fi

echo "Cleaning Gradle folders in $GRADLE_DIR modified before $CUTOFF_DATE..."

for folder in "${FOLDERS[@]}"; do
    TARGET="$GRADLE_DIR/$folder"
    if [ -d "$TARGET" ]; then
        echo "Processing $TARGET..."
        # Find and delete files modified before the cutoff date
        find "$TARGET" -type f ! -newermt "$CUTOFF_DATE" -print -delete
        # Remove empty directories
        find "$TARGET" -type d -empty -print -delete
    else
        echo "Folder $TARGET does not exist, skipping."
    fi
done

echo "Cleanup complete."

Note:

  • change CUTOFF_DATE for any desire date
  • add or remove FOLDERS depends on your folder structure 

termux android: ffmpeg split video and convert h26h video

Requirement

Install termux from 

  • play store 
  • github.com/termux/termux-app latest but require allow install from unknown source

open termux and update

$ pkg upgrade

install ffmpeg

$ pkg install ffmpeg nano

Allow termux to manage files

$ termux-setup-storage

 Split a video by time

Target:

  • every piece has 1 minutes 58 seconds
  • resolution low 480
  • output h264
  • sound mono

command to convert '172967815750944 (1).mp4'

$ ffmpeg -i 172967815750944\ \(1\).mp4 -vf scale=-2:480 -r 25 -c:v libx264 -preset veryfast -crf 28 -ac 1 -c:a aac -b:a 96k -f segment -segment_time 118 -reset_timestamps 1 out_%03d.mp4

this command will create files with name starting with name out_[number].mp4

This is fastest way to split video into smaller size, but you need to write the long command. This is bash sh to split file to avoid write long command. Open nano and copy paste it, name it with mysplitvid.sh. Make it execute $ chmod u+x mysplitvid.sh.

#!/data/data/com.termux/files/usr/bin/bash

# Check input
if [ -z "$1" ]; then
  echo "Usage: $0 inputvideo.mp4"
  exit 1
fi

INPUT="$1"
BASENAME=$(basename "$INPUT" .mp4)

# Output directory (public, visible to other apps)
OUTDIR="/sdcard/Download/${BASENAME}_split"
mkdir -p "$OUTDIR"

# FFmpeg split + encode
ffmpeg -i "$INPUT" \
  -vf scale=-2:480 \
  -r 25 \
  -c:v libx264 \
  -preset veryfast \
  -crf 28 \
  -ac 1 \
  -c:a aac \
  -b:a 96k \
  -f segment \
  -segment_time 118 \
  -reset_timestamps 1 \
  "$OUTDIR/${BASENAME}_out%02d.mp4"

# Notify Android media scanner for all outputs
for file in "$OUTDIR"/*.mp4; do
  am broadcast \
    -a android.intent.action.MEDIA_SCANNER_SCAN_FILE \
    -d "file://$file" >/dev/null
done

echo "✅ Done!"
echo "📂 Output folder: $OUTDIR”

Convert video h256 to social media

Target:

  • Video Codec: H.264 (libx264)
  • Audio Codec: AAC
  • Pixel Format: yuv420p (Required for maximum compatibility)
  • Audio Sample Rate: 48kHz or 44.1kHz
  • Container: MP4  

To convert an H.265 MP4 to a Twitter-compatible, highly compressed, lower-resolution MP4 using FFmpeg, you need to re-encode the video to H.264 video codec, AAC audio codec, a maximum resolution of 1280x720, and a lower bitrate/higher CRF value.

Assume your file is inputfile.mp4 and located at Download folder in your Android.  Here are command to convert your video to make compatible for social media. Currently social media do not support high compression like h265.

~/ $ cd ~/storage
~/storage $ ls
audiobooks downloads movies podcasts
dcim external-0 music shared
documents media-0 pictures
~/storage $ cd downloads
~/storage/downloads $ ls *.mp4
inputfile.mp4
~/storage/downloads $ ffmpeg -i inputfile.mp4 -c:v libx264 -crf 28 -preset medium -vf "scale=1280:720,format=yuv420p" -c:a aac -b:a 128k -movflags faststart output.mp4
...

This is bash sh to split file to avoid write long command. Open nano and copy paste it, name it with myconverth265.sh. Make it execute $ chmod u+x myconverth265.sh.

#!/data/data/com.termux/files/usr/bin/bash

# Usage: ./convert_video.sh videoinput.mp4

INPUT="$1"

if [ -z "$INPUT" ]; then
    echo "Usage: $0 input_video.mp4"
    exit 1
fi

# Remove extension
BASENAME=$(basename "$INPUT")
NAME="${BASENAME%.*}"

# Output path (shared storage)
OUTPUT="/sdcard/Download/${NAME}_out.mp4"

echo "Converting $INPUT → $OUTPUT"

ffmpeg -y -i "$INPUT" \
    -c:v libx264 \
    -crf 28 \
    -preset medium \
    -vf "scale=1280:720,format=yuv420p" \
    -c:a aac \
    -b:a 128k \
    -movflags faststart \
    "$OUTPUT"

# Notify Android media scanner
am broadcast \
    -a android.intent.action.MEDIA_SCANNER_SCAN_FILE \
    -d "file://$OUTPUT"

echo "✅ Done! Video available in Gallery / Files / Photos”

Make your output video accessible by other application 

Your output file is owned by Termux. To make it accessible by other applications e.g. photos, files, etc, you can try these option

• move it to Movies or Download folder

$ mv out_*.mp4 /storage/emulated/0/Movies/

• Force android system to scan your output

$ am broadcast -a android.intent.action.MEDIA_SCANNER_SCAN_FILE -d file:///sdcard/Download/output.mp4