Sunday, May 24, 2026

Java: using maven in Debian 13

Apache Maven is an open-source build automation and project management tool primarily used for Java applications. It simplifies the development process by managing a project's build, reporting, and documentation from a single piece of information.

To install maven:

# apt install maven
Installing:                     
  maven

Installing dependencies:
...

Showing version

$ mvn --version
Apache Maven 3.9.9
Maven home: /usr/share/maven
Java version: 21.0.11, vendor: Debian, runtime: /usr/lib/jvm/java-21-openjdk-amd64
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.12.88+deb13-amd64", arch: "amd64", family: "unix"

Go to your workspace, and run

$ mvn archetype:generate \
  -DgroupId=com.dedetok.myapp \
  -DartifactId=myapp \
  -DarchetypeArtifactId=maven-archetype-quickstart \
  -DarchetypeVersion=1.5 \
  -DinteractiveMode=false
...
$ ls myapp
pom.xml  src

DartifactId=myapp -> project root directory.

The main java code is ~/myapp/src/main/java/com/dedetok/myapp/App.java

package com.dedetok.myapp;

/**
 * Hello world!
 */
public class App {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

To run the project using mvn, go to folder myapp, edit pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.dedetok</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <build>
        <plugins>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.13.0</version>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.6.3</version>
                <configuration>
                    <mainClass>com.dedetok.myapp.App</mainClass>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.4.2</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.dedetok.myapp.App</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>

        </plugins>
    </build>

</project>

then run:

$ mvn package

Run your application 


$ mvn exec:java -Dexec.mainClass="com.dedetok.myapp.App"
[INFO] Scanning for projects...
[INFO] 
[INFO] --------------------------< com.dedetok:myap >--------------------------
[INFO] Building myap 1.0-SNAPSHOT
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO] 
[INFO] --- exec:3.6.3:java (default-cli) @ myap ---
Hello World!
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  8.265 s
[INFO] Finished at: 2026-05-24T14:59:08+07:00
[INFO] -----------------------------------------

 

 

 

 

 

Tuesday, May 19, 2026

Debian 13: configurating Syris RD200-M1-G v01.55 Card Reader Part 2, detecting card reader in Debian

Add you as group of dialout (20)


# usermod -aG dialout myuser
# usermod -aG uucp myuser
# usermod -aG lock myuser
# usermod -aG tty myuser

Note: 

  1. All of these groups may not exist on every Linux distro. (Note, this process must only be done once for each user)
  2. if you use terminal only, this will effect directly, but if you use window Manager, you require to logout and re-login to make it effect especially on Netbeans, If aftar re-login fail, try restart.  

Confirm which com the reader assign


# ls -l /dev/ttyACM0
crw-rw---- 1 root dialout 166, 0 May 19 13:43 /dev/ttyACM0

The card reader assign com at /dev/ttyACM0

This is simple java code to read card when tap on device


import com.fazecast.jSerialComm.SerialPort;
import java.io.InputStream;
import java.io.OutputStream;

/**
 *
 * @author dedetok
 */
public class DedetokSerialCom {

    static private SerialPort serialPort;
    static private int boudRate = 115200;
            
    public static void main(String[] args) {
        System.out.println("Hello World!");
        
        serialPort = SerialPort.getCommPort("/dev/ttyACM0");
        
        // Configure serial port
        serialPort.setBaudRate(boudRate);
        serialPort.setNumDataBits(8);
        serialPort.setNumStopBits(SerialPort.ONE_STOP_BIT);
        serialPort.setParity(SerialPort.NO_PARITY);
        
        // Optional timeouts
        serialPort.setComPortTimeouts(
                SerialPort.TIMEOUT_READ_SEMI_BLOCKING,
                1000,
                0
        );
        
        
        // Open port
        if (!serialPort.openPort()) {
            System.out.println("Failed to open port");
            return;
        }
        
        System.out.println("Port opened"); // debug
                
        // ------------------read mifare id card ----------------------
        System.out.println("MIFARE"); // debug
        // STX LEN CMD
        byte[] cmd = new byte[] {
                (byte)0x02, // STX
                (byte)0x01, // LEN
                (byte)0x11  // CMD Read UID
        };
        
        // Send
        serialPort.writeBytes(cmd, cmd.length);

        System.out.println("command Send"); // debug
        
        /*
        02 02 11 10 : command error
        02 02 11 01 : no card
        02 06 11 00 xx xx xx xx : mifare id xx xx xx xx
        */

        byte[] rx = new byte[64];

        int n = serialPort.readBytes(rx, rx.length);

        System.out.println("Received bytes: " + n);

                // Print raw HEX
        for (int i = 0; i < n; i++) {
            System.out.printf("%02X ", rx[i]);
        }
        System.out.println();

        // ------------------read ISO15693 inventory card ----------------------
        // NOT SUPPORTED


        System.out.println();

        serialPort.closePort();

        
        System.out.println("Port Closed"); // debug
    }
}

This is sample output with uid masked Netbeans


Hello World!
Port opened
MIFARE
command Send
Received bytes: 8
02 06 xx xx xx xx xx 52 

What are they?

02 STX
06 LENGTH
Data 1-5 xx xx xx xx xx
Checksum 52 

e.g. to read Mifare UID (0x11)

command in bytes: 02 01 11

02: run command

01: length

11: command to read Mifare UID

10 checksum ( 01 xor 11 )

if return 4 bytes: MIFARE Classic 1K/4K

if return 7 bytes: NTAG / Ultralight / DESFire

if return 8 bytes: ISO15693

Example response 4 bytes UID (classic mifare)


02 06 01 xx xx xx xx 8E 
 |  |  |  |           |
 |  |  |  |           -> checksum
 |  |  |  -------------> data uid
 |  |  ----------------> status 
 |  -------------------> length data
 ----------------------> STX

Example response 6 bytes UID


02 0A 11 00 xx xx xx xx xx xx 80 00
 |  |  |  |                    |  |
 |  |  |  |                    |  -> SAK
 |  |  |  |                    ----> checksum
 |  |  |  -------------------------> data uid
 |  |  ----------------------------> status
 |  -------------------------------> length data
 ----------------------------------> STX

 

 

 

Debian 13: configurating Syris RD200-M1-G v01.55 Card Reader Part 1, firmware upgrade

By default Syris RD200-M1-G v01.55 run in keyboard mode. It will send to text when card detected. 

You need to upgrade the firmware for support some new card and change it to com mode. At com mode, you can communication with card to utilize it.

DO THIS IN WINDOWS TO UPDATE FIRMWARE:

All documentation link provided by syris is https://syris.com/ftp/index.php. Goto SYRIS_RFID_DVD -> RD200_300 -> USB_ReaderTools
_V0291.rar (All tools and docs), download and extract. 

Inside RD200_RD300_SDK_V0193 -> UtilityTools -> FirmwareFiles, there are 2 directory v1 and v2:

  • RD200(v1)
    • RD200_M1_COM_V0271_20230901.SYB
    • RD200_M1_V0271_20230901.SYB 
  • RD200(v2)
    • RD200-M1-V2_V0290_20250603.SYB
    • RD200-M1-V2_V0291_20251029_01.SYB
    • RD200-M1-V2-COM_V0283_20240216.SYB 

Run USB_ReaderTools_V0291_20251029.exe, go to tab firmware and use RD200_M1_COM_V0271_20230901.SYB to RD200-M1-G v01.55. If you choose wrong firmware, the tools will warn you "firmware type error". upgrade it, wait until finish.

After firmware upgraded, you need to change Auto Usb to Com, e.g. com3,(RD200-M1 0155) 12110056 -> (RD200-M1 0271) 12110056

Windows part is done.

This is Debian before upgrade and change to com mode


# dmesg
[  841.193326] usb 8-1: new full-speed USB device number 2 using xhci_hcd
[  841.349102] usb 8-1: New USB device found, idVendor=0e6a, idProduct=0317, bcdDevice= 1.40
[  841.349118] usb 8-1: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  841.349126] usb 8-1: Product: USB Reader
[  841.349133] usb 8-1: Manufacturer: SYRIS Technology Corp.
[  841.349139] usb 8-1: SerialNumber: 00000000
[  841.389585] input: SYRIS Technology Corp. USB Reader as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.0/0003:0E6A:0317.0004/input/input23
[  841.505357] hid-generic 0003:0E6A:0317.0004: input,hidraw3: USB HID v1.10 Keyboard [SYRIS Technology Corp. USB Reader] on usb-0000:06:00.4-1/input0
[  841.522787] input: SYRIS Technology Corp. USB Reader Consumer Control as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.1/0003:0E6A:0317.0005/input/input24
[  841.577455] input: SYRIS Technology Corp. USB Reader System Control as /devices/pci0000:00/0000:00:08.3/0000:06:00.4/usb8/8-1/8-1:1.1/0003:0E6A:0317.0005/input/input25
[  841.577717] hid-generic 0003:0E6A:0317.0005: input,hidraw4: USB HID v1.10 Device [SYRIS Technology Corp. USB Reader] on usb-0000:06:00.4-1/input1
# lsusb
...
Bus 008 Device 002: ID 0e6a:0317 Megawin Technology Co., Ltd USB Reader
...
# lsusb -v -d 0e6a:0317

Bus 008 Device 002: ID 0e6a:0317 Megawin Technology Co., Ltd USB Reader
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            0 [unknown]
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0e6a Megawin Technology Co., Ltd
  idProduct          0x0317 USB Reader
  bcdDevice            1.40
  iManufacturer           1 SYRIS Technology Corp.
  iProduct                2 USB Reader
  iSerial                 3 00000000
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0042
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0xa0
      (Bus Powered)
      Remote Wakeup
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      1 Boot Interface Subclass
      bInterfaceProtocol      1 Keyboard
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 (null)
          wDescriptorLength      73
          Report Descriptors: 
            ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0008  1x 8 bytes
        bInterval              10
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass         3 Human Interface Device
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      0 
      iInterface              0 
        HID Device Descriptor:
          bLength                 9
          bDescriptorType        33
          bcdHID               1.10
          bCountryCode            0 Not supported
          bNumDescriptors         1
          bDescriptorType        34 (null)
          wDescriptorLength     861
          Report Descriptors: 
            ** UNAVAILABLE **
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               1
Device Status:     0x0000
  (Bus Powered)

After firmware upgrade and com mode


# dmesg
[   65.250090] usb 8-1: New USB device strings: Mfr=1, Product=2, SerialNumber=0
[   65.250095] usb 8-1: Product: USB Reader
[   65.250098] usb 8-1: Manufacturer: SYRIS Technology Corp.
[   65.293079] cdc_acm 8-1:1.0: ttyACM0: USB ACM device
[   65.293108] usbcore: registered new interface driver cdc_acm
[   65.293110] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN 
# lsusb
...
Bus 008 Device 002: ID 0e6a:0316 Megawin Technology Co., Ltd USB Reader
...
# lsusb -v -d 0e6a:0316
Bus 008 Device 002: ID 0e6a:0316 Megawin Technology Co., Ltd USB Reader
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
  bLength                18
  bDescriptorType         1
  bcdUSB               1.10
  bDeviceClass            2 Communications
  bDeviceSubClass         0 [unknown]
  bDeviceProtocol         0 
  bMaxPacketSize0         8
  idVendor           0x0e6a Megawin Technology Co., Ltd
  idProduct          0x0316 USB Reader
  bcdDevice            1.06
  iManufacturer           1 SYRIS Technology Corp.
  iProduct                2 USB Reader
  iSerial                 0 
  bNumConfigurations      1
  Configuration Descriptor:
    bLength                 9
    bDescriptorType         2
    wTotalLength       0x0043
    bNumInterfaces          2
    bConfigurationValue     1
    iConfiguration          0 
    bmAttributes         0x80
      (Bus Powered)
    MaxPower              100mA
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        0
      bAlternateSetting       0
      bNumEndpoints           1
      bInterfaceClass         2 Communications
      bInterfaceSubClass      2 Abstract (modem)
      bInterfaceProtocol      1 AT-commands (v.25ter)
      iInterface              0 
      CDC Header:
        bcdCDC               1.10
      CDC Call Management:
        bmCapabilities       0x01
          call management
        bDataInterface          1
      CDC ACM:
        bmCapabilities       0x06
          sends break
          line coding and serial state
      CDC Union:
        bMasterInterface        0
        bSlaveInterface         1 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x81  EP 1 IN
        bmAttributes            3
          Transfer Type            Interrupt
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval              16
    Interface Descriptor:
      bLength                 9
      bDescriptorType         4
      bInterfaceNumber        1
      bAlternateSetting       0
      bNumEndpoints           2
      bInterfaceClass        10 CDC Data
      bInterfaceSubClass      0 [unknown]
      bInterfaceProtocol      0 
      iInterface              0 
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x82  EP 2 IN
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
      Endpoint Descriptor:
        bLength                 7
        bDescriptorType         5
        bEndpointAddress     0x03  EP 3 OUT
        bmAttributes            2
          Transfer Type            Bulk
          Synch Type               None
          Usage Type               Data
        wMaxPacketSize     0x0040  1x 64 bytes
        bInterval               0
Device Status:     0x0000
  (Bus Powered)

 

 

Saturday, May 16, 2026

Android java: calculating grid column and grid row

Target emoji size 48 dp


        // ------------ EMOJI PICKER
        // target emoji size
        int finalWidth=xxx,finalHeight=yyy; // final value for IME View
        int orientationIdx = (cfg.orientation == Configuration.ORIENTATION_PORTRAIT) ? 0 : 1;
        float density = context.getResources().getDisplayMetrics().density; // 
        final float targetEmojiSize = 48; // dp
        //final float targetEmojiSize = (orientationIdx==0) ? 48f : 40f; // orientationIdx==0 -> portrait

        //final float targetEmojiWidthPx = targetEmojiSize * (mXdpi / 160f);
        //final float targetEmojiHeightPx = targetEmojiSize * (mYdpi / 160f);
        //emojiGridColumns = Math.max(1, (int) (finalWidth/targetEmojiWidthPx));
        //emojiGridRows = Math.max(1, finalHeight/targetEmojiHeightPx;
        float targetEmojiSizePx = targetEmojiSize*density;  // landscape too small
        //float targetEmojiSizePx = (orientationIdx==0) ?
        //          targetEmojiSize * (mYdpi / 160f) :
        //        targetEmojiSize * (mXdpi / 160f) ;

        emojiGridColumns = Math.max(3, (int) (finalWidth/targetEmojiSizePx));
        //emojiGridRows = Math.max(1f,
        //        ((finalHeight*0.8f)/targetEmojiSizePx)-1f); // we just use 0.8 of finalHeight
        emojiGridRows = (orientationIdx==0) ?
                Math.max(1f,
                ((finalHeight*0.8f)/targetEmojiSizePx)) :
                Math.max(1f,
                        ((finalHeight*0.8f)/targetEmojiSizePx)-1)
        ; // we just use 0.8 of finalHeight

        Log.e("dedetok", "targetEmojiSize "+targetEmojiSize+" targetEmojiSizePx "+targetEmojiSizePx); // debug
        Log.e("dedetok", "finalWidth "+finalWidth+" finalHeight "+finalHeight); // debug
        Log.e("dedetok", "emojiGridColumns "+emojiGridColumns+" emojiGridRows "+emojiGridRows); // debug

 Note, you can remove *0.8f, in this context, the emoji only occupied 0.8 of finalHeight, not full. -1 is magic forcing internal emoji picker to reduce row and recalculate the emoji size. 

Friday, May 15, 2026

Android java: calculation for ime wreda keyboard build 2026.09

Code


    // Emoji column and row
    int emojiGridColumns = 8; // store number of emoji
    float emojiGridRows = 3.5f; // store row in emoji
    // IME View size
    int finalWidth=0,finalHeight=0; // final value for IME View
    
    // -------------- set ime view size and emoji -----------------------
    // old version
    private void setIMEViewSizeAndEmojiOld() {
        // 1. Get the Calculator instance
        WindowMetricsCalculator calculator = WindowMetricsCalculator.getOrCreate();
        // 2. Compute current metrics for this specific Activity
        // This works on API 24 (Split Screen) up to API 37
        WindowMetrics metrics = calculator.computeCurrentWindowMetrics(myWredaKeyboard);

        // 3. Get the bounds (Rect)
        Rect bounds = metrics.getBounds();
        float density = myWredaKeyboard.getResources().getDisplayMetrics().density;

        int screenWidth = bounds.width();
        int screenHeight = bounds.height();
        boolean isWide = screenWidth > screenHeight;
        // Handle Navigation Bar Overlap (API 24+ Compatibility)
        // MANUAL INSET
        if (isWide) {
            // In landscape, navigation bars are usually on the left or right.
            // 48dp is the standard minimum size for system bars.
            int navBarWidth = (int) (48 * density);

            // Subtract space for both sides to be safe and centered
            screenWidth = screenWidth - (2 * navBarWidth);
        }

        float screenRatio = (float) screenWidth/screenHeight;
        float smallestWidthDp = Math.min(screenWidth, screenHeight) / density;

        int numberOfKeysColumn = 10;
        int numberOfKeysRow = 5;
        float keyWHRatioPortrait = 0.5f;// width : height = 1:2


        // --- ADAPTIVE ENGINE ---

        if (screenRatio > 0.85f && screenRatio < 1.2f) {
            // 📱 FOLDABLES / SQUARE DEVICES (Unfolded state)

            // 1. Keep it short. 25% is good because the screen is huge vertically.
            finalHeight = (int) (0.25f * screenHeight);

            // 2. Calculate the "Perfect Square" key size
            // If keys are square, width = height.
            int keyHeight = finalHeight / numberOfKeysRow;

            // 3. Set Width based on the Square Key
            // We multiply keyHeight by columns to get a keyboard that fits the keys perfectly.
            int idealWidth = keyHeight * numberOfKeysColumn;

            // 4. Center it
            // This ensures the keyboard doesn't stretch. It will sit in the middle
            // with empty space on the sides, making it reachable for thumbs.
            finalWidth = Math.min(screenWidth, idealWidth);

            // Debug for Foldable
            //Log.e("dedetok", "Foldable Mode active. Width constrained to: " + finalWidth); // debug

        } else if (isWide) {

            // 📺 LANDSCAPE (TV, Tablet, Wide Phone)
            // Goal: "Fat" Keys (Wide). You mentioned "landscape make it fat"
            // To make them fat (wider than tall), we DIVIDE by the 0.5 ratio

            if (smallestWidthDp >= 600) {
                // ️ TABLET LANDSCAPE: Keep it shorter so it doesn't cover the whole screen
                finalHeight = (int) (0.45f * screenHeight);
            } else {
                //  PHONE LANDSCAPE: Make it "bigger" to be usable
                finalHeight = (int) (0.55f * screenHeight);
            }
            // Re-calculate keyHeight AFTER setting finalHeight
            int keyHeight = finalHeight / numberOfKeysRow;
            int idealWidth = (int) (keyHeight / keyWHRatioPortrait * numberOfKeysColumn);
            //Log.e("dedetok", "screenWidth "+screenWidth+" idealWidth "+idealWidth);// debug

            // Use min to ensure it doesn't bleed off screen
            finalWidth = Math.min(screenWidth, idealWidth);

        } else {
            // phone
            if (smallestWidthDp >= 600) {
                // 🏗️ TABLET LANDSCAPE: Keep it shorter so it doesn't cover the whole screen
                finalHeight = (int) (0.30f * screenHeight);
                int keyHeight = finalHeight / numberOfKeysRow;
                int idealWidth = (int) (keyHeight / keyWHRatioPortrait * numberOfKeysColumn);

                // Use min to ensure it doesn't bleed off screen
                finalWidth = Math.min(screenWidth, idealWidth);
            } else {
                // 📱 PHONE PORTRAIT: Make it "bigger" to be usable
                finalHeight = (int) (0.35f * screenHeight);
                finalWidth = screenWidth;
            }

        }
        //Log.e("dedetok", "Width: "+finalWidth+" Height: "+finalHeight); // debug

        // ------------ now calculate emoji grid column and row
        int availableHeight = (int)(finalHeight * 0.8f);
        int emojiSize;
        if (screenRatio > 0.85f && screenRatio < 1.2f) {
            // Treat foldables like portrait tablets
            int targetRows = 4;
            int emojiSizeBox = (availableHeight / targetRows) + 6;
            emojiSize = Math.max(emojiSizeBox, 120); // slightly bigger for the big screen

            emojiGridColumns = finalWidth / emojiSize;
            emojiGridRows = (float) availableHeight / emojiSize;
        } else if (isWide && smallestWidthDp < 600) {
            // Force 9 columns to make emojis large enough to only fit ~2 rows
            emojiGridColumns = 9;
            emojiSize = finalWidth / emojiGridColumns;
            emojiGridRows = (float) availableHeight / emojiSize;
        } else {
            // Portrait: Standard 4 rows
            int targetRows = 4;
            int emojiSizeBox = (availableHeight / targetRows) + 6; // 6 space
            emojiSize = Math.max(emojiSizeBox, 100);

            emojiGridColumns = finalWidth / emojiSize;
            emojiGridRows = (float) availableHeight / emojiSize;
        }
    }    

Build 2026.09