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

Commit 0c646645 authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Add work profile content description for app

Async generate the content description for app icon.
Also semantic group the app icon & label in App Info.

Bug: 236346018
Test: Manually with Settings when Talkback is on
Test: Unit test
Change-Id: I17292243dc1efd591924ee94688430082b318675
parent cf16a02e
Loading
Loading
Loading
Loading
+23 −7
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.settingslib.spaprivileged.model.app
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 androidx.compose.ui.res.stringResource
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 @@ interface AppRepository {
        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 @@ internal class AppRepositoryImpl(private val context: Context) : AppRepository {
    @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
                }
            }
        }
}
+5 −3
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ import androidx.compose.runtime.Composable
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 @@ class AppInfoProvider(private val packageInfo: PackageInfo) {
                .padding(
                    horizontal = SettingsDimension.itemPaddingStart,
                    vertical = SettingsDimension.itemPaddingVertical,
                ),
                )
                .semantics(mergeDescendants = true) {},
            horizontalAlignment = Alignment.CenterHorizontally,
        ) {
            val app = packageInfo.applicationInfo
@@ -93,8 +95,8 @@ internal fun AppIcon(app: ApplicationInfo, size: Dp) {
    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),
    )
}

+3 −0
Original line number Diff line number Diff line
@@ -103,6 +103,9 @@ class AppListViewModelTest {

        @Composable
        override fun produceIcon(app: ApplicationInfo) = stateOf(null)

        @Composable
        override fun produceIconContentDescription(app: ApplicationInfo) = stateOf(null)
    }

    private companion object {
+96 −0
Original line number Diff line number Diff line
/*
 * 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