Merge "Add work profile content description for app" into udc-dev
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
index 18b20733..1a7d896 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppRepository.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import android.content.pm.ApplicationInfo
 import android.graphics.drawable.Drawable
-import android.os.UserManager
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.produceState
@@ -28,6 +27,7 @@
 import com.android.settingslib.Utils
 import com.android.settingslib.spa.framework.compose.rememberContext
 import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
 
@@ -42,23 +42,25 @@
         val context = LocalContext.current
         return produceState(initialValue = stringResource(R.string.summary_placeholder), app) {
             withContext(Dispatchers.IO) {
-                if (isClonedAppPage || isCloneApp(context, app)) {
-                    value = context.getString(R.string.cloned_app_info_label, loadLabel(app))
+                value = if (isClonedAppPage || isCloneApp(context, app)) {
+                    context.getString(R.string.cloned_app_info_label, loadLabel(app))
                 } else {
-                    value = loadLabel(app)
+                    loadLabel(app)
                 }
             }
         }
     }
 
     private fun isCloneApp(context: Context, app: ApplicationInfo): Boolean {
-        val userManager = context.getSystemService(UserManager::class.java)!!
-        val userInfo = userManager.getUserInfo(app.userId)
+        val userInfo = context.userManager.getUserInfo(app.userId)
         return userInfo != null && userInfo.isCloneProfile
     }
 
     @Composable
     fun produceIcon(app: ApplicationInfo): State<Drawable?>
+
+    @Composable
+    fun produceIconContentDescription(app: ApplicationInfo): State<String?>
 }
 
 internal class AppRepositoryImpl(private val context: Context) : AppRepository {
@@ -69,8 +71,22 @@
     @Composable
     override fun produceIcon(app: ApplicationInfo) =
         produceState<Drawable?>(initialValue = null, app) {
-            withContext(Dispatchers.Default) {
+            withContext(Dispatchers.IO) {
                 value = Utils.getBadgedIcon(context, app)
             }
         }
+
+    @Composable
+    override fun produceIconContentDescription(app: ApplicationInfo) =
+        produceState<String?>(initialValue = null, app) {
+            withContext(Dispatchers.IO) {
+                value = when {
+                    context.userManager.isManagedProfile(app.userId) -> {
+                        context.getString(R.string.category_work)
+                    }
+
+                    else -> null
+                }
+            }
+        }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
index 602df54..e3ea2e7 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.framework.compose.rememberDrawablePainter
@@ -50,7 +51,8 @@
                 .padding(
                     horizontal = SettingsDimension.itemPaddingStart,
                     vertical = SettingsDimension.itemPaddingVertical,
-                ),
+                )
+                .semantics(mergeDescendants = true) {},
             horizontalAlignment = Alignment.CenterHorizontally,
         ) {
             val app = packageInfo.applicationInfo
@@ -93,8 +95,8 @@
     val appRepository = rememberAppRepository()
     Image(
         painter = rememberDrawablePainter(appRepository.produceIcon(app).value),
-        contentDescription = null,
-        modifier = Modifier.size(size)
+        contentDescription = appRepository.produceIconContentDescription(app).value,
+        modifier = Modifier.size(size),
     )
 }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
index fc40aed..4f0cdde 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt
@@ -103,6 +103,9 @@
 
         @Composable
         override fun produceIcon(app: ApplicationInfo) = stateOf(null)
+
+        @Composable
+        override fun produceIconContentDescription(app: ApplicationInfo) = stateOf(null)
     }
 
     private companion object {
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
new file mode 100644
index 0000000..26caa01
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppRepositoryTest.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2023 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.settingslib.spaprivileged.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.os.UserManager
+import androidx.compose.runtime.State
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.testutils.delay
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.framework.common.userManager
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppRepositoryTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var userManager: UserManager
+
+    private lateinit var appRepository: AppRepositoryImpl
+
+    @Before
+    fun setUp() {
+        whenever(context.userManager).thenReturn(userManager)
+        appRepository = AppRepositoryImpl(context)
+    }
+
+    @Test
+    fun produceIconContentDescription_workProfile() {
+        whenever(userManager.isManagedProfile(APP.userId)).thenReturn(true)
+
+        val contentDescription = produceIconContentDescription()
+
+        assertThat(contentDescription.value).isEqualTo(context.getString(R.string.category_work))
+    }
+
+    @Test
+    fun produceIconContentDescription_personalProfile() {
+        whenever(userManager.isManagedProfile(APP.userId)).thenReturn(false)
+
+        val contentDescription = produceIconContentDescription()
+
+        assertThat(contentDescription.value).isNull()
+    }
+
+    private fun produceIconContentDescription(): State<String?> {
+        var contentDescription: State<String?> = stateOf(null)
+        composeTestRule.setContent {
+            contentDescription = appRepository.produceIconContentDescription(APP)
+        }
+        composeTestRule.delay()
+        return contentDescription
+    }
+
+    private companion object {
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            uid = UID
+        }
+    }
+}
\ No newline at end of file