summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt154
-rw-r--r--packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt42
8 files changed, 345 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index d30f33f5ba2c..a67dcdb70b67 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -515,7 +515,7 @@ public class AssistManager {
}
@Nullable
- private ComponentName getAssistInfo() {
+ public ComponentName getAssistInfo() {
return getAssistInfoForUser(mSelectedUserInteractor.getSelectedUserId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 9c8a481fcb76..501fee629e10 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -20,15 +20,15 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.RingerModeTrackerImpl;
import com.android.systemui.util.animation.data.repository.AnimationStatusRepository;
import com.android.systemui.util.animation.data.repository.AnimationStatusRepositoryImpl;
+import com.android.systemui.util.icons.AppCategoryIconProvider;
+import com.android.systemui.util.icons.AppCategoryIconProviderImpl;
import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds;
import dagger.Module;
/** Dagger Module for code in the util package. */
-@Module(includes = {
- UtilWrapperModule.class
- })
+@Module(includes = {UtilWrapperModule.class})
public interface UtilModule {
/** */
@Binds
@@ -37,4 +37,8 @@ public interface UtilModule {
@Binds
AnimationStatusRepository provideAnimationStatus(
AnimationStatusRepositoryImpl ringerModeTrackerImpl);
+
+ /** */
+ @Binds
+ AppCategoryIconProvider appCategoryIconProvider(AppCategoryIconProviderImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt b/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt
new file mode 100644
index 000000000000..6e3f8f16b623
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/icons/AppCategoryIconProvider.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.content.Intent
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.os.RemoteException
+import android.util.Log
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.system.PackageManagerWrapper
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.withContext
+
+interface AppCategoryIconProvider {
+ /** Returns the [Icon] of the default assistant app, if it exists. */
+ suspend fun assistantAppIcon(): Icon?
+
+ /**
+ * Returns the [Icon] of the default app of [category], if it exists. Category can be for
+ * example [Intent.CATEGORY_APP_EMAIL] or [Intent.CATEGORY_APP_CALCULATOR].
+ */
+ suspend fun categoryAppIcon(category: String): Icon?
+}
+
+class AppCategoryIconProviderImpl
+@Inject
+constructor(
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val assistManager: AssistManager,
+ private val packageManager: PackageManager,
+ private val packageManagerWrapper: PackageManagerWrapper,
+) : AppCategoryIconProvider {
+
+ override suspend fun assistantAppIcon(): Icon? {
+ val assistInfo = assistManager.assistInfo ?: return null
+ return getPackageIcon(assistInfo.packageName)
+ }
+
+ override suspend fun categoryAppIcon(category: String): Icon? {
+ val intent = Intent(Intent.ACTION_MAIN).apply { addCategory(category) }
+ val packageInfo = getPackageInfo(intent) ?: return null
+ return getPackageIcon(packageInfo.packageName)
+ }
+
+ private suspend fun getPackageInfo(intent: Intent): PackageInfo? =
+ withContext(backgroundDispatcher) {
+ val packageName =
+ packageManagerWrapper
+ .resolveActivity(/* intent= */ intent, /* flags= */ 0)
+ ?.activityInfo
+ ?.packageName ?: return@withContext null
+ return@withContext getPackageInfo(packageName)
+ }
+
+ private suspend fun getPackageIcon(packageName: String): Icon? {
+ val appInfo = getPackageInfo(packageName)?.applicationInfo ?: return null
+ return if (appInfo.icon != 0) {
+ Icon.createWithResource(appInfo.packageName, appInfo.icon)
+ } else {
+ null
+ }
+ }
+
+ private suspend fun getPackageInfo(packageName: String): PackageInfo? =
+ withContext(backgroundDispatcher) {
+ try {
+ return@withContext packageManager.getPackageInfo(packageName, /* flags= */ 0)
+ } catch (e: RemoteException) {
+ Log.e(TAG, "Failed to retrieve package info for $packageName")
+ return@withContext null
+ }
+ }
+
+ companion object {
+ private const val TAG = "DefaultAppsIconProvider"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
new file mode 100644
index 000000000000..ef41b6ee69cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/icons/AppCategoryIconProviderTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.content.ComponentName
+import android.content.Intent
+import android.content.Intent.CATEGORY_APP_BROWSER
+import android.content.Intent.CATEGORY_APP_CONTACTS
+import android.content.Intent.CATEGORY_APP_EMAIL
+import android.content.mockPackageManager
+import android.content.mockPackageManagerWrapper
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageInfo
+import android.content.pm.ResolveInfo
+import android.graphics.drawable.Icon
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.mockAssistManager
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppCategoryIconProviderTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val packageManagerWrapper = kosmos.mockPackageManagerWrapper
+ private val packageManager = kosmos.mockPackageManager
+ private val assistManager = kosmos.mockAssistManager
+ private val provider = kosmos.appCategoryIconProvider
+
+ @Before
+ fun setUp() {
+ whenever(packageManagerWrapper.resolveActivity(any<Intent>(), any<Int>())).thenAnswer {
+ invocation ->
+ val category = (invocation.arguments[0] as Intent).categories.first()
+ val categoryAppIcon =
+ categoryAppIcons.firstOrNull { it.category == category } ?: return@thenAnswer null
+ val activityInfo = ActivityInfo().also { it.packageName = categoryAppIcon.packageName }
+ return@thenAnswer ResolveInfo().also { it.activityInfo = activityInfo }
+ }
+ whenever(packageManager.getPackageInfo(any<String>(), any<Int>())).thenAnswer { invocation
+ ->
+ val packageName = invocation.arguments[0] as String
+ val categoryAppIcon =
+ categoryAppIcons.firstOrNull { it.packageName == packageName }
+ ?: return@thenAnswer null
+ val applicationInfo =
+ ApplicationInfo().also {
+ it.packageName = packageName
+ it.icon = categoryAppIcon.iconResId
+ }
+ return@thenAnswer PackageInfo().also {
+ it.packageName = packageName
+ it.applicationInfo = applicationInfo
+ }
+ }
+ }
+
+ @Test
+ fun assistantAppIcon_defaultAssistantSet_returnsIcon() =
+ testScope.runTest {
+ whenever(assistManager.assistInfo)
+ .thenReturn(ComponentName(ASSISTANT_PACKAGE, ASSISTANT_CLASS))
+
+ val icon = provider.assistantAppIcon() as Icon
+
+ assertThat(icon.resPackage).isEqualTo(ASSISTANT_PACKAGE)
+ assertThat(icon.resId).isEqualTo(ASSISTANT_ICON_RES_ID)
+ }
+
+ @Test
+ fun assistantAppIcon_defaultAssistantNotSet_returnsNull() =
+ testScope.runTest {
+ whenever(assistManager.assistInfo).thenReturn(null)
+
+ assertThat(provider.assistantAppIcon()).isNull()
+ }
+
+ @Test
+ fun categoryAppIcon_returnsIconOfKnownBrowserApp() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_BROWSER) as Icon
+
+ assertThat(icon.resPackage).isEqualTo(BROWSER_PACKAGE)
+ assertThat(icon.resId).isEqualTo(BROWSER_ICON_RES_ID)
+ }
+ }
+
+ @Test
+ fun categoryAppIcon_returnsIconOfKnownContactsApp() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_CONTACTS) as Icon
+
+ assertThat(icon.resPackage).isEqualTo(CONTACTS_PACKAGE)
+ assertThat(icon.resId).isEqualTo(CONTACTS_ICON_RES_ID)
+ }
+ }
+
+ @Test
+ fun categoryAppIcon_noDefaultAppForCategoryEmail_returnsNull() {
+ testScope.runTest {
+ val icon = provider.categoryAppIcon(CATEGORY_APP_EMAIL)
+
+ assertThat(icon).isNull()
+ }
+ }
+
+ private companion object {
+ private const val ASSISTANT_PACKAGE = "the.assistant.app"
+ private const val ASSISTANT_CLASS = "the.assistant.app.class"
+ private const val ASSISTANT_ICON_RES_ID = 123
+
+ private const val BROWSER_PACKAGE = "com.test.browser"
+ private const val BROWSER_ICON_RES_ID = 1
+
+ private const val CONTACTS_PACKAGE = "app.test.contacts"
+ private const val CONTACTS_ICON_RES_ID = 234
+
+ private val categoryAppIcons =
+ listOf(
+ App(ROLE_ASSISTANT, ASSISTANT_PACKAGE, ASSISTANT_ICON_RES_ID),
+ App(CATEGORY_APP_BROWSER, BROWSER_PACKAGE, BROWSER_ICON_RES_ID),
+ App(CATEGORY_APP_CONTACTS, CONTACTS_PACKAGE, CONTACTS_ICON_RES_ID),
+ )
+ }
+
+ private class App(val category: String, val packageName: String, val iconResId: Int)
+}
diff --git a/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
index 8901314d8e76..9d7d916a222e 100644
--- a/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/content/PackageManagerKosmos.kt
@@ -17,6 +17,13 @@ package android.content
import android.content.pm.PackageManager
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.shared.system.PackageManagerWrapper
import com.android.systemui.util.mockito.mock
-val Kosmos.packageManager by Kosmos.Fixture { mock<PackageManager>() }
+val Kosmos.mockPackageManager by Kosmos.Fixture { mock<PackageManager>() }
+
+var Kosmos.packageManager by Kosmos.Fixture { mockPackageManager }
+
+val Kosmos.mockPackageManagerWrapper by Kosmos.Fixture { mock<PackageManagerWrapper>() }
+
+var Kosmos.packageManagerWrapper by Kosmos.Fixture { mockPackageManagerWrapper }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
index b7d6f3a5f91f..22eb646ff48a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/assist/AssistManagerKosmos.kt
@@ -19,4 +19,6 @@ package com.android.systemui.assist
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.util.mockito.mock
-var Kosmos.assistManager by Kosmos.Fixture { mock<AssistManager>() }
+val Kosmos.mockAssistManager by Kosmos.Fixture { mock<AssistManager>() }
+
+var Kosmos.assistManager by Kosmos.Fixture { mockAssistManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt
new file mode 100644
index 000000000000..798718560c08
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/AppCategoryIconProviderKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.content.mockPackageManager
+import android.content.mockPackageManagerWrapper
+import com.android.systemui.assist.mockAssistManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+
+var Kosmos.fakeAppCategoryIconProvider by Kosmos.Fixture { FakeAppCategoryIconProvider() }
+
+var Kosmos.appCategoryIconProvider: AppCategoryIconProvider by
+ Kosmos.Fixture {
+ AppCategoryIconProviderImpl(
+ testDispatcher,
+ mockAssistManager,
+ mockPackageManager,
+ mockPackageManagerWrapper
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt
new file mode 100644
index 000000000000..3e7bf21112ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/icons/FakeAppCategoryIconProvider.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2024 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.util.icons
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.graphics.drawable.Icon
+
+class FakeAppCategoryIconProvider : AppCategoryIconProvider {
+
+ private val installedApps = mutableMapOf<String, App>()
+
+ fun installCategoryApp(category: String, packageName: String, iconResId: Int) {
+ installedApps[category] = App(packageName, iconResId)
+ }
+
+ fun installAssistantApp(packageName: String, iconResId: Int) {
+ installedApps[ROLE_ASSISTANT] = App(packageName, iconResId)
+ }
+
+ override suspend fun assistantAppIcon() = categoryAppIcon(ROLE_ASSISTANT)
+
+ override suspend fun categoryAppIcon(category: String): Icon? {
+ val app = installedApps[category] ?: return null
+ return Icon.createWithResource(app.packageName, app.iconResId)
+ }
+
+ private class App(val packageName: String, val iconResId: Int)
+}