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
diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt
index 9fc358b..5ed3615 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.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 @@
private val packageManager = context.packageManager
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
- 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 @@
}
private fun getPictureInPicturePackages(userId: Int): Set<String> =
- 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<PackageInfo> = 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 f90d639..fb0fb69 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.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
@@ -101,6 +102,23 @@
}
@Test
+ fun transform_getInstalledPackagesAsUserThrowsException_treatAsNotSupported() = runTest {
+ whenever(packageManager.getInstalledPackagesAsUser(any<PackageInfoFlags>(), 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(
packageManager.getPackageInfoAsUser(
@@ -115,6 +133,20 @@
}
@Test
+ fun transformItem_getPackageInfoAsUserThrowsException_treatAsNotSupported() {
+ whenever(
+ packageManager.getPackageInfoAsUser(
+ eq(PICTURE_IN_PICTURE_PACKAGE_NAME), any<PackageInfoFlags>(), 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)