Android Version Application Adaptation Guide

Background

This column aims to help developers using iMin POS machines quickly understand the content that applications may need to adapt after Android version upgrades. You can compare whether your company's current application involves the modifications mentioned below, allowing your application to adapt more quickly and be applied to higher version POS machines.

Upgrading from Android 11 to Android 13

Description

Google official change description: https://developer.android.google.cn/about/versions/13/migration?hl=zh-cn

1. Feature and Permission Changes

1. PendingIntent Changes

Friendly Reminder

If your application has not been adapted to Android 12 or above and uses PendingIntent, it may throw exceptions causing crashes. You can adapt according to the instructions below or visit the Android developer website for more detailed information!

Exception as follows:

Task exception on worker thread: java.lang.IllegalArgumentException: com.imin.apitest: 
Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE 
be specified when creating a PendingIntent

The core change of PendingIntent is the mandatory requirement of FLAG_MUTABLE and FLAG_IMMUTABLE flags, which was introduced in Android 12 and remains a mandatory rule in Android 13.

1.1. Main Changes in Android 12 and Above

Change Description:

  • Mandatory Flags: When creating a PendingIntent, you must explicitly specify its mutability: FLAG_MUTABLE (mutable) or FLAG_IMMUTABLE (immutable).

  • Security Enhancement: This aims to prevent malicious apps from intercepting and tampering with the internal Intent of PendingIntent, thereby improving security.

  • Default Behavior Removed: In Android 11 and earlier, there was a default behavior when no flag was specified. This default behavior has now been removed, and not specifying a flag will cause an IllegalArgumentException.

Adaptation Solution:

  • Prioritize FLAG_IMMUTABLE: Unless you know exactly that you need to allow the system or other apps to modify the Intent you put in PendingIntent, you should always use FLAG_IMMUTABLE for security. This is the most common case.

  • Use FLAG_MUTABLE Only When Needed: For example, when you provide PendingIntent to system UI (such as custom notification button actions) and want the system to fill in some additional data when sending back the Intent (such as extra data for ACTION_SEND), you need to use FLAG_MUTABLE.

Code Examples:

Case 1: Most scenarios, use FLAG_IMMUTABLE

For example, USB device permission request, when your app needs to access a USB device, user authorization must be obtained

private static final String ACTION_USB_PERMISSION = "com.example.USB_PERMISSION";

// Create PendingIntent for permission request
private void requestUSBPermission(UsbDevice device) {
    UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);

    // Create BroadcastReceiver to receive permission result
    IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
    registerReceiver(usbPermissionReceiver, filter);

    // Create PendingIntent
    Intent permissionIntent = new Intent(ACTION_USB_PERMISSION);
    permissionIntent.setPackage(getPackageName()); // Ensure sent to current app

    PendingIntent pendingIntent = PendingIntent.getBroadcast(
        this,
        0,
        permissionIntent,
        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
    );

    // Request permission
    usbManager.requestPermission(device, pendingIntent);
}

Case 2: Scenarios requiring system to modify Intent, use FLAG_MUTABLE

// For example, an Action Button in a notification for replying to messages
// The system needs to add user input text as an Extra to your Intent

Intent replyIntent = new Intent(this, MessageReplyReceiver.class);
replyIntent.setAction("com.yourapp.ACTION_REPLY");

PendingIntent replyPendingIntent = PendingIntent.getBroadcast(
    applicationContext,
    conversationId, // Use unique ID
    replyIntent,
    PendingIntent.FLAG_MUTABLE // Allow system to modify this Intent
);

// Then set replyPendingIntent to the notification Action
Notification.Action action = new Notification.Action.Builder(
    Icon.createWithResource(this, R.drawable.ic_reply),
    "Reply",
    replyPendingIntent
).build();

Modification Instructions:

//Previous usage
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0);

//After upgrading to Android 13
PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 
    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
1.2. Android 13 Further Refinement of PendingIntent Behavior

Android 13 further refines the behavior of mutable PendingIntent, requiring developers to declare its purpose more precisely.

Change Description:

For PendingIntent using FLAG_MUTABLE, it is now possible (and recommended) to pre-specify its target component through setter methods (such as setActivity, setBroadcast, setService). This provides more information to the system and helps with security.

Adaptation Solution (for scenarios using FLAG_MUTABLE):

// Android 13+ safer way to create mutable PendingIntent (for Broadcast)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    val replyIntent = Intent("com.yourapp.ACTION_REPLY")
    val replyPendingIntent: PendingIntent = PendingIntent.getBroadcast(
        applicationContext,
        conversationId.toInt(),
        replyIntent,
        PendingIntent.FLAG_MUTABLE
    )
} else {
    // ... Old version code
}

Note

For getActivity and getService, the API itself already implies the target component, so this change has the greatest impact on getBroadcast.

2. Changes in Component Declaration Attribute export

In Android 12 and higher, the android:exported attribute must be explicitly set. Setting it to true makes the component callable by other apps.

Change Description:

Android 12 (API 31) introduced stricter security requirements, requiring all <activity>, <service>, and <receiver> components to explicitly declare the android:exported attribute.

Role of android:exported:

  • true: Allows other apps (or the system) to start this component (such as through implicit Intent).

  • false: Only accessible by the same app or apps with the same user ID.

  • Not set: Will directly report an error in Android 12+.

Adaptation Solution:

  • Main Activity: Usually set to true (otherwise cannot be launched through desktop icon).

  • Background Service: If it needs to be called by other apps (such as accessibility), set to true; otherwise set to false

  • BroadcastReceiver: System broadcasts (such as boot startup) need to be set to true, custom broadcasts can be set to false.

Code Example:

<manifest ...>
    <application ...>
        <!-- Main Activity needs to be set to exported=true -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Background Service (only for internal app calls) -->
        <service
            android:name=".InternalService"
            android:exported="false" />
    </application>
</manifest>

3. Security Changes

Change Description: Stop using shared user ID

Adaptation Solution:

If your app uses the deprecated android:sharedUserId attribute and no longer depends on its functionality, you can set the android:sharedUserMaxSdkVersion attribute to 32

Code Example:

<manifest ...>
    <!-- To maintain backward compatibility, continue to use
    "android:sharedUserId" if you already added it to your manifest. -->
    android:sharedUserId="SHARED_PACKAGE_NAME"
    android:sharedUserMaxSdkVersion="32"
    ...
</manifest>

This attribute tells the system that your app no longer depends on the shared user ID. If your app declares android:sharedUserMaxSdkVersion and is first installed on a device running Android 13 or higher, the app behaves as if you never defined android:sharedUserId. Updated apps will still use the existing shared user ID.

Note

If you have already defined the android:sharedUserId attribute in your manifest, do not remove it. Doing so will cause app updates to fail.

Recommendation

For third-party apps, it is strongly not recommended to use sharedUserId anymore. This feature itself is considered outdated and insecure by Google, as it breaks Android's inherent app sandbox isolation mechanism. It is recommended to use ContentProvider

4. Notification Runtime Permission

Follow best practices, such as requesting permission when users interact with notification-related features, and explain the purpose of the permission.

Change Description:

Starting from Android 13 (API level 33), apps need to request a new runtime permission (POST_NOTIFICATIONS) to send notifications to users, not just declare it in the manifest file.

Adaptation Solution:

  • Update your app's targetSdkVersion to 33 or higher.

  • Declare the POST_NOTIFICATIONS permission in your app's manifest file.

  • At runtime, request this permission from users through the Activity's requestPermissions method or ActivityResultContracts.RequestPermission contract.

Code Example:

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

2. Common Issues

1. Presentation Changes

Description:https://developer.android.google.cn/reference/kotlin/android/view/WindowManager.LayoutParams

If you previously performed the following operations in the Presentation implementation class (usually used for secondary screen display)

if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    // For details such as picture in picture, please check the android sdk
    getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}else {
    // Android versions below 8.0 use the following apis to achieve the above functions
    getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
}

May report the following error:

java.lang.IllegalArgumentException: Window type mismatch. Window Context's window type is 2037, 
while LayoutParams' type is set to 2038. Please create another Window Context via 
createWindowContext(getDisplay(), 2038, null) to add window with type:2038

You can refer to the following modification:

if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.S_V2){
    //Set to 2037 or don't set to use the default type
    getWindow().setType(2037);
}else if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
    // For details such as picture in picture, please check the android sdk
    getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}else {
    // Android versions below 8.0 use the following apis to achieve the above functions
    getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
}

2. Unable to Access Clipboard Content

Android 13+: Apps must be visible in the foreground to read the clipboard

  • Foreground visibility requirement: Apps can only read clipboard content when visible in the foreground

  • Automatic clearing: Clipboard content will be automatically cleared after a period of time

  • Preview restrictions: Prevents apps from secretly reading the clipboard

Adaptation Strategy:

  • Read in lifecycle methods such as onResume()

  • Use clipboard listeners combined with foreground status checks

  • Provide user-friendly prompt information

Compatibility:

Lower versions of Android still work as before

Determine if the app is in the foreground:

public boolean canReadClipboard(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();

        if (appProcesses != null) {
            for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
                if (appProcess.pid == android.os.Process.myPid()) {
                    return appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
                }
            }
        }
        return false;
    }
    return true;
}

Upgrading from Android 13 to Android 15

Description

Google official change description: https://developer.android.google.cn/about/versions/15/get?hl=zh-cn

1. Feature and Permission Changes

1. Minimum Installable Target API Level

Change Description:

Users cannot install apps with targetSdkVersion lower than 24.

Adaptation Solution:

Need to upgrade targetSdkVersion to be greater than 24 as soon as possible. If you need to upload to major app stores (such as Google Play, Xiaomi/Huawei/OPPO/Vivo app markets), you need to adjust targetSdkVersion according to the platform's upload rules. It is recommended to upgrade to 35.

Code Example:

android {
    compileSdk = 35

    defaultConfig {
        applicationId 'com.imin.xxx'
        minSdkVersion 30
        targetSdkVersion 35  //Need to be greater than or equal to 24, recommended to upgrade to 35
        ...
        ...
    }
}

2. Support for 16 KB Page Size

Official Documentation:https://developer.android.google.cn/guide/practices/page-sizes?hl=zh-cn#build

Note

Starting November 1, 2025, all new apps and existing app updates submitted to Google Play that target Android 15 and higher must support 16 KB page size on 64-bit devices. Domestic app stores currently have no mandatory requirements, but it is recommended to plan ahead

Change Description:

New generation devices are starting to adopt the 16KB memory page (Page Size) mechanism, gradually replacing the traditional 4KB memory page design. This underlying change has a direct impact on app compatibility, especially for apps that rely on Native layer libraries, JNI interfaces, or custom memory management modules

Adaptation Solution:

  1. Check if the app uses native code, which can be analyzed using Android Studio's APK analyzer

  2. Check the lib folder, which will host shared object (.so) files (if any). If there are any shared object files, it indicates that your app uses native code. The alignment column will display warning messages for any files with alignment issues.

  3. If there are no shared object files or no lib folder, it indicates that your app does not use native code.

Code Examples:

a. Scenario using CMake to compile so libraries

#set(CMAKE_SHARED_LINKER_FLAGS "-Wl,-z,max-page-size=16384")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-z,max-page-size=16384 -Wl,-z,common-page-size=16384")

b. Scenario using ndk-build to compile so libraries

LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384"

3. Security Changes

3.1. Restrictions on Implicit Intents and Pending Intents

Prevents malicious apps from intercepting implicit intents intended for internal app components.

Change Description:

For apps targeting Android 14 (API level 34) or higher, Android restricts apps from sending implicit intents to internal app components in the following ways:

  • Implicit intents can only be delivered to exported components. Apps must use explicit intents to deliver to non-exported components, or mark the component as exported.

  • If an app creates a mutable pending intent with an intent that does not specify a component or package, the system throws an exception

Adaptation Solution:

To start a non-exported (export=false) activity, apps should use explicit intents instead

Starting directly in the original way may throw an exception, causing the app to crash

// Throws an ActivityNotFoundException exception when targeting Android 14.
context.startActivity(new Intent("com.example.action.APP_ACTION"));

Code Example:

<activity
    android:name=".AppActivity"
    android:exported="false">
    <intent-filter>
        <action android:name="com.example.action.APP_ACTION" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>
//Start with explicit Intent
// This makes the intent explicit.
Intent explicitIntent =
        new Intent("com.example.action.APP_ACTION")
explicitIntent.setPackage(context.getPackageName());
context.startActivity(explicitIntent);
3.2. Foreground Service Type is Required

Change Description:

Android 10 introduced foregroundServiceType, Android 14 further refines foreground service type requirements, and service purpose must be explicitly declared

If your app targets Android 14 (API level 34) or higher, you must specify at least one foreground service type for each foreground service in your app. You should choose a foreground service type that represents your app's use case. The system requires specific types of foreground services to meet specific use cases.

Adaptation Solution:

Declare specific service types for foreground services in AndroidManifest.xml, and set the corresponding type when starting the service

Code Example:

<!-- AndroidManifest.xml -->
<service
    android:name=".MyForegroundService"
    android:foregroundServiceType="location|camera" />
//In service code, need to add type for versions greater than 34
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    startForeground(1,builder.build(),FOREGROUND_SERVICE_TYPE_LOCATION || FOREGROUND_SERVICE_TYPE_CAMERA);
}else{
    startForeground(1, builder.build());
}
3.3. Broadcast Receivers Registered at Runtime Must Specify Export Behavior

Change Description:

Broadcast receivers registered at runtime must specify export behavior. Apps targeting Android 14 (API level 34) or higher must explicitly declare the receiver's export status

Adaptation Solution:

When registering a broadcast receiver using Context.registerReceiver(), you must specify the RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED flag

Code Example:

IntentFilter filter = new IntentFilter("ACTION_MY_BROADCAST");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
    registerReceiver(receiver, filter);
}

RECEIVER_EXPORTED Meaning:

  • The receiver can receive broadcasts from other apps

  • Use Case: System broadcasts or broadcasts that require cross-app communication

  • Example: Battery status changes, network status changes, and other system broadcasts

RECEIVER_NOT_EXPORTED Meaning:

  • The receiver can only receive broadcasts from the app itself or the system

  • Use Case: Private broadcasts used internally within the app

  • Example: Custom Action broadcasts within the app

2. Common Issues

1.1. What to Do if Third-party SDK Does Not Support 16 KB?

Recommendations:

  • Contact the SDK provider to request an update.

  • Look for alternative solutions.

  • Consider removing the SDK.

  • If it's open source, compile a compatible version yourself

1.2. Error When Service Starts Using SharedPreferences After Setting Lock Screen

Error Message:

SharedPreferences in credential encrypted storage are not available until after user (id 0) is unlocked

Recommendation:

Monitor boot broadcast before starting the service and handling SharedPreferences

Document Update Notes

Tip

This document will be continuously updated, stay tuned