Friday, January 23, 2026

Android java: creating icon launcher and icon for Play Store

Prepare your file, it preferred to use transparent background. I use png for image source. 

Open your Android Studio, follow these steps: 

Step 1 open Resource Manager: 

Tool -> Resource Manager

Press "+" -> Image Asset it will open Configure Image Asset

Fill

  • Icon type: Launcer icons (Adaptive and Legacy)
  • Name: my_ic_launcher 

Step 2 Set foreground with your png file:

Tab: Foreground Layer

  • Source Asset
  • Asset type: Image
    Path: /Workspace/[your_project]/iconLauncher.png
  • Scalling
    Trim: Yes for automatic scalling
    If Trim No, you can adjust scaling by scrolling Resize until it doesn't get cuted.

Step 3 set background: 

Tab: Background Layer
Asset type:
Color: directing choose color (recommended)
Image: xml file to edit or put image for background

Step 4: Finish

Attach your icon into your application, open your AndroidManifest.xml

...
    <application
...
        android:roundIcon="@mipmap/my_ic_launcher"
...

Icon for playstore can found at Workspace/[your_project]/app/src/main/my_ic_launcher-playstore.png 

Android java: gradle corrupted, how to fix it

This is error message when gradle corrupted due to accidentally deleted or fail updated.

"The contents of the immutable workspace '~/.gradle/caches/9.1.0/groovy-dsl/52b1078aa736f3877f522d2046a7f837' have been modified" 

To make easier to fix gradle, exit Android Studio, open shell and go to project root directory. You will find file gradlew.

Clean up your gradle

$ ./gradlew --stop
Stopping Daemon(s)
2 Daemons stopped
$ rm -rf ~/.gradle/caches/9.1.0/

Run help to force gradlew download and rebuild library required.

$ ./gradlew help
Starting a Gradle Daemon, 2 stopped Daemons could not be reused, use --status for details

> Configure project :app
WARNING: The option setting 'android.usesSdkInManifest.disallowed=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.sdk.defaultTargetSdkToCompileSdkIfUnset=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableAppCompileTimeRClass=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.builtInKotlin=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.newDsl=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.r8.optimizedResourceShrinking=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.defaults.buildfeatures.resvalues=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
w: file://~/Workspace/Android/WredaContactBackup/app/build.gradle.kts:5:1: 'fun Project.android(configure: Action<BaseAppModuleExtension>): Unit' is deprecated. Replaced by com.android.build.api.dsl.ApplicationExtension.
This class is not used for the public extensions in AGP when android.newDsl=true, which is the default in AGP 9.0, and will be removed in AGP 10.0.
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 28s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Open your Android Studio, repair android studio project choose index repair.

If this error occurred

"Ambiguous method call: both 'FragmentActivity.onCreate(Bundle) (In ~/.gradle/caches/9.1.0/transforms/dae60150ff0476652c6b13819aa96f10/transformed/fragment-1.5.4/jars/classes.jar!/androidx/fragment/app/FragmentActivity.class)' and 'ComponentActivity.onCreate(Bundle) (In ~/.gradle/caches/9.1.0/transforms/48c7a661e0aa50fff79a603bd45cc34c/transformed/activity-1.12.2/jars/classes.jar!/androidx/activity/ComponentActivity.class)' match"

Invalidate cache (everything) and restart.

If you find some deprecated gradle option like these:

$ ./gradlew help
Starting a Gradle Daemon, 1 incompatible and 1 stopped Daemons could not be reused, use --status for details

> Configure project :app
WARNING: The option setting 'android.usesSdkInManifest.disallowed=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.sdk.defaultTargetSdkToCompileSdkIfUnset=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.enableAppCompileTimeRClass=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.builtInKotlin=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.newDsl=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.r8.optimizedResourceShrinking=false' is deprecated.
The current default is 'true'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The option setting 'android.defaults.buildfeatures.resvalues=true' is deprecated.
The current default is 'false'.
It will be removed in version 10.0 of the Android Gradle plugin.
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties
WARNING: The property android.dependency.excludeLibraryComponentsFromConstraints improves project import performance for very large projects. It should be enabled to improve performance.
To suppress this warning, add android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false to gradle.properties

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 5s
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Edit file gradle.properties, find those configuration and take some adjustment. You can close Android Studio and back to shell, this is my gradle.properties:

# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
#android.defaults.buildfeatures.resvalues=true #deprecated
#android.sdk.defaultTargetSdkToCompileSdkIfUnset=false #deprecated
#android.enableAppCompileTimeRClass=false #deprecated
#android.usesSdkInManifest.disallowed=false #deprecated
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
#android.r8.optimizedResourceShrinking=false #deprecated
#android.builtInKotlin=false #deprecated
#android.newDsl=false #deprecated
# edited dedetok
#android.enableJetifier=true
android.generateSyncIssueWhenLibraryConstraintsAreEnabled=false

Validate gradle is clear

$ ./gradlew help

> Task :help

Welcome to Gradle 9.1.0.

To run a build, run gradlew <task> ...

To see a list of available tasks, run gradlew tasks

To see more detail about a task, run gradlew help --task <task>

To see a list of command-line options, run gradlew --help

For more detail on using Gradle, see https://docs.gradle.org/9.1.0/userguide/command_line_interface.html

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 503ms
1 actionable task: 1 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.1.0/userguide/configuration_cache_enabling.html

Now you have clean project.  

Saturday, January 17, 2026

adb command references (android debug bridge)

to add path for platform-tool edit .bashrc (for Debian), append at the end. log out and sign in again to get affect or run $ source ~/.bashrc

export PATH=$PATH:$HOME/AndroidStudio/Sdk/platform-tools/

to show devices attached 

$ adb devices
* daemon not running; starting now at tcp:5037
adb * daemon started successfully
List of devices attached
A6501D9863TH039779    unauthorized -> Not allow, please allow on your device
$ adb devices
List of devices attached
A6501D9863TH039779    device -> your device allow to debug

to install application (don't support aab, compile to apk) 

$ adb install example.apk
Performing Streamed Install
Success

to uninstall package 

$ adb uninstall com.dedetok.radiowalkman
Success

to uninstall package (bloatware) for user 0 (default user). Use adb shell pm instead directly adb uninstall. Note: it just remove for current default user, it does not remove from firmware, you need to root your device for fully removal. After reset your device, the bloatware will come again.

$ adb uninstall --user 0 com.example.app vs 
$ adb shell uninstall --user 0 com.example.app
$ adb shell pm uninstall --user 0 net.bat.store
Success
com.transsion.aivoiceassistant

to list packages 

$ adb shell pm list packages | grep dedetok
package:com.dedetok.turuntirta
package:com.dedetok.pitrapuja

to list directory Download 

$ adb shell ls /sdcard/Download/com.dedetok.wredacontactbackup
test.txt

to get error Logcat 

$ adb logcat | grep com.dedetok.wredacontactbackup

  • WorkManager
    $ adb logcat -s dedetok WorkManager WM-WorkerWrapper
    --------- beginning of system
    --------- beginning of crash
    --------- beginning of main
    01-20 14:51:59.303 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id 6548d3d5-2c2e-47a0-a795-ff4f9365b2be
    01-20 14:51:59.311 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id e42ac4be-f95e-41e8-9694-e4eb6bc40671
    01-20 14:51:59.319 25534 25561 E WM-WorkerWrapper: Didn't find WorkSpec for id e42ac4be-f95e-41e8-9694-e4eb6bc40671

to get error

$ adb shell dumpsys dropbox | grep com.dedetok.wredacontactbackup

to clear data include cache for package com.dedetok.wredacontactbackup

$ adb shell pm clear com.dedetok.wredacontactbackup

Check JobScheduler (where WorkManager stores jobs)

$ adb shell dumpsys jobscheduler | grep -A 30 "com.dedetok.wredacontactbackup"
  JOB #u0a124/1: 7b2babf com.dedetok.wredacontactbackup/androidx.work.impl.background.systemjob.SystemJobService
    u0a124 tag=*job*/com.dedetok.wredacontactbackup/androidx.work.impl.background.systemjob.SystemJobService

#u0a124/1 last digit /1 is job id  

Force scheduler to run, use job id

$ adb shell cmd jobscheduler run -f com.dedetok.wredacontactbackup 1
Running job [FORCED] 

 

 

 

 

Android java: dealing with Trusted credentials on Android 7.1.1 below for letencrypt

Dealing with connection to https on old android may depend on Trusted credentials on the the device. 

Letsencrypt root certificate does not installed on Trusted credentials prior android 7.1.1.

Some application may throw:

java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

Solution 1 add user Trusted credentials 

Install Letsencrypt pem manually on old phone i.e android version 7.1.1 or older. You can download from https://letsencrypt.org/certs/isrgrootx1.pem?hl=en-US .

On your device (mine is evercoss gen pro x pro android 7.0). Go to setting -> Security.

You may find:

  • Trusted Credentials 
  • User Credentials
  • Install from SD Card

Choose "Install from SD Card":

  • Filed Name of Certification e.g ISGR ROOT X1 or Letsecrypt.
  • Credentials Use select VPN and aps

Open your Android Studio project and create res/xml/network_security_config.xml.

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config>
        <trust-anchors>
            <certificates src="system" />
            <certificates src="user" />
        </trust-anchors>
    </base-config>
</network-security-config>

Edit AndroidManifest.xml

...
   <application
    ... 
   android:networkSecurityConfig="@xml/network_security_config"
   ...
   >
...

If https server ever falls back to http (not https), you should add cleartextTrafficPermitted="true" to the <base-config> tag (Not Recommended except your application need to access http):

...
<base-config 
   ...
   cleartextTrafficPermitted="true"
   ...
>
...

if this XML fix doesn't work, it's not the certificate—it's the Android 7.0 Cipher bug. In that specific case, you will have to use Conscrypt library (Solution 2).

Solution 2 using Conscript Library

Add dependency into gradle.build app

dependencies {
    ...
    implementation 'org.conscrypt:conscrypt-android:2.5.2'
    ...
}

Initialize at application startup or foreground

...
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N_MR1) { 
    // Below Android 7.1.1
    Security.insertProviderAt(Conscrypt.newProvider(), 1);
}
...


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


Monday, December 22, 2025

python3: my personal package mini function

 

~/
├── .gitignore
├── dedetoklib/
│     ├── mydate
│     │     ├── __init__.py
│     │     ├── check_holiday.py
│     │     └── requirements.txt
│     ├── 
│     ├── 
│     └── 

├── test
│     ├── main.py

└── requirements.txt

 

check_holiday.py

import holidays
from datetime import datetime

# Create holiday object for Indonesia
ID_HOLIDAYS = holidays.country_holidays("ID")

def is_holiday(date_str):
    """
    Check if a date (YYYY-MM-DD) is a holiday.
    
    Returns:
        (bool, str | None): 
        - True and holiday name if holiday
        - False and None if not
    """
    date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
    
    if date_obj in ID_HOLIDAYS:
        return True, ID_HOLIDAYS[date_obj]
    else:
        return False, None

def is_working_day(date_str):
    """
    Check if a date (YYYY-MM-DD) is a working day (Monday to Friday).
    Returns:
        bool: True if Monday–Friday, False if Saturday or Sunday
    """
    date_obj = datetime.strptime(date_str, "%Y-%m-%d").date()
    # weekday(): Monday = 0, Sunday = 6    if date_obj.weekday() < 5:
        return True, "Weekday"
    else:
        return False, "Weekend"

Test main.py

from dedetoklib import check_holiday

dates = ["2025-01-01", "2025-01-02", "2025-08-17"]

for d in dates:
    # Check holiday
    holiday_flag, holiday_name = check_holiday.is_holiday(d)
    
    # Check working day
    working_flag, working_str = check_holiday.is_working_day(d)
    
    print(f"{d}: {working_str}, Holiday? {holiday_flag}", end="")
    if holiday_flag:
        print(f" ({holiday_name})")
    else:
        print()

 from dedetoklib import check_holiday as ch



 


 

python3: writing package

 
create folder to put your pyhton files e.g. dedetoklib, 

the structure of directory  

~/
├── .gitignore 

├── dedetoklib/
│   ├── __init__.py
│   └── hello.py

── main.py
└── requirements.txt

hello.py

def say_hello():
    return "Hello, World"

__init__.py

(leave it empty)

main.py 

import dedetoklib.hello

dedetoklib.hello.say_hello()

import as library

import dedetoklib.hello as h

print(h.say_hello())

 requirements.txt contains dependency.

.gitignore contains  files and folders to ignore.

Wednesday, December 10, 2025

Debian 13: rebuilding nvidia driver after kernel upgrade from linux-image-6.12.31-amd64 to linux-image-6.12.57+deb13-amd64

After you upgrade kernel, in my case linux-image-6.12.31-amd64 to linux-image-6.12.57+deb13-amd64, you need to rebuild nvidia driver. During boot, Debian will show some error loading nvidia module

To check which module fail during boot 

# journalctl -b -u systemd-modules-load.service 
Dec 10 09:48:02 mylocalpc systemd-modules-load[781]: modprobe: ERROR: Error running install command 'modprobe n> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[781]: modprobe: ERROR: could not insert 'nvidia_modeset': Invali> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[789]: modprobe: FATAL: Module nvidia-current-drm not found in di> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[767]: Error running install command 'modprobe nvidia-modeset ; m> 
Dec 10 09:48:02 mylocalpc systemd-modules-load[767]: Failed to insert module 'nvidia_drm': Invalid argument 
Dec 10 09:48:02 mylocalpc systemd[1]: systemd-modules-load.service: Main process exited, code=exited, status=1/> 
Dec 10 09:48:02 mylocalpc systemd[1]: systemd-modules-load.service: Failed with result 'exit-code'. 
Dec 10 09:48:02 mylocalpc systemd[1]: Failed to start systemd-modules-load.service - Load 

To install dkms

root@mylocalpc:~# apt install dkms
dkms is already the newest version (3.2.2-1~deb13u1).
dkms set to manually installed.
Summary:
  Upgrading: 0, Installing: 0, Removing: 0, Not Upgrading: 1

To show dkms status

root@mylocalpc:~# dkms status
nvidia-current/550.163.01, 6.12.31-amd64, x86_64: installed 

To rebuild nvidia driver, it will build any kernel installed.

root@mylocalpc:~# dkms install nvidia-current/550.163.01
Sign command: /lib/modules/6.12.57+deb13-amd64/build/scripts/sign-file
Signing key: /var/lib/dkms/mok.key
Public certificate (MOK): /var/lib/dkms/mok.pub

Building module(s)................ done.
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-modeset.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-drm.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-uvm.ko
Signing module /var/lib/dkms/nvidia-current/550.163.01/build/nvidia-peermem.ko
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-modeset.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-drm.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-uvm.ko.xz
Installing /lib/modules/6.12.57+deb13-amd64/updates/dkms/nvidia-current-peermem.ko.xz
Running depmod.... done.

to check status of nvidia modul

root@mylocalpc:~# dkms status
nvidia-current/550.163.01, 6.12.31-amd64, x86_64: installed
nvidia-current/550.163.01, 6.12.57+deb13-amd64, x86_64: installed

restart your debian. 

Friday, December 5, 2025

Pyhton3: playwright to get audio url stream

There are many tool to find url audio stream from radio station's page, here are a few list:

  1. playwright
  2. Selenium
  3. Puppeteer

To use Wget to find url audo on statik and simple web  

$ wget --spider -r -l 5 -nv -w 1 --no-clobber -A .mp3,.m3u8 "https://www.example.com/audio/stations" 2>&1 | grep -E '\.(mp3|m3u8)$' | awk '{print $3}' | sort -u > out_audio_urls.txt

Install playwright on debian with virtual environment:

myuser@mypc:~$ mkdir spider_playwright
myuser@mypc:~$ cd spider_playwright/
myuser@mypc:~/spider_playwright$ python3 -m venv venv
myuser@mypc:~/spider_playwright$ source venv/bin/activate
(venv) myuser@mypc:~/spider_playwright$ pip install playwright
Collecting playwright
  Downloading playwright-1.56.0-py3-none-manylinux1_x86_64.whl.metadata (3.5 kB)
Collecting pyee<14,>=13 (from playwright)
  Downloading pyee-13.0.0-py3-none-any.whl.metadata (2.9 kB)
Collecting greenlet<4.0.0,>=3.1.1 (from playwright)
  Using cached greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (4.1 kB)
Collecting typing-extensions (from pyee<14,>=13->playwright)
  Using cached typing_extensions-4.15.0-py3-none-any.whl.metadata (3.3 kB)
Downloading playwright-1.56.0-py3-none-manylinux1_x86_64.whl (46.3 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 46.3/46.3 MB 2.5 MB/s eta 0:00:00
Using cached greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (610 kB)
Downloading pyee-13.0.0-py3-none-any.whl (15 kB)
Using cached typing_extensions-4.15.0-py3-none-any.whl (44 kB)
Installing collected packages: typing-extensions, greenlet, pyee, playwright
Successfully installed greenlet-3.2.4 playwright-1.56.0 pyee-13.0.0 typing-extensions-4.15.0
(venv) myuser@mypc:~/spider_playwright$ playwright install
Downloading Chromium 141.0.7390.37 (playwright build v1194) from https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/1194/chromium-linux.zip
173.9 MiB [====================] 100% 0.0s
Chromium 141.0.7390.37 (playwright build v1194) downloaded to /home/myuser/.cache/ms-playwright/chromium-1194
Downloading Chromium Headless Shell 141.0.7390.37 (playwright build v1194) from https://cdn.playwright.dev/dbazure/download/playwright/builds/chromium/1194/chromium-headless-shell-linux.zip
104.3 MiB [====================] 100% 0.0s
Chromium Headless Shell 141.0.7390.37 (playwright build v1194) downloaded to /home/myuser/.cache/ms-playwright/chromium_headless_shell-1194
Downloading Firefox 142.0.1 (playwright build v1495) from https://cdn.playwright.dev/dbazure/download/playwright/builds/firefox/1495/firefox-debian-13.zip
96.7 MiB [====================] 100% 0.0s
Firefox 142.0.1 (playwright build v1495) downloaded to /home/myuser/.cache/ms-playwright/firefox-1495
Downloading Webkit 26.0 (playwright build v2215) from https://cdn.playwright.dev/dbazure/download/playwright/builds/webkit/2215/webkit-debian-13.zip
88.1 MiB [====================] 100% 0.0s
Webkit 26.0 (playwright build v2215) downloaded to /home/myuser/.cache/ms-playwright/webkit-2215
Downloading FFMPEG playwright build v1011 from https://cdn.playwright.dev/dbazure/download/playwright/builds/ffmpeg/1011/ffmpeg-linux.zip
2.3 MiB [====================] 100% 0.0s
FFMPEG playwright build v1011 downloaded to /home/myuser/.cache/ms-playwright/ffmpeg-1011

create file myspider.py and customize to your requirements, make it executable

(venv) myuser@mypc:~/spider_playwright$ chmod u+x myspider.py 

source code myspider.py modify it to meet your requirements 

import asyncio
from playwright.async_api import async_playwright

# generated and adjust by chatgpt.com

AUDIO_HINTS = [
    ".mp3", ".aac", ".m3u8", ".ogg", ".opus", ".wav",
    "stream", "/proxy/", "/live", "radio"
]

async def scan_audio_stream(url: str, listen_seconds: int = 15):
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False)
        page = await browser.new_page()

        # ----- Detect manual browser close -----
        browser_disconnected = asyncio.Event()

        def on_browser_disconnect():
            print("\n[BROWSER CLOSED] Exiting application.")
            browser_disconnected.set()

        browser.on("disconnected", on_browser_disconnect)
        # ---------------------------------------

        found = set()

        async def on_response(res):
            u = res.url.lower()
            ct = res.headers.get("content-type", "").lower()

            if "audio" in ct or any(k in u for k in AUDIO_HINTS):
                if u not in found:
                    print(f"[NEW AUDIO STREAM] {u}")
                    found.add(u)

        page.on("response", on_response)

        print("Opening page...")
        await page.goto(url, wait_until="domcontentloaded")
        await asyncio.sleep(2)

        # ---- Find audio player iframe ----
        candidate_frames = [
            f for f in page.frames
            if any(kw in f.url.lower() for kw in ["player", "radio", "embed"])
        ]
        if not candidate_frames:
            candidate_frames = page.frames  # fallback

        print("Attempting to click play...")
        for f in candidate_frames:
            try:
                for sel in ["button[aria-label='Play']", "button.play", ".jp-play", "button"]:
                    btn = await f.query_selector(sel)
                    if btn:
                        print(f"Clicked play in iframe → {f.url}")
                        await btn.click()
                        break
            except:
                pass

        print(f"\nListening for audio streams up to {listen_seconds} seconds...\n")

        # ----- Wait for 15 seconds OR browser close -----
        try:
            await asyncio.wait_for(browser_disconnected.wait(), timeout=listen_seconds)
        except asyncio.TimeoutError:
            print("\n[TIMEOUT] Done scanning.")
        # -------------------------------------------------

        await browser.close()
        return list(found)


if __name__ == "__main__":
    results = asyncio.run(scan_audio_stream(
        "https://www.example.com", # CHANGE HERE
        listen_seconds=15     # ← Fast scan
    ))

    print("\n===== ALL STREAMS FOUND =====")
    for s in results:
        print(s)

Code

from playwright.sync_api import sync_playwright

def main():
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=False   # <-- You can manually interact
        )
        context = browser.new_context()
        page = context.new_page()

        print("🚀 Browser launched. Type the radio station URL manually.")
        print("👉 When you press Play, audio stream URLs will appear here.\n")

        # Capture outgoing requests
        def on_request(request):
            url = request.url

            if (
                ".mp3" in url or
                ".aac" in url or
                ".m3u8" in url or
                ".ogg" in url or
                "stream" in url.lower() or
                request.resource_type == "media"
            ):
                print("🎧 AUDIO STREAM REQUEST FOUND:")
                print(url, "\n")

        page.on("request", on_request)

        # Capture responses containing audio
        def on_response(response):
            url = response.url
            headers = response.headers
            content_type = headers.get("content-type", "")

            if any(fmt in content_type for fmt in ["audio", "mpeg", "aac", "ogg"]):
                print("🎧 AUDIO STREAM RESPONSE FOUND:")
                print(url, "\n")

        page.on("response", on_response)

        # Open a blank page — you will type the URL manually
        page.goto("about:blank")

        # Keep browser open
        browser.wait_for_event("disconnected")


if __name__ == "__main__":
    main()

Enjoy

Thursday, December 4, 2025

Android java: improving application screen form factor

In AndroidManifest.xml we can define screen form factor to used (androidmanifest.xml android:screenOrientation).

These are the options

  1. "unspecified" : The default value. The system chooses the orientation. The policy it uses, and therefore the choices made in specific contexts, might differ from device to device.
  2. "behind" : The same orientation as the activity that's immediately beneath it in the activity stack.
  3. "landscape" : Landscape orientation (the display is wider than it is tall).
  4. "portrait" : Portrait orientation (the display is taller than it is wide).
  5. "reverseLandscape" : Landscape orientation in the opposite direction from normal landscape. Added in API level 9.
  6. "reversePortrait" : Portrait orientation in the opposite direction from normal portrait. Added in API level 9.
  7. "sensorLandscape" : Landscape orientation, but can be either normal or reverse landscape based on the device sensor. The sensor is used even if the user has locked sensor-based rotation. Added in API level 9.
  8. "sensorPortrait" : Portrait orientation, but can be either normal or reverse portrait based on the device sensor. The sensor is used even if the user has locked sensor-based rotation. However, depending on the device configuration, upside-down rotation might not be allowed. Added in API level 9.
  9. "userLandscape" : Landscape orientation, but can be either normal or reverse landscape based on the device sensor and the user's preference. Added in API level 18.
  10. "userPortrait" : Portrait orientation, but can be either normal or reverse portrait based on the device sensor and the user's preference. However, depending on the device configuration, upside-down rotation might not be allowed. Added in API level 18.
  11. "sensor" : The device orientation sensor determines the orientation. The orientation of the display depends on how the user is holding the device. It changes when the user rotates the device. Some devices, though, don't rotate to all four possible orientations, by default. To use all four orientations, use "fullSensor". The sensor is used even if the user locked sensor-based rotation.
  12. "fullSensor" : The device orientation sensor determines the orientation for any of the four orientations. This is similar to "sensor", except this allows for any of the four possible screen orientations regardless of what the device normally supports. For example, some devices don't normally use reverse portrait or reverse landscape, but this enables those orientations. Added in API level 9.
  13. "nosensor" : The orientation is determined without reference to a physical orientation sensor. The sensor is ignored, so the display doesn't rotate based on how the user moves the device.
  14. "user" : The user's current preferred orientation.
  15. "fullUser" : If the user has locked sensor-based rotation, this behaves the same as user, otherwise it behaves the same as fullSensor and allows any of the four possible screen orientations. Added in API level 18.
  16. "locked" : Locks the orientation to its current rotation, whatever that is. Added in API level 18. 

Warning: To improve the layout of apps on form factors with smallest width >= 600dp, the system ignores the following values of this attribute for apps that target Android 16 (API level 36):

  1.     portrait
  2.     landscape
  3.     reversePortrait
  4.     reverseLandscape
  5.     sensorPortrait
  6.     sensorLandscape
  7.     userPortrait
  8.     userLandscape 

To create variant for table,  Goto main activity, on your main activity dropdown select "create table qualifier", it will create copy of main activity that require to modify to match tablet out. the layout is on layout -> main_activity -> main_activity.xml (sw600dp).

I use:

  1. main_activity.xml
  2. main_activity.xml (sw600dp)
main_activity.xml will be use for all mobile device, don't create main_activity (land). main_activity.xml (sw600dp) will be use on tablet. 

In case you want to lock phone to use portrait, delete main_activity.xml (land), you need to remove android:screenOrientation from AndroidManifest.xml. To make it consistent, this is sample of code in java for your main activity

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 1. Enable edge-to-edge for Java Activities
        // You need to import androidx.activity.EdgeToEdge;
        EdgeToEdge.enable(this);
        super.onCreate(savedInstanceState);
        // Fragment
        // layout portrait or lanscape?
        // portrait my_fragment_container
        // langsacpe fragment_config_container, fragment_main_container
        setContentView(R.layout.activity_m); // general, let system choose between portrait and landscape
        // Check if landscape (two-pane) layout is active
        View config = findViewById(R.id.fragment_config_container);
        View main = findViewById(R.id.fragment_main_container);
        isTwoPane = (config != null && main != null); // already checked not null
        // Conditional Orientation Lock
        if (!isTwoPane) {
            // If single-pane (phone), force portrait before fragment transactions
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        }
        if (isTwoPane) {
            // dual-pane
            setFragment(R.id.fragment_config_container, new FragmentConfig());
            setFragment(R.id.fragment_main_container, new FragmentMain());
        } else {
            // single-pane
            // all phone must use this activity_m.xml (land)
            setFragment(R.id.my_fragment_container, new FragmentMain());
        }
        ...
    }

    private void setFragment(int containerId, Fragment fragment) {
        View container = findViewById(containerId);
        if (container == null) {
            //Log.e("dedetok", "Container ID " + containerId + " not found in this layout!");
            return;  // prevent crash
        }
        FragmentTransaction transaction = fragmentManager.beginTransaction();
        transaction.replace(containerId, fragment);
        transaction.commit();

    } 

Immediately, lock screen for single pane (phone) to portrait, i.e. setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); 

Wednesday, December 3, 2025

Debian 13: bash script to show nvme/ssd healthy and cpu temperature

Requirement

  1. smartmontools
  2. lm-sensors
  3. psensor 

Installation

# apt-get install smartmontools lm-sensors psensor

copy paste this script to show general temperature on nvme/ssd and cpu. It also give information about nvme/ssd healthy.

#!/bin/bash
# this code generated by chatgpt.com no sign in
# each step was interactive, output from pc will be feed to chatgpt to generate correct output

DRIVE=${1:-/dev/sda}

echo "=== SMART Drive Information ($DRIVE) ==="

sudo smartctl -H -A "$DRIVE" | awk '
/^SMART overall-health/      {print "Drive Health: " $NF}
/^194 Temperature_Celsius/   {print "Drive Temperature: " $10 "°C"}
/^190 Airflow_Temperature/   {print "Drive Temperature: " $10 "°C"}
'

echo
echo "=== CPU Temperature ==="
sensors | awk '
/k10temp/ {found=1}
/temp1:/ && found {print "CPU Temp:", $2; found=0}
'

echo
echo "=== GPU Temperature (AMDGPU) ==="
sensors | awk '
/amdgpu/ {found=1}
/edge:/ && found {print "GPU Temp:", $2; found=0}
'

echo
echo "=== ACPI Temperatures ==="
sensors | awk '
/acpitz/ {block=1}
/temp/ && block {print "ACPI " $1, $2}
/^\s*$/ {block=0}
'

Change permission

# chmod u+x ./checkme.sh

Run as sudo or root 

Tuesday, December 2, 2025

Android java: Releasing resource Activity/Fragment <-> AndroidViewModel <-> MyController

Activity/Fragment <-> AndroidViewModel <-> MyController

MyController will hold

  1. Executor
  2. MediaController 
  3. Network access
  4. Database access
  5. Application context for above operation  

Proper way to clear the resource

  1. Executor
            // MyAndroidViewModel onClear() has been reached
            if (myExecutor!=null) {
                myExecutor.shutdown();
                myExecutor = null;
            }
  2. Media Controller
            if (myMediaController!=null) {
                myMediaController.releaseMediaSession(); // MUST BE CLEAR
                myMediaController = null;
            }
  3. Network access
            myGetRadioLogo=null;
  4. Database access  
            myDBAccess=null;
  5. Application context
            appContext=null; // MUST SET NULL AT ONCLEAR

Fail to release these resources may lead to memory leak

Fail to release resource with not properly sequence may lead application crashed, this crash can be found on logcat.  

Debian 13: part 3 participate your vps, cofigure your vps as tor relay/exit

There 2 type of TOR network you an configure on VPS

  1. Relay
  2. Exit

Minimum system requirement:

  1. CPU 1 vcpu
  2. Ram 512MB (1GB Recommended)
  3. Bandwidth 10 Mbps in & out
  4. Traffic 100 GB in & out
  5. Disk 200 MB 
  6. swap not mention (better twice RAM size) 

Enable Debian 13 auto upgrade

# apt install unattended-upgrades
# systemctl enable unattended-upgrades
# systemctl start unattended-upgrades

Installing tor packages and key

  1. Install require software
    # apt install apt-transport-https gnupg
  2. Create /etc/apt/sources.list.d/tor.list with:
    deb     [signed-by=/usr/share/keyrings/deb.torproject.org-keyring.gpg] https://deb.torproject.org/torproject.org trixiemain
    deb-src [signed-by=/usr/share/keyrings/deb.torproject.org-keyring.gpg] https://deb.torproject.org/torproject.org trixie main
  3. installing key and tor
    # wget -qO- https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/deb.torproject.org-keyring.gpg >/dev/null
    # apt install tor deb.torproject.org-keyring
    # apt install tor

Note: you can browse using browser https://deb.torproject.org/torproject.org/ if A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc has change 

TOR relay 

TOR relay consider safer way to contribute to TOR network, It does not act as critical point entry or exit network. 

configure your tor relay /etc/tor/torrc:

Nickname    myNiceRelay  # Change "myNiceRelay" to something you like
ContactInfo your@e-mail  # Write your e-mail and be aware it will be published
ORPort      443          # You might use a different port, should you want to
ExitRelay   0
SocksPort   0 

Enable and restart tor service

# systemctl enable tor
# systemctl restart tor

TOR Exit

Exit TOR may have high security risk including breaking law. you need to carefully decide your county's rules where your vps located. Some country has aware with Exit TOR node.  

install unbound dns resolver

  1. install unbound
    # apt install unbound
  2. configure dns resolver using localhost
    # cp /etc/resolv.conf /etc/resolv.conf.backup
    # echo nameserver 127.0.0.1 > /etc/resolv.conf
  3. protect resolver from change
    # chattr +i /etc/resolv.conf
  4. edit unbound configuration
    server:    ...
        qname-minimisation: yes
        ...
  5. enable and restart unbound
    # systemctl enable unbound
    # systemctl restart unbound

Definitely do not use torproject.org as a domain name for your reverse DNS

configure your tor relay /etc/tor/torrc:

Nickname    myNiceRelay  # Change "myNiceRelay" to something you like
ContactInfo your@e-mail  # Write your e-mail and be aware it will be published
ORPort      443          # You might use a different port, should you want to
ExitRelay   1            # it is exit relay
SocksPort   0
RunAsDaemon 1
#IPv6Exit 1               # Advertise and allow IPv6 exits automatically
#AvoidDiskWrites 1
#Sandbox 1
# example policy
ExitPolicy reject *:25,reject *:119,reject *:135,reject *:445,reject *:563,reject *:1214,reject *:4661:4666,reject *:6346,reject *:6697,reject *:6881:6999
ExitPolicy accept *:80,accept *:443,accept *:53,accept *:443
## === Bandwidth limits ===
#RelayBandwidthRate 3 MBytes
#RelayBandwidthBurst 5 MBytes
## === Accounting (optional traffic cap) ===
#AccountingMax 150 GBytes
#AccountingStart day 00:00
## === Logging ===
Log notice file /var/log/tor/notices.log

Enable and restart tor service

# systemctl enable tor
# systemctl restart tor

Wait until tor network recognize your node.

Done, now you are on track to run vps as tor volunteer.

Windows 11: installing windows 11 and post configuration part 2

Copy your windows iso into Ventoy USB data partition, not in boot partition. you can create folder windows and put iso file in this directory. Ventoy boot manager will find any bootable iso file in Ventoy data partition. see here to create Ventoy USB boot manager https://dedetoknotes.blogspot.com/2025/09/debian-13-using-ventoy-to-create.html

Before you starting to. install in computer/laptop, in my case axioo Hype 5 x6, download wifi driver, it does not have Ethernet port. if you have Ethernet port, download it. if those driver are zip, extract it, and put in Ventoy data directory. e.g. after you extract file in directory wifi, copy directory wifi into Ventoy data -> driver (optional you need to create). 

To start installing windows 11, insert Ventoy USB that has been prepared before and turn your laptop/pc on. it will show which iso you want to run. 

Note: you need to change UEFI boot order if you use used nvme contains working os!

installation windows is straight forward. if you use iso windows 11 24h2 or after, you need outlook account or Hotmail if you come from generation like me ☺️. when it reach internet connection, install driver that has been provided in Ventoy USB data.

if you use windows 11 iso 23h2 or before, you can bypass it by pressing shift+f10 when windows ask internet connection and run:

OOBE\BYPASSNRO

after you enter that command, it will reboot/restart. now you can choose no connection internet and create local account. 

after finish, login into windows, go to system, update and check additional update to update hardware driver. update all driver, include printer you want to use. 

if you need software/driver for printer and scanner, find it in windows store and install it. for HP printer inktank multi function, you need to update windows driver. windows said it is optional, but in my opinion it is bridge between system and HP Smart from windows store.

Tweak your windows post installation (optional)

Turning off fast boot 

turn off fast boot if you want to dual boot later. if it is turn on, windows will never unmount driver/partition they use. any files and folder created from second os will be thread as not valid. it will setback to position when windows shutdown. turn off fast boot will completely unmount any drive/partition.

I usually prefer for performance, go to control panel -> system -> advanced system settings or run sysdm.cpl. select performance.

Set fix windows virtual memory

I also use fix windows Virtual Memory / swap. from sysdm.cpl, select Virtual Memory Settings.

select custom size. if your memory less then 16GB, the value is equal twice of your physical ram. e.g physical ram 8GB, use 8GB, if ram 4GB use 8GB. if you use 16 or above, put this value 4GB. set minimum and maximum with the same value e.g 4GB in my case axioo Hype 5 x6 with ram ddr5 2x8GB.

note: there is performance penalty when you windows run out of physical ram and use swap into virtual memory, i.e your nvme. event it is fast as close to pci speed, it will slower due to overhead swap ram to disk vice versa and also reduce your nvme life! when you need bigger memory, do upgrade instead of make virtual memory bigger.

Monitor your NVME

install CrystalDiskInfo to monitor NVME, don't let run your nvme run on high temperatur for long time wihtout proper heat sink or cooling system, especially of laptop. In my option running nvme on 70°C or above on a long time may damage your nvme's chips.

Using windows encryption (bitlocker) on nvme may arise higher activity and increase temperature. if it is not urgent, don't use it.  when it bitlocker turning on or turning off, it will start encrypt or decrypt your entire harddisk. in Task manager it will show 100% harddisk utilization and at CrystalDiskInfo the temperature may arise

Adding Local Account 

for windows with link account to outlook or Hotmail, here is how you create local account.

  1. run cmd as administor 
  2. command to create local user
    > net user myname mypassword /add
  3. to set user myname as administor 
    > net localgroup administrators myname /add
  4. to verify user 
    > net user
  5. sign out and try sign in with local account.

note; you can remove account linked to outlook or Hotmail using local administrator user created before. 











Wednesday, November 26, 2025

Windows 11: clean install preparation part 1

This is documentation when I need to reinstall windows 11, because of nvme damage (overheat). It is Axioo 5 hype X6 with Ryzen 6600h. It has windows 11 installed, and this laptop does have ethernet port. 

Download ISO should I download

  1. 25h2: shift+f10 keyboard input has been disabled (download from archive.org/)
  2. 24h2: shift+f10 keyboard input has been disabled (download from archive.org/)

I did not try 23h2 version, it will take more time to install and laptop must be ready for studying.

During installation shift+f10 will bypass network configuration, you have to run

oobe\bypassnro

after terminal. it will restart installation.  

If you want to use 25h2 and 24h2, download the driver from laptop provider website. in my case download from driver.axiooworld.com, just network driver, others can be installed at the end of windows 11 installation. If you want to use only Windows skip step below, and go to part 2 installation windows.

This below instruction uses other pc/laptop to prepare nvme disk. 

Optional but recommended for dual OS, create EFI System partition (ESP) for new nvme, the minimum size is 500MB. this is step on debian using gparted.

Put a nvme, into nvme ssd/nvme external usb, and connect it your laptop.

Check your nvme

# dmesg
...
[  262.395926] scsi 0:0:0:0: Direct-Access     ADATA LE GEND 710         0213 PQ: 0 ANSI: 6
[  262.397870] sd 0:0:0:0: Attached scsi generic sg0 type 0
[  262.398189] sd 0:0:0:0: [sda] 500118192 512-byte logical blocks: (256 GB/238 GiB)
...
# lsblk
NAME        MAJ:MIN RM   SIZE RO TYPE MOUNTPOINTS
sda           8:0    0 238.5G  0 disk 
# fdisk -l
...
Disk /dev/sda: 238.47 GiB, 256060514304 bytes, 500118192 sectors
Disk model: GEND 710        
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

this is mount point /dev/sda.

I use this schema

  1. 500 MB ESP (FAT32 Flag boot and esp)
  2. 120 GB Windows system (NTFS)
  3. 30 GB Data Windows (NTFS
  4. 80 GB Debian / (ext4)
  5. 4 GB swap (swap)

 To use GParted to create partition on nvme (Be careful not select wrong disk)

  1. Open GParted
  2. Select Gparted -> devices -> /dev/sda, If it a new device (never use), you need to create parititon table, Device -> Create partition table, choose 'gpt'
  3. select unallocated space, and create EFI System Partition (ESP) for boot loader
        File System: FAT32
        Size: At least 300 MiB (recommended 500 MiB for future needs)
        Label: Optional, but you can name it "EFI". 
  4. Apply all operation 
  5. Now you can manage flag for ESP, select partition EFI,right click -> Manage Flags -> check 'boot' and 'esp' 

Note: if you try to create new partition in brand new nvme (never used), it will reported No Partition table found, it is really a new device.

Just, to create the partition, the Adata legend 710 heat sink feel a bit warmer.

You can not download windows 11 multi iso from windows https://www.microsoft.com/en-us/software-download/windows11. You can continue with windows 11 installation, it will put boot loader in EFI system partition (ESP) and next you want to install Debian.


Sunday, November 23, 2025

Using private Private DNS in Android 9 and above for more for security

Sometimes we disappointed to use isp's dns resolver. whatever the reasons, since Android 9 (Pie) and later versions, user can change his/her android phone to use Private DNS. Only one DNS server can be used, it does not support multiple DNS resolver! 

Original list was taken from https://www.geeksforgeeks.org/android/how-to-enable-private-dns-on-android/ with additional text.

Private DNS Hostname List for Android

  1. Google DNS: dns.google
  2. Quad9: dns.quad9.net
  3. Cleanbrowsing DNS: security-filter-dns.cleanbrowsing.org
  4. Open DNS: dns.opendns.com for dot, https://doh.opendns.com/dns-query for doh, 208.67.222.222 
  5. NextDNS: your-id.dns.nextdns.io 45.90.28.0 (custom IPv4) require registration to get id and proper full qualified hostname
  6. Cloudflare : cloudflare-dns.com 1.1.1.1

Please check their website or use search engine for more detail and update information.

When you use private DNS in public network, your mobile phone will use your private DNS resolver that have you define. It won't use DNS from public access point. This will prevent you from "man in the middle attack". 

Warning: You also can bypass country blocked to reach your desire web. You don't need vpn if private DNS can solved country limitation.