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

Commit 2b11c1fe authored by Chaohui Wang's avatar Chaohui Wang
Browse files

Add AppPermissionPreference for Spa

This is the permission preference in the App Settings page.
The summary is single line.

Add the first Kotlin Robolectric Test for Settings, since Kotlin is not
directly supported by the Robolectric test, using a Java class as a
wrapper.

Bug: 236346018
Test: Manual with App Settings page
Test: Robolectric Test
Change-Id: Ic5a4f7d965885a9cd143428a8cd1900981e316a9
parent fcf10fa0
Loading
Loading
Loading
Loading
+70 −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.settings.spa.app.appsettings

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.android.settings.R
import com.android.settingslib.spa.widget.preference.Preference
import com.android.settingslib.spa.widget.preference.PreferenceModel
import com.android.settingslib.spaprivileged.model.app.userHandle

private const val TAG = "AppPermissionPreference"
private const val EXTRA_HIDE_INFO_BUTTON = "hideInfoButton"

@Composable
fun AppPermissionPreference(app: ApplicationInfo) {
    val context = LocalContext.current
    val summaryLiveData = remember { AppPermissionSummaryLiveData(context, app) }
    val summaryState = summaryLiveData.observeAsState(initial = AppPermissionSummaryState(
        summary = stringResource(R.string.summary_placeholder),
        enabled = false,
    ))
    Preference(
        model = remember {
            object : PreferenceModel {
                override val title = context.getString(R.string.permissions_label)
                override val summary = derivedStateOf { summaryState.value.summary }
                override val enabled = derivedStateOf { summaryState.value.enabled }
                override val onClick = { startManagePermissionsActivity(context, app) }
            }
        },
        singleLineSummary = true,
    )
}

/** Starts new activity to manage app permissions */
private fun startManagePermissionsActivity(context: Context, app: ApplicationInfo) {
    val intent = Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS).apply {
        putExtra(Intent.EXTRA_PACKAGE_NAME, app.packageName)
        putExtra(EXTRA_HIDE_INFO_BUTTON, true)
    }
    try {
        context.startActivityAsUser(intent, app.userHandle)
    } catch (e: ActivityNotFoundException) {
        Log.w(TAG, "No app can handle android.intent.action.MANAGE_APP_PERMISSIONS")
    }
}
+102 −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.settings.spa.app.appsettings

import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager.OnPermissionsChangedListener
import android.icu.text.ListFormatter
import androidx.lifecycle.LiveData
import com.android.settings.R
import com.android.settingslib.applications.PermissionsSummaryHelper
import com.android.settingslib.applications.PermissionsSummaryHelper.PermissionsResultCallback
import com.android.settingslib.spaprivileged.model.app.userHandle

data class AppPermissionSummaryState(
    val summary: String,
    val enabled: Boolean,
)

class AppPermissionSummaryLiveData(
    private val context: Context,
    private val app: ApplicationInfo,
) : LiveData<AppPermissionSummaryState>() {
    private val contextAsUser = context.createContextAsUser(app.userHandle, 0)
    private val packageManager = contextAsUser.packageManager

    private val onPermissionsChangedListener = OnPermissionsChangedListener { uid ->
        if (uid == app.uid) update()
    }

    override fun onActive() {
        packageManager.addOnPermissionsChangeListener(onPermissionsChangedListener)
        update()
    }

    override fun onInactive() {
        packageManager.removeOnPermissionsChangeListener(onPermissionsChangedListener)
    }

    private fun update() {
        PermissionsSummaryHelper.getPermissionSummary(
            contextAsUser, app.packageName, permissionsCallback
        )
    }

    private val permissionsCallback = object : PermissionsResultCallback {
        override fun onPermissionSummaryResult(
            requestedPermissionCount: Int,
            additionalGrantedPermissionCount: Int,
            grantedGroupLabels: List<CharSequence>,
        ) {
            if (requestedPermissionCount == 0) {
                postValue(noPermissionRequestedState())
                return
            }
            val labels = getDisplayLabels(additionalGrantedPermissionCount, grantedGroupLabels)
            val summary = when {
                labels.isEmpty() -> {
                    context.getString(R.string.runtime_permissions_summary_no_permissions_granted)
                }

                else -> ListFormatter.getInstance().format(labels)
            }
            postValue(AppPermissionSummaryState(summary = summary, enabled = true))
        }
    }

    private fun noPermissionRequestedState() = AppPermissionSummaryState(
        summary = context.getString(R.string.runtime_permissions_summary_no_permissions_requested),
        enabled = false,
    )

    private fun getDisplayLabels(
        additionalGrantedPermissionCount: Int,
        grantedGroupLabels: List<CharSequence>,
    ): List<CharSequence> = when (additionalGrantedPermissionCount) {
        0 -> grantedGroupLabels
        else -> {
            grantedGroupLabels +
                // N additional permissions.
                context.resources.getQuantityString(
                    R.plurals.runtime_permissions_additional_count,
                    additionalGrantedPermissionCount,
                    additionalGrantedPermissionCount,
                )
        }
    }
}
+2 −0
Original line number Diff line number Diff line
@@ -90,6 +90,8 @@ private fun AppSettings(packageInfoPresenter: PackageInfoPresenter) {

        AppButtons(packageInfoPresenter)

        AppPermissionPreference(app)

        Category(title = stringResource(R.string.advanced_apps)) {
            DisplayOverOtherAppsAppListProvider.InfoPageEntryItem(app)
            ModifySystemSettingsAppListProvider.InfoPageEntryItem(app)
+1 −0
Original line number Diff line number Diff line
@@ -73,6 +73,7 @@ android_robolectric_test {
    name: "SettingsRoboTests",
    srcs: [
        "src/**/*.java",
        "src/**/*.kt",
    ],

    static_libs: [
+24 −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.settings.spa.app.appsettings;

import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;

// TODO: Remove this class when Kotlin is supported by the Robolectric test.
@RunWith(RobolectricTestRunner.class)
public class AppPermissionSummaryJavaTest extends AppPermissionSummaryTest {
}
Loading