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)