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