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

Commit 7459ecff authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Refactor TogglePermissionAppInfoPageProvider

Also add unit tests.

Bug: 260660819
Test: Unit test
Test: Manually with Settings
Change-Id: Ifb6844f4127f07ea5d211d68806e683144310589
parent e3bda562
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -74,6 +74,8 @@ interface RestrictionsProvider {
    fun restrictedModeState(): State<RestrictedMode?>
}

typealias RestrictionsProviderFactory = (Context, Restrictions) -> RestrictionsProvider

internal class RestrictionsProviderImpl(
    private val context: Context,
    private val restrictions: Restrictions,
+9 −10
Original line number Diff line number Diff line
@@ -16,11 +16,12 @@

package com.android.settingslib.spaprivileged.template.app

import android.content.pm.PackageInfo
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import com.android.settingslib.spa.widget.scaffold.RegularScaffold
import com.android.settingslib.spa.widget.ui.Footer
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import com.android.settingslib.spaprivileged.model.app.IPackageManagers

@Composable
fun AppInfoPage(
@@ -28,18 +29,16 @@ fun AppInfoPage(
    packageName: String,
    userId: Int,
    footerText: String,
    content: @Composable () -> Unit,
    packageManagers: IPackageManagers,
    content: @Composable PackageInfo.() -> Unit,
) {
    val packageInfo = remember(packageName, userId) {
        packageManagers.getPackageInfoAsUser(packageName, userId)
    } ?: return
    RegularScaffold(title = title) {
        val appInfoProvider = remember {
            PackageManagers.getPackageInfoAsUser(packageName, userId)?.let { packageInfo ->
                AppInfoProvider(packageInfo)
            }
        } ?: return@RegularScaffold

        appInfoProvider.AppInfo(displayVersion = true)
        remember(packageInfo) { AppInfoProvider(packageInfo) }.AppInfo(displayVersion = true)

        content()
        packageInfo.content()

        Footer(footerText)
    }
+35 −42
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settingslib.spaprivileged.template.app
import android.content.Context
import android.content.pm.ApplicationInfo
import android.os.Bundle
import androidx.annotation.VisibleForTesting
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
@@ -38,23 +39,15 @@ import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.app.PackageManagers
import com.android.settingslib.spaprivileged.model.app.toRoute
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl
import com.android.settingslib.spaprivileged.template.preference.RestrictedSwitchPreference
import kotlinx.coroutines.Dispatchers

private const val ENTRY_NAME = "AllowControl"
private const val PERMISSION = "permission"
private const val PACKAGE_NAME = "rt_packageName"
private const val USER_ID = "rt_userId"
private const val PAGE_NAME = "TogglePermissionAppInfoPage"
private val PAGE_PARAMETER = listOf(
    navArgument(PERMISSION) { type = NavType.StringType },
    navArgument(PACKAGE_NAME) { type = NavType.StringType },
    navArgument(USER_ID) { type = NavType.IntType },
)

internal class TogglePermissionAppInfoPageProvider(
    private val appListTemplate: TogglePermissionAppListTemplate,
) : SettingsPageProvider {
@@ -64,11 +57,7 @@ internal class TogglePermissionAppInfoPageProvider(

    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
        val owner = SettingsPage.create(name, parameter = parameter, arguments = arguments)
        val entryList = mutableListOf<SettingsEntry>()
        entryList.add(
            SettingsEntryBuilder.create(ENTRY_NAME, owner).build()
        )
        return entryList
        return listOf(SettingsEntryBuilder.create("AllowControl", owner).build())
    }

    @Composable
@@ -76,11 +65,22 @@ internal class TogglePermissionAppInfoPageProvider(
        val permissionType = arguments?.getString(PERMISSION)!!
        val packageName = arguments.getString(PACKAGE_NAME)!!
        val userId = arguments.getInt(USER_ID)
        val listModel = appListTemplate.rememberModel(permissionType)
        TogglePermissionAppInfoPage(listModel, packageName, userId)
        appListTemplate.rememberModel(permissionType)
            .TogglePermissionAppInfoPage(packageName, userId)
    }

    companion object {
        private const val PAGE_NAME = "TogglePermissionAppInfoPage"
        private const val PERMISSION = "permission"
        private const val PACKAGE_NAME = "rt_packageName"
        private const val USER_ID = "rt_userId"

        private val PAGE_PARAMETER = listOf(
            navArgument(PERMISSION) { type = NavType.StringType },
            navArgument(PACKAGE_NAME) { type = NavType.StringType },
            navArgument(USER_ID) { type = NavType.IntType },
        )

        @Composable
        fun navigator(permissionType: String, app: ApplicationInfo) =
            navigator(route = "$PAGE_NAME/$permissionType/${app.toRoute()}")
@@ -116,43 +116,36 @@ internal class TogglePermissionAppInfoPageProvider(
    }
}

@VisibleForTesting
@Composable
private fun TogglePermissionAppInfoPage(
    listModel: TogglePermissionAppListModel<out AppRecord>,
internal fun TogglePermissionAppListModel<out AppRecord>.TogglePermissionAppInfoPage(
    packageName: String,
    userId: Int,
    packageManagers: IPackageManagers = PackageManagers,
    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
    AppInfoPage(
        title = stringResource(listModel.pageTitleResId),
        title = stringResource(pageTitleResId),
        packageName = packageName,
        userId = userId,
        footerText = stringResource(listModel.footerResId),
        footerText = stringResource(footerResId),
        packageManagers = packageManagers,
    ) {
        val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
        LaunchedEffect(model, Dispatchers.Default) {
            model.initState()
        }
        RestrictedSwitchPreference(model, Restrictions(userId, listModel.switchRestrictionKeys))
        val model = createSwitchModel(applicationInfo)
        val restrictions = Restrictions(userId, switchRestrictionKeys)
        RestrictedSwitchPreference(model, restrictions, restrictionsProviderFactory)
    }
}

@Composable
private fun <T : AppRecord> createSwitchModel(
    listModel: TogglePermissionAppListModel<T>,
    packageName: String,
    userId: Int,
): TogglePermissionSwitchModel<T>? {
    val record = remember {
        PackageManagers.getApplicationInfoAsUser(packageName, userId)?.let { app ->
            listModel.transformItem(app)
        }
    } ?: return null

private fun <T : AppRecord> TogglePermissionAppListModel<T>.createSwitchModel(
    app: ApplicationInfo,
): TogglePermissionSwitchModel<T> {
    val context = LocalContext.current
    val isAllowed = listModel.isAllowed(record)
    return remember {
        TogglePermissionSwitchModel(context, listModel, record, isAllowed)
    }
    val record = remember(app) { transformItem(app) }
    val isAllowed = isAllowed(record)
    return remember(record) { TogglePermissionSwitchModel(context, this, record, isAllowed) }
        .also { model -> LaunchedEffect(model, Dispatchers.IO) { model.initState() } }
}

private class TogglePermissionSwitchModel<T : AppRecord>(
+3 −8
Original line number Diff line number Diff line
@@ -38,19 +38,14 @@ import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode
import com.android.settingslib.spaprivileged.model.enterprise.Restrictions
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProvider
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory
import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl

@Composable
fun RestrictedSwitchPreference(model: SwitchPreferenceModel, restrictions: Restrictions) {
    RestrictedSwitchPreferenceImpl(model, restrictions, ::RestrictionsProviderImpl)
}

@Composable
internal fun RestrictedSwitchPreferenceImpl(
fun RestrictedSwitchPreference(
    model: SwitchPreferenceModel,
    restrictions: Restrictions,
    restrictionsProviderFactory: (Context, Restrictions) -> RestrictionsProvider,
    restrictionsProviderFactory: RestrictionsProviderFactory = ::RestrictionsProviderImpl,
) {
    if (restrictions.keys.isEmpty()) {
        SwitchPreference(model)
+153 −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.settingslib.spaprivileged.template.app

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotEnabled
import androidx.compose.ui.test.assertIsOff
import androidx.compose.ui.test.assertIsOn
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.settingslib.spaprivileged.model.app.IPackageManagers
import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted
import com.android.settingslib.spaprivileged.tests.testutils.FakeRestrictionsProvider
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListModel
import com.android.settingslib.spaprivileged.tests.testutils.TestTogglePermissionAppListProvider
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.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.Mockito.`when` as whenever

@RunWith(AndroidJUnit4::class)
class TogglePermissionAppInfoPageTest {
    @get:Rule
    val composeTestRule = createComposeRule()

    @get:Rule
    val mockito: MockitoRule = MockitoJUnit.rule()

    private val context: Context = ApplicationProvider.getApplicationContext()

    @Mock
    private lateinit var packageManagers: IPackageManagers

    private val fakeRestrictionsProvider = FakeRestrictionsProvider()

    private val appListTemplate =
        TogglePermissionAppListTemplate(listOf(TestTogglePermissionAppListProvider))

    private val appInfoPageProvider = TogglePermissionAppInfoPageProvider(appListTemplate)

    @Before
    fun setUp() {
        fakeRestrictionsProvider.restrictedMode = NoRestricted
        whenever(packageManagers.getPackageInfoAsUser(PACKAGE_NAME, USER_ID))
            .thenReturn(PACKAGE_INFO)
    }

    @Test
    fun buildEntry() {
        val entryList = appInfoPageProvider.buildEntry(null)

        assertThat(entryList).hasSize(1)
        assertThat(entryList[0].displayName).isEqualTo("AllowControl")
    }

    @Test
    fun title_isDisplayed() {
        val listModel = TestTogglePermissionAppListModel()

        setTogglePermissionAppInfoPage(listModel)

        composeTestRule.onNodeWithText(context.getString(listModel.pageTitleResId))
            .assertIsDisplayed()
    }

    @Test
    fun whenAllowed_switchIsOn() {
        val listModel = TestTogglePermissionAppListModel(isAllowed = true)

        setTogglePermissionAppInfoPage(listModel)

        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
            .assertIsOn()
    }

    @Test
    fun whenNotAllowed_switchIsOff() {
        val listModel = TestTogglePermissionAppListModel(isAllowed = false)

        setTogglePermissionAppInfoPage(listModel)

        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
            .assertIsOff()
    }

    @Test
    fun whenNotChangeable_switchNotEnabled() {
        val listModel = TestTogglePermissionAppListModel(isAllowed = false, isChangeable = false)

        setTogglePermissionAppInfoPage(listModel)

        composeTestRule.onNodeWithText(context.getString(listModel.switchTitleResId))
            .assertIsDisplayed()
            .assertIsNotEnabled()
    }

    @Test
    fun footer_isDisplayed() {
        val listModel = TestTogglePermissionAppListModel()

        setTogglePermissionAppInfoPage(listModel)

        composeTestRule.onNodeWithText(context.getString(listModel.footerResId))
            .assertIsDisplayed()
    }

    private fun setTogglePermissionAppInfoPage(listModel: TestTogglePermissionAppListModel) {
        composeTestRule.setContent {
            listModel.TogglePermissionAppInfoPage(
                packageName = PACKAGE_NAME,
                userId = USER_ID,
                packageManagers = packageManagers,
                restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider },
            )
        }
    }

    private companion object {
        const val USER_ID = 0
        const val PACKAGE_NAME = "package.name"
        val APP = ApplicationInfo().apply {
            packageName = PACKAGE_NAME
        }
        val PACKAGE_INFO = PackageInfo().apply {
            packageName = PACKAGE_NAME
            applicationInfo = APP
        }
    }
}
Loading