Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ce56dcc3 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

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
parent 71a5715e
Loading
Loading
Loading
Loading
+39 −20
Original line number Diff line number Diff line
@@ -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,9 +57,8 @@ class PictureInPictureListModel(private val context: Context) :
    private val packageManager = context.packageManager

    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
        userIdFlow.map(::getPictureInPicturePackages).combine(appListFlow) {
            pictureInPicturePackages,
            appList ->
        userIdFlow.map(::getPictureInPicturePackages)
            .combine(appListFlow) { pictureInPicturePackages, appList ->
                appList.map { app ->
                    createPictureInPictureRecord(
                        app = app,
@@ -67,15 +67,11 @@ class PictureInPictureListModel(private val context: Context) :
                }
            }

    override fun transformItem(app: ApplicationInfo): PictureInPictureRecord {
        return createPictureInPictureRecord(
    override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord(
        app = app,
        isSupport = app.installed &&
                packageManager
                    .getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId)
                    .supportsPictureInPicture(),
            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<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

+32 −0
Original line number Diff line number Diff line
@@ -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<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(
@@ -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<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)