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
This commit is contained in:
Chaohui Wang
2023-05-24 19:41:20 +08:00
parent 71a5715e3e
commit ce56dcc30b
2 changed files with 71 additions and 20 deletions

View File

@@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.GET_ACTIVITIES
import android.content.pm.PackageManager.PackageInfoFlags import android.content.pm.PackageManager.PackageInfoFlags
import android.util.Log
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import com.android.settings.R import com.android.settings.R
@@ -56,26 +57,21 @@ class PictureInPictureListModel(private val context: Context) :
private val packageManager = context.packageManager private val packageManager = context.packageManager
override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) = override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
userIdFlow.map(::getPictureInPicturePackages).combine(appListFlow) { userIdFlow.map(::getPictureInPicturePackages)
pictureInPicturePackages, .combine(appListFlow) { pictureInPicturePackages, appList ->
appList -> appList.map { app ->
appList.map { app -> createPictureInPictureRecord(
createPictureInPictureRecord( app = app,
app = app, isSupport = app.packageName in pictureInPicturePackages,
isSupport = app.packageName in pictureInPicturePackages, )
) }
} }
}
override fun transformItem(app: ApplicationInfo): PictureInPictureRecord { override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord(
return createPictureInPictureRecord( app = app,
app = app, isSupport = app.installed &&
isSupport = app.installed && getPackageAndActivityInfo(app)?.supportsPictureInPicture() == true,
packageManager )
.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
.supportsPictureInPicture(),
)
}
private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) = private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) =
PictureInPictureRecord( PictureInPictureRecord(
@@ -103,13 +99,36 @@ class PictureInPictureListModel(private val context: Context) :
} }
private fun getPictureInPicturePackages(userId: Int): Set<String> = private fun getPictureInPicturePackages(userId: Int): Set<String> =
packageManager getPackageAndActivityInfoList(userId)
.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId)
.filter { it.supportsPictureInPicture() } .filter { it.supportsPictureInPicture() }
.map { it.packageName } .map { it.packageName }
.toSet() .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 { companion object {
private const val TAG = "PictureInPictureListModel"
private fun PackageInfo.supportsPictureInPicture() = private fun PackageInfo.supportsPictureInPicture() =
activities?.any(ActivityInfo::supportsPictureInPicture) ?: false activities?.any(ActivityInfo::supportsPictureInPicture) ?: false

View File

@@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags import android.content.pm.PackageManager.PackageInfoFlags
import android.os.DeadSystemRuntimeException
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settings.R import com.android.settings.R
@@ -100,6 +101,23 @@ class PictureInPictureTest {
assertThat(record.isSupport).isTrue() assertThat(record.isSupport).isTrue()
} }
@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 @Test
fun transformItem() { fun transformItem() {
whenever( whenever(
@@ -114,6 +132,20 @@ class PictureInPictureTest {
assertThat(record.isSupport).isTrue() assertThat(record.isSupport).isTrue()
} }
@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 @Test
fun filter_isSupport() = runTest { fun filter_isSupport() = runTest {
val record = createRecord(isSupport = true) val record = createRecord(isSupport = true)