How to Handle Version Differences in Android 14 Development? Quick Adaptation Guide for Android 14!
- The system no longer grants precise alarm permissions by default, applications need to apply for them themselves.
- Android 14 no longer grants the
SCHEDULE_EXACT_ALARM
permission by default for apps targeting SDK >= 33
(By default, it is set todeny
) - This permission was introduced in Android 12 (previous note)
At that time, it was only necessary to declare it in AndroidManifest.xml Now you need to
request permission
Request permission
steps:- Use
AlarmManager.canScheduleExactAlarms()
to check for permission - Call an intent containing
ACTION_REQUEST_SCHEDULE_EXACT_ALARM
inonResume()
- Example:
override fun onResume() { … if (alarmManager.canScheduleExactAlarms()) { // Set exact alarms. alarmManager.setExact(...) } else { // Permission not yet approved. Display user notice and revert to a fallback // approach. alarmManager.setWindow(...) } }
- Use
-
The official recommendation is to remove the use of precise alarms if not necessary: Click here
- When the application enters the
cached state
, broadcasts registered by the context will be queued.- Broadcasts received at this time will enter the queue and will be returned to the app sequentially the next time the app returns to the
foreground
or leaves thecached state
. - Cached state: Simply understood as an app in the background, not currently in the foreground process, so if the system needs memory elsewhere, it can freely terminate these processes as needed.
- Only context-registered broadcasts will be queued,
static registrations will not
, such as those added in AndroidManifest.xml.
- Broadcasts received at this time will enter the queue and will be returned to the app sequentially the next time the app returns to the
- Android 14 no longer grants the
- Apps can only terminate their own background processes and cannot affect other applications.
- After targeting SDK 34, you can no longer use
killBackgroundProcesses
to close other app background processes - If your app uses this method to close other app background processes, it may no longer work in the future
- After targeting SDK 34, you can no longer use
- The MTU setting for the first GATT client requesting MTU is set to
517
bytes andignores
all subsequent MTU requests for that ACL connection.- Simply put, after the GATT client in the app creates and connects (BluetoothGatt#connect),
the first time the APIBluetoothGatt#requestMtu(int)
is used to set the MTU, the system sets it to 517 bytes - Related knowledge:
MTU (Maximum Transmission Unit)
: The maximum amount of data that can be sent in a single packet
Bluetooth Core Specification 5.2
: The official change is mainly to strictly comply with this specification. Click to view the specification - If you plan to implement Gatt connection to
Bluetooth devices
and set MTU in the future, you can refer to: This tutorial If your product actually transmits data with Bluetooth devices
,
due to MTU limitations, you may encounter many situations that need adaptation.
For example: setting MTU in Gatt, but the target Bluetooth device does not support it, you may need a contingency plan
Or the Bluetooth device needs to adjust its firmware due to changes in Android 14, etc…
- Simply put, after the GATT client in the app creates and connects (BluetoothGatt#connect),
- New permissions allow users to select which
photos
andvideos
can be accessed.- Android 14 introduces the
photo selection
permissionREAD_MEDIA_VISUAL_USER_SELECTED
-
Using
READ_MEDIA_VISUAL_USER_SELECTED
allows users toselect
whichphotos
andvideos
can be accessed
or choose toallow all
access. As shown in the image below, two options will appear for the user to choose from:
- In
Android 13
, permissions were already refined once,
usingREAD_MEDIA_VIDEO
andREAD_MEDIA_IMAGES
to accessall
photos and videos at once: Previous Android 13 notes<!-- Devices running Android 12L (API level 32) or lower --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" /> <!-- Devices running Android 13 (API level 33) or higher --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- To handle the reselection within the app on Android 14 (API level 34) --> <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
-
The above methods can be adjusted according to needs, but if
READ_MEDIA_VISUAL_USER_SELECTED
is not used in Android 14, it will use compatibility mode - Compatibility mode: Each time, it will prompt the user to select which photos and videos can be used by the app.
-
The official statement is that this is a way to enhance
user privacy
. - Practical example:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED)) } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO)) } else { requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE)) }
Or see the official permission example: Click here
- New behavior:
Non-dismissible
notifications can now bedismissed
by users. Official documentation- Android 14 has changed this behavior, allowing users to dismiss such notifications
- In simple terms: Currently using
Notification.Builder#setOngoing(true)
andNotificationCompat.Builder#setOngoing(true)
to set Notification.FLAG_ONGOING_EVENT to prevent users from dismissing foreground notificationswill no longer work
- The actual effect will be like this (left: Android 14, right: Android 11):
- However, the following two situations are still not prohibited:
- The phone is locked
- The user selects
Clear all
notifications
- And the following types are currently not affected:
- CallStyle notifications: Simply put,
phone-like
notifications For example:val builder = NotificationCompat.Builder(context, CHANNEL_ID) .setSmallIcon(R.drawable.notification_icon) .setContentTitle("Incoming call") .setContentText("Caller Name") .setPriority(NotificationCompat.PRIORITY_HIGH) .setCategory(NotificationCompat.CATEGORY_CALL) .setFullScreenIntent(fullScreenPendingIntent, true) .setOngoing(true) .addAction(R.drawable.ic_accept, "Accept", acceptPendingIntent) .addAction(R.drawable.ic_decline, "Decline", declinePendingIntent) .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
- Becoming a Device Policy Controller (DPC) and supporting software packages
- CallStyle notifications: Simply put,
- Android 14 introduces the
- Non-linear font scaling up to 200%.
- Refers to Android adjusting system support for
text font
scaling up to 200%
Before adaptation, font scaling might cause layout issues. - You can adapt using the following methods:
- a. Always use
sp
to set text font size - b. You can also set it programmatically
Using
TypedValue.applyDimension()
: sp to pixelTypedValue.deriveDimension()
: pixel to sp - c. (optional) Use sp units for
lineHeight
: If using dp or px units
In this case, text cannot scale or may look squeezed
Set both textSize and lineHeight using sp
The system will adjust line height and font size according to settings. (Mainly depends on whether your product needs to set line height) - d. Official documentation
- a. Always use
- Testing method:
Open
Settings
>Accessibility
>Display size and text
In the font size option, click the plus (+) icon to adjust to the maximum scale. - It has little impact on
compilation
, mainly requiring additional adaptation if needed, In older code, you might see font settings using dp, or some codeconverting dp to pixel
, So if encountered, you can change it conveniently, or develop a habit of using the officially recommended solution.
- Refers to Android adjusting system support for
- The minimum installation requirement for
targetSdkVersion
API level has been raised to23
.-
Mainly, after
Android 14
, only apps with targetSdkVersion>= 23
can be installed. -
However, if you just want to test during development, you can use the following adb command:
adb install --bypass-low-target-sdk-block filename.apk
-
The main purpose of this is to
prevent malicious software
from using old versions to bypass new security constraints,
for example: using targetSDK 22 to bypass the permission request restrictions ofAndroid 6.0 Marshmallow (API 23)
.
-
- The data safety information displayed on Google Play has been adjusted: it mainly displays based on the provided information,
it will not have a compilation impact on the app, mainly the information displayed on the page after listing,
you can refer to this URL to see if your app or product needs adjustments: click here - System notifications: Now, if an app shares location with a third-party library, users will be notified which apps have shared location with third parties within 30 days.
- You can use the media storage query to list the OWNER_PACKAGE_NAME field of applications that store specific media files.
Starting from Android 14, unless at least one of the following conditions is met, the system will mask this value:- The application storing the media file has a package name that is always browsable by other applications.
- The application querying the media storage requests the QUERY_ALL_PACKAGES permission.
- For applications targeting Android 14 (API level 34) or higher,
Android 14 will enforce theBLUETOOTH_CONNECT
permission when calling the BluetoothAdapter#getProfileConnectionState() method.
- If used, add this permission in
AndroidManifest.xml
- Check if the permission has been granted before use
- If used, add this permission in
-
If JobScheduler uses
setRequiredNetworkType
orsetRequiredNetwork
,
it now needs to declare theACCESS_NETWORK_STATE
permission,
otherwise, it will cause a SecurityException on Android 14 or higher. - Restrictions on
implicit
intents and pending intents: Android 14 adds restrictions on the use of implicit intents.- Implicit intents can now only be used on components that are already exported, you can set
exported = true
, or useexplicit intent
e.g. Useexplicit intent
orexported = true
<activity android:name=".MyActivity" android:exported="true"> <intent-filter> <action android:name="com.example.action.APP_ACTION" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity>
// This makes the intent explicit. val explicitIntent = Intent("com.example.action.APP_ACTION") explicitIntent.apply { package = context.packageName } context.startActivity(explicitIntent)
- When using
mutable
pending intents without specifying the package name, exceptions may be thrown.
Example of an exception using a pending intent withFLAG_MUTABLE
:
Intent intent = new Intent(Intent.ACTION_VIEW); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
Add the package name to avoid exceptions:
Intent intent = new Intent(Intent.ACTION_VIEW); intent.setPackage("com.example.myapp"); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
- Implicit intents can now only be used on components that are already exported, you can set
- Now you need to add the flag
RECEIVER_EXPORTED
orRECEIVER_NOT_EXPORTED
to the Broadcast receiver registered in the context,
to protect the application from security vulnerabilities.
val receiverFlags = if (listenToBroadcastsFromOtherApps) {
ContextCompat.RECEIVER_EXPORTED
} else {
ContextCompat.RECEIVER_NOT_EXPORTED
}
ContextCompat.registerReceiver(context, br, filter, receiverFlags)
- Files currently using dynamic code loading must be set to
read only
Otherwise, an exception will occur
This is due to the official consideration of its security- Official recommendations for existing loading files can be found here
- If you must dynamically load code, use the following method
Set the dynamic file (such as DEX, JAR, or APK file) to read-only immediately before opening and writing any content to it:
val jar = File("DYNAMICALLY_LOADED_FILE.jar") val os = FileOutputStream(jar) os.use { // Set the file to read-only first to prevent race conditions jar.setReadOnly() // Then write the actual file content } val cl = PathClassLoader(jar, parentClassLoader)
- To prevent zip traversal vulnerabilities
Now, when using ZipInputStream.getNextEntry(), paths containing..
or/
will throw aZipException
- If you want to bypass this validation step, you can directly call
dalvik.system.ZipPathValidator.clearCallback().
- If you want to bypass this validation step, you can directly call
USE_FULL_SCREEN_INTENT
: Used to display full-screen notifications on Android 11 and above
However, on Android 14, it can only be used oncall
andalarm
type apps
After2024/05/31
, Google Play will revoke apps that use this permission for other purposes.
- Currently, you can use the API
NotificationManager#canUseFullScreenIntent()
to check if full-screen notifications can be used. - If you do not have permission, you can request the
ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT
permission.
- Currently, you can use the API
- Now, when using a foregroundService, you must declare the
android:foregroundServiceType
attribute- This attribute was introduced in Android 10, and now in Android 14, it must be declared, otherwise, an error will occur.
- The official documentation provides
13
types for developers to declare, refer to documentation - The official recommendation is that
if it is unrelated to the above types
, you can migrate the logic toWorkManager
oruser-initiated data transfer operations
. -
If you declare using the above types, each type requires different permissions to be declared, for example: for the commonly used
mediaProjection
in our project, the following steps need to be completed:
a. Declareandroid:foregroundServiceType mediaProjection
in AndroidManifest.xml
b. In addition to the original ForegroundService permission, declare theFOREGROUND_SERVICE_MEDIA_PROJECTION
permission
c. Before executing startForeground, use thecreateScreenCaptureIntent()
method to confirm the permission with the user, then start the foreground service.
d. When calling startForeground, includeFOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
startForeground( AIRSOS_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION)
- Other types follow similar concepts, but the permissions to be declared or the ServiceInfo to be added will vary depending on the type, please refer to the aforementioned documentation.
-
Besides the above, currently using
AirDroid Remote Support
to adjust and see, there are several directions to fix this issue a. Use thedataSync
type to bypass this issue.
dataSync
does not require additional permissions at runtime, so the original process is less likely to be affected,
->Risk
: If all are declared asdataSync
, it can be modified with less effort in the short term and is less likely to affect the process, but it will be unrelated to the official documentation types.
(The official documentation explains the responsibilities suitable for different types)
It was found thatdataSync
is already noted by the official to bedeprecated
in the future, as shown in the image
b. For those requiring additional permissions at runtime, obtain the permissions from the user before executing startForeground in the original process
For example:mediaProjection
requires calling createScreenCaptureIntent() to obtain the mediaProjection permission before startForeground.
Here is a demonstration
Update your build sdk version
Add the following permissions
Add the foreground service type according to your needs
```->
Actual Issues Encountered
: In theService
code I wrote earlier,
I had already addedforegroundServiceType
(previously not mandatory),
and thisservice
has methods that operate actions within the class,
such as startForegroundService.
Therefore, according to the official documentation, in Android 14 and above,
you need to callcreateScreenCaptureIntent()
to obtain permission.
Although adding the above sample can prevent crashes,
the expected flow will differ from theoriginal plan
,
requiring additional time to split logic, test, and modify the overall code, etc.
Since each time you need to obtain the above permission for the foreground service,
it means that products or solutions that previously used foreground services will need adjustments.- Note: 【Runtime】, here refers to the execution of
startForeground
.
In practice, if youdo not follow the documentation
to obtain the corresponding permissionbefore execution
, it will cause anexception
andcrash
. - Example of a crash is as follows:
c. Later, cross-tested several scenarios: -
Declaring multiple foregroundServiceTypes in the
Manifest
using|
.
And in the source code, providingdifferent types
offoregroundServiceType
based ondifferent version checks
.
-
Tried not adding
foregroundServiceType
in the source code,
which wouldcrash
and show noFOREGROUND_SERVICE_MICROPHONE
permission
(in the case of declaringmultiple types
).
So, I went to test other Services,
simply addingdataSync
in the Manifest,
even if noforegroundServiceType
is entered in the source code, it will notcrash
.
But mixingmicrophone
with an emptyforegroundServiceType
willcrash
.
(Left willcrash
, right runs normally, click to enlarge)
- Note: 【Runtime】, here refers to the execution of