From ce56dcc30bb5adb1e609f5f681ee49ce189b6d32 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 24 May 2023 19:41:20 +0800 Subject: [PATCH] Fix crash of PictureInPicture This follows change I3115cf1b99a305efef192a0dcf3e809eb7903d0a PackageManager.getPackageInfoAsUser() will throw exceptions when the package is too large which is a known issue to PackageManager but very low priority given resourcing constraints. As per the PackageManager team suggestion, catch the exception on the app side to alleviate the impact to the PictureInPicture & App info page. Fix: 283076353 Fix: 283354211 Test: Unit test Change-Id: Iad2bf9fbfca6ee7f604fec1c4afa1b9382f6ec7e --- .../spa/app/specialaccess/PictureInPicture.kt | 59 ++++++++++++------- .../app/specialaccess/PictureInPictureTest.kt | 32 ++++++++++ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt index 9fc358b5cf8..5ed361560d7 100644 --- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt +++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.PackageInfoFlags +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import com.android.settings.R @@ -56,26 +57,21 @@ class PictureInPictureListModel(private val context: Context) : private val packageManager = context.packageManager override fun transform(userIdFlow: Flow, appListFlow: Flow>) = - userIdFlow.map(::getPictureInPicturePackages).combine(appListFlow) { - pictureInPicturePackages, - appList -> - appList.map { app -> - createPictureInPictureRecord( - app = app, - isSupport = app.packageName in pictureInPicturePackages, - ) + userIdFlow.map(::getPictureInPicturePackages) + .combine(appListFlow) { pictureInPicturePackages, appList -> + appList.map { app -> + createPictureInPictureRecord( + app = app, + isSupport = app.packageName in pictureInPicturePackages, + ) + } } - } - override fun transformItem(app: ApplicationInfo): PictureInPictureRecord { - return createPictureInPictureRecord( - app = app, - isSupport = app.installed && - packageManager - .getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) - .supportsPictureInPicture(), - ) - } + override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord( + app = app, + isSupport = app.installed && + getPackageAndActivityInfo(app)?.supportsPictureInPicture() == true, + ) private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) = PictureInPictureRecord( @@ -103,13 +99,36 @@ class PictureInPictureListModel(private val context: Context) : } private fun getPictureInPicturePackages(userId: Int): Set = - packageManager - .getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId) + getPackageAndActivityInfoList(userId) .filter { it.supportsPictureInPicture() } .map { it.packageName } .toSet() + private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try { + packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) + } catch (e: Exception) { + // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause + // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture + // packages, we need to catch the exception to alleviate the impact before PackageManager + // fixing this issue or provide a better api. + Log.e(TAG, "Exception while getPackageInfoAsUser", e) + null + } + + private fun getPackageAndActivityInfoList(userId: Int): List = try { + packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId) + } catch (e: Exception) { + // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause + // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture + // packages, we need to catch the exception to alleviate the impact before PackageManager + // fixing this issue or provide a better api. + Log.e(TAG, "Exception while getInstalledPackagesAsUser", e) + emptyList() + } + companion object { + private const val TAG = "PictureInPictureListModel" + private fun PackageInfo.supportsPictureInPicture() = activities?.any(ActivityInfo::supportsPictureInPicture) ?: false diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt index f90d63947d0..fb0fb698045 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.PackageInfoFlags +import android.os.DeadSystemRuntimeException import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R @@ -100,6 +101,23 @@ class PictureInPictureTest { assertThat(record.isSupport).isTrue() } + @Test + fun transform_getInstalledPackagesAsUserThrowsException_treatAsNotSupported() = runTest { + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenThrow(DeadSystemRuntimeException()) + + val recordListFlow = listModel.transform( + userIdFlow = flowOf(USER_ID), + appListFlow = flowOf(listOf(PICTURE_IN_PICTURE_APP)), + ) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + val record = recordList[0] + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isFalse() + } + @Test fun transformItem() { whenever( @@ -114,6 +132,20 @@ class PictureInPictureTest { assertThat(record.isSupport).isTrue() } + @Test + fun transformItem_getPackageInfoAsUserThrowsException_treatAsNotSupported() { + whenever( + packageManager.getPackageInfoAsUser( + eq(PICTURE_IN_PICTURE_PACKAGE_NAME), any(), eq(USER_ID) + ) + ).thenThrow(DeadSystemRuntimeException()) + + val record = listModel.transformItem(PICTURE_IN_PICTURE_APP) + + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isFalse() + } + @Test fun filter_isSupport() = runTest { val record = createRecord(isSupport = true)