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

Commit df18b672 authored by Nick Chameyev's avatar Nick Chameyev
Browse files

[Partial screensharing] Fix crash when having a recents app available only in work profile

AppIconLoader in the recent app selector crashed when getting
information about an app that is available only in work profile.
This is because package manager gets activity information only
for the current user.
Replaced with using private package manager wrapper that allows
to get activity info for any user.

Bug: 256835220
Test: open app selector with work profile apps in the recents
Test: com.android.systemui.mediaprojection.appselector.data.IconLoaderLibAppIconLoaderTest
Change-Id: Ib608176a80e4a8f02452de3bc72f01b184ee4680
parent 71bf4aaf
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.systemui.mediaprojection.appselector
import android.app.Activity
import android.content.ComponentName
import android.content.Context
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.MediaProjectionAppSelectorActivity
import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader
@@ -91,6 +92,11 @@ interface MediaProjectionAppSelectorModule {
            activity: MediaProjectionAppSelectorActivity
        ): ConfigurationController = ConfigurationControllerImpl(activity)

        @Provides
        fun bindIconFactory(
            context: Context
        ): IconFactory = IconFactory.obtain(context)

        @Provides
        @MediaProjectionAppSelector
        @MediaProjectionAppSelectorScope
+10 −5
Original line number Diff line number Diff line
@@ -19,13 +19,14 @@ package com.android.systemui.mediaprojection.appselector.data
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ComponentInfoFlags
import android.graphics.drawable.Drawable
import android.os.UserHandle
import com.android.launcher3.icons.BaseIconFactory.IconOptions
import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.system.PackageManagerWrapper
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext

@@ -38,14 +39,18 @@ class IconLoaderLibAppIconLoader
constructor(
    @Background private val backgroundDispatcher: CoroutineDispatcher,
    private val context: Context,
    private val packageManager: PackageManager
    // Use wrapper to access hidden API that allows to get ActivityInfo for any user id
    private val packageManagerWrapper: PackageManagerWrapper,
    private val packageManager: PackageManager,
    private val iconFactoryProvider: Provider<IconFactory>
) : AppIconLoader {

    override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
        withContext(backgroundDispatcher) {
            IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
                val activityInfo = packageManager
                        .getActivityInfo(component, ComponentInfoFlags.of(0))
            iconFactoryProvider.get().use<IconFactory, Drawable?> { iconFactory ->
                val activityInfo =
                    packageManagerWrapper.getActivityInfo(component, userId)
                        ?: return@withContext null
                val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
                val userHandler = UserHandle.of(userId)
                val options = IconOptions().apply { setUser(userHandler) }
+83 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.mediaprojection.appselector.data

import android.content.ComponentName
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import androidx.test.filters.SmallTest
import com.android.launcher3.icons.BitmapInfo
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.IconFactory
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.system.PackageManagerWrapper
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4

@SmallTest
@RunWith(JUnit4::class)
class IconLoaderLibAppIconLoaderTest : SysuiTestCase() {

    private val iconFactory: IconFactory = mock()
    private val packageManagerWrapper: PackageManagerWrapper = mock()
    private val packageManager: PackageManager = mock()
    private val dispatcher = Dispatchers.Unconfined

    private val appIconLoader =
        IconLoaderLibAppIconLoader(
            backgroundDispatcher = dispatcher,
            context = context,
            packageManagerWrapper = packageManagerWrapper,
            packageManager = packageManager,
            iconFactoryProvider = { iconFactory }
        )

    @Test
    fun loadIcon_loadsIconUsingTheSameUserId() {
        val icon = createIcon()
        val component = ComponentName("com.test", "TestApplication")
        givenIcon(component, userId = 123, icon = icon)

        val loadedIcon = runBlocking { appIconLoader.loadIcon(userId = 123, component = component) }

        assertThat(loadedIcon).isEqualTo(icon)
    }

    private fun givenIcon(component: ComponentName, userId: Int, icon: FastBitmapDrawable) {
        val activityInfo = mock<ActivityInfo>()
        whenever(packageManagerWrapper.getActivityInfo(component, userId)).thenReturn(activityInfo)
        val rawIcon = mock<Drawable>()
        whenever(activityInfo.loadIcon(packageManager)).thenReturn(rawIcon)

        val bitmapInfo = mock<BitmapInfo>()
        whenever(iconFactory.createBadgedIconBitmap(eq(rawIcon), any())).thenReturn(bitmapInfo)
        whenever(bitmapInfo.newIcon(context)).thenReturn(icon)
    }

    private fun createIcon(): FastBitmapDrawable =
        FastBitmapDrawable(Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888))
}