Android13 adaptation

some problems encountered

1. API onReachedMaxAppCacheSize of WebChromeClient is gone

'onReachedMaxAppCacheSize' overrides nothing

    // 扩充缓存的容量
        override fun onReachedMaxAppCacheSize(
            spaceNeeded: Long, totalUsedQuota: Long, quotaUpdater: QuotaUpdater
        ) {
            quotaUpdater.updateQuota(spaceNeeded * 2)
        }

2. The setAppCacheEnabled(true) of WebSettings is gone

WebView adjustment: discard setAppCacheEnabled and setForceDark methods;
(if targetSdk>=33 is set, this item must be adapted!)

change into

 mWebSettings.setCacheMode(WebSettings.LOAD_DEFAULT);

IntentFilter

In the previous version of the Android system, you only need to set android:exported to true to explicitly start the Activity and Service across applications, even if the action or type in the intent-filter does not match, it can also be started.

In order to avoid the above vulnerabilities, Android 13 has enhanced the matching filtering logic of intent-filter. In the case of the receiver's targetSdk == 33, if the intent-filter matches and hits, the intent will take effect regardless of the sender's targetSdk version.

Kind tips:

The following situations do not need to follow the matching filtering logic of intent-filter:

  • component is not declared

  • Intents in the same App

  • Intents issued by the system or Root process

BroadcastReceiver

Under the previous Android system, the BroadcastReceiver broadcast receiver dynamically registered by the application will receive the broadcast sent by any application (unless the receiver uses application signature permission protection), which will make the dynamically registered broadcast receiver a security risk.

Android13 requires that the broadcast receiver dynamically registered by the application must indicate in a prominent way whether other applications are allowed to access , that is, whether other applications can send broadcasts to it. Otherwise, the system will throw a security exception (SecurityException) during dynamic registration.

Currently, this enhancement does not take effect by default. Developers need to enable the DYNAMIC_RECEIVER_EXPLICIT_EXPORT_REQUIRED compatibility framework, and specify whether to accept broadcasts from other applications when registering broadcasts dynamically:

context.registerReceiver(receiver, intentFilter, RECEIVER_EXPORTED)
context.registerReceiver(receiver, intentFilter, RECEIVER_NOT_EXPORTED)

permissions

Segment media rights

Google has further refined local data access permissions on Android 13.

Subdivide READ_EXTERNAL_STORAGE into IAMGES, VIDEO, and AUDIO permissions, and targetSdk>=33 must be adapted.

Starting from Android 13, the system adds runtime permissions READ_MEDIA_IAMGES, READ_MEDIA_VIDEO, and READ_MEDIA_AUDIO to replace the original READ_EXTERNAL_STORAGE permission.

permissions Permission Description
READ_MEDIA_IAMGES Image permissions
READ_MEDIA_VIDEO video rights
READ_MEDIA_AUDIO audio permissions

When the app is upgraded to targetSdk>=33:

Apps that have been granted the READ_EXTERNAL_STORAGE permission: the system will automatically grant the corresponding fine-grained permissions.
READ_EXTERNAL_STORAGE permission is still requested without authorization: the pro-test system will not grant any permission.

Therefore, for API 32, which is Android 12 and below, we still declare the READ_EXTERNAL_STORAGE permission. Starting from Android 13, we will use READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_AUDIO instead.

Static broadcast registration

Starting from Android 13, when registering a static broadcast, you need to set the visibility to other apps:

If visible to other applications, set during broadcast registration: Context.RECEIVER_EXPORTED
If only used within the application, set during broadcast registration: Context.RECEIVER_NOT_EXPORTED
 

// This broadcast receiver should be able to receive broadcasts from other apps.
// This option causes the same behavior as setting the broadcast receiver's
// "exported" attribute to true in your app's manifest.
context.registerReceiver(sharedBroadcastReceiver, intentFilter,
    RECEIVER_EXPORTED)

// For app safety reasons, this private broadcast receiver should **NOT**
// be able to receive broadcasts from other apps.
context.registerReceiver(privateBroadcastReceiver, intentFilter,
    RECEIVER_NOT_EXPORTED)

 notification permissions

Android 13 introduces a new runtime notification permission: POST_NOTIFICATIONS.
POST_NOTIFICATIONS permission level is defined as dangerous developers need to apply dynamically when using this permission.

For Android 13, when displaying the Android notification bar, on the one hand, you need to declare android.permission.POST_NOTIFICATION in the AndroidManifest, and on the other hand, you need to dynamically apply for the notification bar permission in the code.

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="xxx.xxxx.xxxx">
    
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

EasyPermissions.requestPermissions(this, this.getString(R.string.ask_again),
		PermissionActivity.RC_PERMISSION_NOTIFICATIONS, Manifest.permission.POST_NOTIFICATIONS);

Determine whether the application has the ability to send notifications for users to see (all system versions can work normally):

val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (notificationManager.areNotificationsEnabled()) {
    // Permission granted
} else {
    // Permission not granted
}

Judging permission to send notifications on Android 13:

if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
    == PackageManager.PERMISSION_GRANTED) {
    // Permission granted
} else {
    // Permission not granted
}

Note: Once the authorization is rejected by the user, the pop-up window for permission application will not appear next time. 

Guide users to the settings interface to enable notification permissions. code show as below:

private void goSetting() {
    final ApplicationInfo applicationInfo = getApplicationInfo();

    try {
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction("android.settings.APP_NOTIFICATION_SETTINGS");
        intent.putExtra("app_package", applicationInfo.packageName);
        intent.putExtra("android.provider.extra.APP_PACKAGE", applicationInfo.packageName);
        intent.putExtra("app_uid", applicationInfo.uid);
        startActivity(intent);
    } catch (Throwable t) {
        t.printStackTrace();
        Intent intent = new Intent();
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        intent.setData(Uri.fromParts("package", applicationInfo.packageName, null));
        startActivity(intent);
    }
}

class TestNavigationActivity : AppCompatActivity() {

    companion object {
        const val TAG = "TestNavigationActivityTAG"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_navigation)

        checkAndShow()
    }

    private fun checkAndShow() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val manager = getSystemService(NotificationManager::class.java)
            val flag = manager.areNotificationsEnabled()
            if (!flag) {
                requestPermission(PermissionWrapper(POST_NOTIFICATIONS, 1001))
            }
        }
    }


    private fun checkPermission(permission: String): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true
        }
        return (ContextCompat.checkSelfPermission(
            this, permission
        ) == PackageManager.PERMISSION_GRANTED)
    }

    private fun requestPermission(permission: PermissionWrapper) {
        if (checkPermission(permission.name)) {
            Toast.makeText(this, "已授予权限:" + permission.name, Toast.LENGTH_LONG).show()
            Log.v(TAG, "已授予权限:" + permission.name)
        } else {
            Toast.makeText(this, "未授予权限:" + permission.name + ",尝试获取权限...", Toast.LENGTH_LONG)
                .show()
            Log.v(TAG, "未授予权限:" + permission.name + ",尝试获取权限...")
            ActivityCompat.requestPermissions(this, arrayOf(permission.name), permission.code)
        }
    }

    data class PermissionWrapper(val name: String, val code: Int)


    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String>, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        val result = StringBuilder().apply {
            append("请求码:$requestCode")
            append("-权限:")
            permissions.forEach {
                append(it)
            }
            append("-授权结果:")
            grantResults.forEach {
                append(it)
            }
        }.toString()
        Toast.makeText(this, result, Toast.LENGTH_LONG).show()
        Log.v(TAG, result)
    }
}

V/TestNavigationActivityTAG: 未授予权限:android.permission.POST_NOTIFICATIONS,尝试获取权限...
 V/TestNavigationActivityTAG: 请求码:1001-权限:android.permission.POST_NOTIFICATIONS-授权结果:0

The notification can only be sent after obtaining the permission. The code is as follows:

class TestNavigationActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "TestNavigationActivityTAG"

        private const val CHANNEL_ID = "myApplication_channel_1"

        private const val CHANNEL_NAME = "myApplication_channel_name"

        private const val NOTIFICATION_ID_MSG = 100
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test_navigation)

        checkAndShow()
    }

    private fun checkAndShow() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val manager = getSystemService(NotificationManager::class.java)
            val flag = manager.areNotificationsEnabled()
            if (!flag) {
                requestPermission(PermissionWrapper(POST_NOTIFICATIONS, 1001))
            } else {
                showNotification()
            }
        }
    }


    private fun checkPermission(permission: String): Boolean {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return true
        }
        return (ContextCompat.checkSelfPermission(
            this, permission
        ) == PackageManager.PERMISSION_GRANTED)
    }

    private fun requestPermission(permission: PermissionWrapper) {
        if (checkPermission(permission.name)) {
            Toast.makeText(this, "已授予权限:" + permission.name, Toast.LENGTH_LONG).show()
            Log.v(TAG, "已授予权限:" + permission.name)
        } else {
            Toast.makeText(this, "未授予权限:" + permission.name + ",尝试获取权限...", Toast.LENGTH_LONG)
                .show()
            Log.v(TAG, "未授予权限:" + permission.name + ",尝试获取权限...")
            ActivityCompat.requestPermissions(this, arrayOf(permission.name), permission.code)
        }
    }

    data class PermissionWrapper(val name: String, val code: Int)


    override fun onRequestPermissionsResult(
        requestCode: Int, permissions: Array<out String>, grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        val result = StringBuilder().apply {
            append("请求码:$requestCode")
            append("-权限:")
            permissions.forEach {
                append(it)
            }
            append("-授权结果:")
            grantResults.forEach {
                append(it)
            }
        }.toString()
        Toast.makeText(this, result, Toast.LENGTH_LONG).show()
        Log.v(TAG, result)
    }


    private fun showNotification() {
        val intent = Intent(this, NotificationResultMainActivity::class.java)
        val manager = getSystemService(NotificationManager::class.java)
        val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel =
                NotificationChannel(CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH)
            channel.setShowBadge(true)
            manager.createNotificationChannel(channel)
        }
        val builder = NotificationCompat.Builder(this@TestNavigationActivity, CHANNEL_ID)
            .setContentTitle("重要通知")
            .setContentText("重要通知")
            .setSmallIcon(R.mipmap.ic_launcher)
            .setLargeIcon(BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher))
            .setAutoCancel(true)
            .setNumber(999)
            .addAction(R.mipmap.ic_launcher, "去看看", pendingIntent)
            .setCategory(NotificationCompat.CATEGORY_MESSAGE)
            .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
        manager.notify(NOTIFICATION_ID_MSG, builder.build())
    }
}

 

Guess you like

Origin blog.csdn.net/zhangying1994/article/details/128408176