Add AutoRevoke UI
Add a PermissionController UI for the AutoRevoke feature
Test: none
Bug: 151252163
Change-Id: I98247633a23321b3a21b97c10f07512c89e72be5
diff --git a/PermissionController/res/layout/app_permission.xml b/PermissionController/res/layout/app_permission.xml
index 4831a8c..bde26eb 100644
--- a/PermissionController/res/layout/app_permission.xml
+++ b/PermissionController/res/layout/app_permission.xml
@@ -36,20 +36,20 @@
<include layout="@layout/header_large" />
<View
- style="@style/LargeHeaderDivider"/>
+ style="@style/LargeHeaderDivider" />
<LinearLayout
style="@style/AppPermissionSelection">
<TextView
android:id="@+id/permission_message"
- style="@style/AppPermissionMessage"/>
+ style="@style/AppPermissionMessage" />
<RadioGroup
android:id="@+id/radiogroup"
android:animateLayoutChanges="true"
android:layout_width="match_parent"
- android:layout_height="wrap_content" >
+ android:layout_height="wrap_content">
<RadioButton
android:id="@+id/allow_radio_button"
@@ -108,7 +108,7 @@
<LinearLayout
android:id="@+id/widget_frame"
- style="@style/AppPermissionWidgetFrame"/>
+ style="@style/AppPermissionWidgetFrame" />
</LinearLayout>
diff --git a/PermissionController/res/navigation/nav_graph.xml b/PermissionController/res/navigation/nav_graph.xml
index 0934550..235b5f1 100644
--- a/PermissionController/res/navigation/nav_graph.xml
+++ b/PermissionController/res/navigation/nav_graph.xml
@@ -101,7 +101,6 @@
app:popEnterAnim="@anim/activity_open_enter"/>
</fragment>
-
<fragment
android:id="@+id/all_app_permissions"
android:name="com.android.permissioncontroller.permission.ui.handheld.AllAppPermissionsFragment"
@@ -132,7 +131,5 @@
app:enterAnim="@anim/activity_open_enter"
app:popExitAnim="@anim/activity_close_exit"
app:popEnterAnim="@anim/activity_open_enter"/>
-
</fragment>
-
</navigation>
\ No newline at end of file
diff --git a/PermissionController/res/values/strings.xml b/PermissionController/res/values/strings.xml
index 6ec493c..b378897 100644
--- a/PermissionController/res/values/strings.xml
+++ b/PermissionController/res/values/strings.xml
@@ -307,6 +307,24 @@
<!-- Text for linking to the page that shows the apps with a given permission [CHAR LIMIT=none] -->
<string name="app_permission_footer_permission_apps_link">See all apps with this permission</string>
+ <!-- Label for the auto revoke switch [CHAR LIMIT=60] -->
+ <string name="auto_revoke_label">Auto revoke permissions</string>
+
+ <!-- Summary for stating that auto revoke is disabled for this app[CHAR LIMIT=none] -->
+ <string name="auto_revoke_summary">To protect your data, permissions for this app will be removed if the app isn\u2019t used for a few months.</string>
+
+ <!-- Summary for stating that no permission groups that are granted and auto revocable[CHAR LIMIT=none] -->
+ <string name="auto_revocable_permissions_none">No auto revocable permissions are currently granted</string>
+
+ <!-- Summary for stating that one permission will be auto revoked[CHAR LIMIT=none] -->
+ <string name="auto_revocable_permissions_one"><xliff:g id="perm" example="location">%1$s</xliff:g> permission will be removed.</string>
+
+ <!-- Summary for stating that two permissions will be auto revoked[CHAR LIMIT=none] -->
+ <string name="auto_revocable_permissions_two"><xliff:g id="perm" example="location">%1$s</xliff:g> and <xliff:g id="perm" example="contacts">%2$s</xliff:g> permissions will be removed.</string>
+
+ <!-- Summary for stating that more than two permissions will be auto revoked[CHAR LIMIT=none] -->
+ <string name="auto_revocable_permissions_many">Permissions that will be removed: <xliff:g id="perms" example="location, contacts, and phone">%1$s</xliff:g>.</string>
+
<!-- Label for showing a permission group's description in the header of the list of apps that have that permission [CHAR LIMIT=none] -->
<string name="permission_description_summary_generic">Apps with this permission can <xliff:g id="description" example="record audio">%1$s</xliff:g></string>
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokeStateLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokeStateLiveData.kt
new file mode 100644
index 0000000..f9d5363
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/AutoRevokeStateLiveData.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 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.permissioncontroller.permission.data
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
+import android.app.Application
+import android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT
+import android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE
+import android.os.UserHandle
+import android.provider.DeviceConfig
+import androidx.lifecycle.Observer
+import com.android.permissioncontroller.PermissionControllerApplication
+import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
+import com.android.permissioncontroller.permission.model.livedatatypes.AutoRevokeState
+import com.android.permissioncontroller.permission.utils.KotlinUtils
+import com.android.permissioncontroller.permission.utils.Utils
+import kotlinx.coroutines.Job
+import java.util.concurrent.TimeUnit
+
+/**
+ * A LiveData which tracks the AutoRevoke state for one user package.
+ *
+ * @param app The current application
+ * @param packageName The package name whose state we want
+ * @param user The user for whom we want the package
+ */
+class AutoRevokeStateLiveData private constructor(
+ app: Application,
+ private val packageName: String,
+ private val user: UserHandle
+) : SmartAsyncMediatorLiveData<AutoRevokeState>(), AppOpsManager.OnOpChangedListener {
+
+ private val packagePermsLiveData =
+ PackagePermissionsLiveData[packageName, user]
+ private val packageLiveData = LightPackageInfoLiveData[packageName, user]
+ private val permStateLiveDatas = mutableMapOf<String, PermStateLiveData>()
+ private val appOpsManager = app.getSystemService(AppOpsManager::class.java)!!
+
+ init {
+ addSource(packagePermsLiveData) {
+ updateIfActive()
+ }
+ addSource(packageLiveData) {
+ updateIfActive()
+ }
+ }
+
+ override suspend fun loadDataAndPostValue(job: Job) {
+ val uid = packageLiveData.value?.uid
+ if (uid == null && packageLiveData.isInitialized) {
+ postValue(null)
+ return
+ } else if (uid == null) {
+ return
+ }
+
+ val groups = packagePermsLiveData.value?.keys?.filter { it != NON_RUNTIME_NORMAL_PERMS }
+ if (groups == null && packagePermsLiveData.isInitialized) {
+ postValue(null)
+ return
+ } else if (groups == null) {
+ return
+ }
+
+ addAndRemovePermStateLiveDatas(groups)
+
+ if (!permStateLiveDatas.all { it.value.isInitialized }) {
+ return
+ }
+
+ val revocable = appOpsManager.unsafeCheckOpNoThrow(
+ OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName) == MODE_ALLOWED
+ val autoRevokeState = mutableListOf<String>()
+ permStateLiveDatas.forEach { (groupName, liveData) ->
+ val default = liveData.value?.any { (_, permState) ->
+ permState.permFlags and (FLAG_PERMISSION_GRANTED_BY_DEFAULT or
+ FLAG_PERMISSION_GRANTED_BY_ROLE) != 0
+ } ?: false
+ if (!default) {
+ autoRevokeState.add(groupName)
+ }
+ }
+
+ postValue(AutoRevokeState(isAutoRevokeEnabledGlobal(), revocable, autoRevokeState))
+ }
+
+ private fun isAutoRevokeEnabledGlobal(): Boolean {
+ val unusedThreshold = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
+ Utils.PROPERTY_AUTO_REVOKE_UNUSED_THRESHOLD_MILLIS, TimeUnit.DAYS.toMillis(90))
+ val checkFrequency = DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
+ Utils.PROPERTY_AUTO_REVOKE_CHECK_FREQUENCY_MILLIS, TimeUnit.DAYS.toMillis(1))
+ return unusedThreshold > 0 && checkFrequency > 0
+ }
+
+ private fun addAndRemovePermStateLiveDatas(groupNames: List<String>) {
+ val (toAdd, toRemove) = KotlinUtils.getMapAndListDifferences(groupNames,
+ permStateLiveDatas)
+
+ for (groupToAdd in toAdd) {
+ val permStateLiveData =
+ PermStateLiveData[packageName, groupToAdd, user]
+ permStateLiveDatas[groupToAdd] = permStateLiveData
+ }
+
+ for (groupToAdd in toAdd) {
+ postAddSource(permStateLiveDatas[groupToAdd]!!, Observer {
+ updateIfActive()
+ })
+ }
+
+ for (groupToRemove in toRemove) {
+ postRemoveSource(permStateLiveDatas[groupToRemove]!!)
+ permStateLiveDatas.remove(groupToRemove)
+ }
+ }
+
+ override fun onOpChanged(op: String?, packageName: String?) {
+ if (op == OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED && packageName == packageName) {
+ updateIfActive()
+ }
+ }
+
+ override fun onActive() {
+ super.onActive()
+ appOpsManager.startWatchingMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageName, this)
+ updateIfActive()
+ }
+
+ override fun onInactive() {
+ super.onInactive()
+ appOpsManager.stopWatchingMode(this)
+ }
+ /**
+ * Repository for AutoRevokeStateLiveDatas.
+ * <p> Key value is a pair of string package name and UserHandle, value is its corresponding
+ * LiveData.
+ */
+ companion object : DataRepository<Pair<String, UserHandle>, AutoRevokeStateLiveData>() {
+ override fun newValue(key: Pair<String, UserHandle>): AutoRevokeStateLiveData {
+ return AutoRevokeStateLiveData(PermissionControllerApplication.get(),
+ key.first, key.second)
+ }
+ }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
index c278308..43ec4a9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/data/SmartUpdateMediatorLiveData.kt
@@ -29,6 +29,9 @@
import com.android.permissioncontroller.permission.utils.ensureMainThread
import com.android.permissioncontroller.permission.utils.getInitializedValue
import com.android.permissioncontroller.permission.utils.shortStackTrace
+import kotlinx.coroutines.Dispatchers.Main
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* A MediatorLiveData which tracks how long it has been inactive, compares new values before setting
@@ -161,6 +164,14 @@
super.removeSource(toRemote)
}
+ fun <S : Any?> postAddSource(source: LiveData<S>, onChanged: Observer<in S>) {
+ GlobalScope.launch(Main) { addSource(source, onChanged) }
+ }
+
+ fun <S : Any?> postRemoveSource(toRemote: LiveData<S>) {
+ GlobalScope.launch(Main) { removeSource(toRemote) }
+ }
+
private fun <S : Any?> removeChild(liveData: LiveData<S>) {
children.removeIf { it.first == liveData }
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AppPermGroupUiInfo.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AppPermGroupUiInfo.kt
index 9f4cfe7..bebb108 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AppPermGroupUiInfo.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AppPermGroupUiInfo.kt
@@ -29,7 +29,7 @@
val permGrantState: PermGrantState,
val isSystem: Boolean
) {
- enum class PermGrantState(val grantState: Int) {
+ enum class PermGrantState(private val grantState: Int) {
PERMS_DENIED(0),
PERMS_ALLOWED(1),
PERMS_ALLOWED_FOREGROUND_ONLY(2),
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AutoRevokeState.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AutoRevokeState.kt
new file mode 100644
index 0000000..b0422f7
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/AutoRevokeState.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2020 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.permissioncontroller.permission.model.livedatatypes
+
+/**
+ * Tracks the state of auto revoke for a package
+ *
+ * @param isEnabledGlobal Whether or not the Auto Revoke feature is enabled globally
+ * @param isEnabledForApp Whether or not the OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED is set to
+ * MODE_ALLOWED for this package
+ * @param revocableGroupNames A list of which permission groups of this package are eligible for
+ * auto-revoke. A permission group is auto-revocable if it does not contain a default granted
+ * permission.
+ */
+class AutoRevokeState(
+ val isEnabledGlobal: Boolean,
+ val isEnabledForApp: Boolean,
+ val revocableGroupNames: List<String>
+) {
+
+ /**
+ * If the auto revoke switch should be shown.
+ */
+ val shouldShowSwitch = revocableGroupNames.isNotEmpty()
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
index 255d27a..edb6078 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionFragment.java
@@ -154,7 +154,6 @@
return arguments;
}
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -337,7 +336,6 @@
setResult(DENIED_DO_NOT_ASK_AGAIN);
});
- // TODO(ntmyren): pass button states in a non-order specific way
setButtonState(mAllowButton, states.get(ButtonType.ALLOW));
setButtonState(mAllowAlwaysButton, states.get(ButtonType.ALLOW_ALWAYS));
setButtonState(mAllowForegroundButton, states.get(ButtonType.ALLOW_FOREGROUND));
@@ -361,8 +359,6 @@
}
}
-
-
private void setResult(@GrantPermissionsViewHandler.Result int result) {
Intent intent = new Intent()
.putExtra(EXTRA_RESULT_PERMISSION_INTERACTED, mPermGroupName)
@@ -428,7 +424,7 @@
* 1. `showDefaultDenyDialog`
* 1. [DefaultDenyDialog.onCreateDialog]
* 1. [AppPermissionViewModel.onDenyAnyWay]
- * TODO: Remove once data can be passed between dialogs and fragments with nav component
+ * TODO ntmyren: Remove once data can be passed between dialogs and fragments with nav component
*
* @param changeRequest Whether background or foreground should be changed
* @param messageId The Id of the string message to show
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
index e81d7c6..2711fb1 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/AppPermissionGroupsFragment.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
+import android.icu.text.ListFormatter;
import android.net.Uri;
import android.os.Bundle;
import android.os.UserHandle;
@@ -47,23 +48,26 @@
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
+import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
import com.android.permissioncontroller.PermissionControllerStatsLog;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.model.livedatatypes.AutoRevokeState;
import com.android.permissioncontroller.permission.ui.Category;
import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel;
+import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModel.GroupUiInfo;
import com.android.permissioncontroller.permission.ui.model.AppPermissionGroupsViewModelFactory;
import com.android.permissioncontroller.permission.utils.KotlinUtils;
import com.android.permissioncontroller.permission.utils.Utils;
import com.android.settingslib.HelpUtils;
import java.text.Collator;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
-import kotlin.Triple;
-
/**
* Show and manage permission groups for an app.
*
@@ -71,8 +75,12 @@
*/
public final class AppPermissionGroupsFragment extends SettingsWithLargeHeader {
- private static final String LOG_TAG = "ManagePermsFragment";
+ private static final String LOG_TAG = AppPermissionGroupsFragment.class.getSimpleName();
private static final String IS_SYSTEM_PERMS_SCREEN = "_is_system_screen";
+ private static final String AUTO_REVOKE_CATEGORY_KEY = "_AUTO_REVOKE_KEY";
+ private static final String AUTO_REVOKE_SWITCH_KEY = "_AUTO_REVOKE_SWITCH_KEY";
+ private static final String AUTO_REVOKE_SUMMARY_KEY = "_AUTO_REVOKE_SUMMARY_KEY";
+ private static final String AUTO_REVOKE_PERMS_KEY = "_AUTO_REVOKE_PERMS_KEY";
static final String EXTRA_HIDE_INFO_BUTTON = "hideInfoButton";
@@ -139,10 +147,11 @@
mIsSystemPermsScreen = getArguments().getBoolean(IS_SYSTEM_PERMS_SCREEN, true);
AppPermissionGroupsViewModelFactory factory = new AppPermissionGroupsViewModelFactory(
- getActivity().getApplication(), mPackageName, mUser);
+ mPackageName, mUser);
mViewModel = new ViewModelProvider(this, factory).get(AppPermissionGroupsViewModel.class);
mViewModel.getPackagePermGroupsLiveData().observe(this, this::updatePreferences);
+ mViewModel.getAutoRevokeLiveData().observe(this, this::setAutoRevokeToggleState);
mCollator = Collator.getInstance(
getContext().getResources().getConfiguration().getLocales().get(0));
@@ -202,12 +211,17 @@
}
- private void updatePreferences(Map<Category, List<Triple<String, Boolean, Boolean>>> groupMap) {
+ private void createPreferenceScreenIfNeeded() {
if (getPreferenceScreen() == null) {
addPreferencesFromResource(R.xml.allowed_denied);
+ addAutoRevokePreferences(getPreferenceScreen());
logAppPermissionsFragmentView();
bindUi(this, mPackageName, mUser);
}
+ }
+
+ private void updatePreferences(Map<Category, List<GroupUiInfo>> groupMap) {
+ createPreferenceScreenIfNeeded();
Context context = getPreferenceManager().getContext();
if (context == null) {
@@ -244,23 +258,21 @@
}
}
- for (Triple<String, Boolean, Boolean> groupTriple : groupMap.get(grantCategory)) {
- String groupName = groupTriple.getFirst();
- boolean isSystem = groupTriple.getSecond();
- boolean isForegroundOnly = groupTriple.getThird();
+ for (GroupUiInfo uiInfo : groupMap.get(grantCategory)) {
+ String groupName = uiInfo.getGroupName();
PermissionControlPreference preference = new PermissionControlPreference(context,
mPackageName, groupName, mUser, AppPermissionGroupsFragment.class.getName(),
sessionId, grantCategory.getCategoryName(), true);
preference.setTitle(KotlinUtils.INSTANCE.getPermGroupLabel(context, groupName));
preference.setIcon(KotlinUtils.INSTANCE.getPermGroupIcon(context, groupName));
- preference.setKey(preference.getTitle().toString());
- if (isForegroundOnly) {
+ preference.setKey(groupName);
+ if (uiInfo.isForeground()) {
preference.setSummary(R.string.permission_subtitle_only_in_foreground);
}
- if (isSystem == mIsSystemPermsScreen) {
+ if (uiInfo.isSystem() == mIsSystemPermsScreen) {
category.addPreference(preference);
- } else if (!isSystem) {
+ } else if (!uiInfo.isSystem()) {
numExtraPerms++;
}
}
@@ -281,6 +293,86 @@
KotlinUtils.INSTANCE.sortPreferenceGroup(category, false,
this::comparePreferences);
}
+
+ setAutoRevokeToggleState(mViewModel.getAutoRevokeLiveData().getValue());
+ }
+
+ private void addAutoRevokePreferences(PreferenceScreen screen) {
+ Context context = screen.getPreferenceManager().getContext();
+
+ PreferenceCategory autoRevokeCategory = new PreferenceCategory(context);
+ autoRevokeCategory.setKey(AUTO_REVOKE_CATEGORY_KEY);
+ screen.addPreference(autoRevokeCategory);
+
+ SwitchPreference autoRevokeSwitch = new SwitchPreference(context);
+ autoRevokeSwitch.setOnPreferenceClickListener((preference) -> {
+ mViewModel.setAutoRevoke(autoRevokeSwitch.isChecked());
+ return true;
+ });
+ autoRevokeSwitch.setTitle(R.string.auto_revoke_label);
+ autoRevokeSwitch.setKey(AUTO_REVOKE_SWITCH_KEY);
+ autoRevokeCategory.addPreference(autoRevokeSwitch);
+
+ Preference autoRevokeSummary = new Preference(context);
+ autoRevokeSummary.setIcon(Utils.applyTint(getActivity(), R.drawable.ic_info_outline,
+ android.R.attr.colorControlNormal));
+ autoRevokeSummary.setKey(AUTO_REVOKE_SUMMARY_KEY);
+ autoRevokeSummary.setSummary(R.string.auto_revoke_summary);
+ autoRevokeCategory.addPreference(autoRevokeSummary);
+
+ Preference autoRevokePerms = new Preference(context);
+ autoRevokePerms.setKey(AUTO_REVOKE_PERMS_KEY);
+ autoRevokeCategory.addPreference(autoRevokePerms);
+ }
+
+ private void setAutoRevokeToggleState(AutoRevokeState state) {
+ if (state == null || !mViewModel.getPackagePermGroupsLiveData().isInitialized()
+ || getListView() == null || getView() == null) {
+ return;
+ }
+
+ PreferenceCategory autoRevokeCategory = getPreferenceScreen()
+ .findPreference(AUTO_REVOKE_CATEGORY_KEY);
+ SwitchPreference autoRevokeSwitch = autoRevokeCategory.findPreference(
+ AUTO_REVOKE_SWITCH_KEY);
+ Preference autoRevokePerms = autoRevokeCategory.findPreference(AUTO_REVOKE_PERMS_KEY);
+ Preference autoRevokeSummary = autoRevokeCategory.findPreference(AUTO_REVOKE_SUMMARY_KEY);
+
+ if (!state.isEnabledGlobal() || !state.getShouldShowSwitch()) {
+ autoRevokeSwitch.setVisible(false);
+ autoRevokePerms.setVisible(false);
+ autoRevokeSummary.setVisible(false);
+ return;
+ }
+ autoRevokeSwitch.setVisible(true);
+ autoRevokePerms.setVisible(true);
+ autoRevokeSummary.setVisible(true);
+ autoRevokeSwitch.setChecked(state.isEnabledForApp());
+
+ List<String> groupLabels = new ArrayList<>();
+ for (String groupName : state.getRevocableGroupNames()) {
+ PreferenceCategory category = getPreferenceScreen().findPreference(
+ Category.ALLOWED.getCategoryName());
+ Preference pref = category.findPreference(groupName);
+ if (pref != null) {
+ groupLabels.add(pref.getTitle().toString());
+ }
+ }
+
+ groupLabels.sort(mCollator);
+ if (groupLabels.isEmpty()) {
+ autoRevokePerms.setSummary(R.string.auto_revocable_permissions_none);
+ } else if (groupLabels.size() == 1) {
+ autoRevokePerms.setSummary(getString(R.string.auto_revocable_permissions_one,
+ groupLabels.get(0)));
+ } else if (groupLabels.size() == 2) {
+ autoRevokePerms.setSummary(getString(R.string.auto_revocable_permissions_two,
+ groupLabels.get(0), groupLabels.get(1)));
+
+ } else {
+ autoRevokePerms.setSummary(getString(R.string.auto_revocable_permissions_many,
+ ListFormatter.getInstance().format(groupLabels)));
+ }
}
private int comparePreferences(Preference lhs, Preference rhs) {
@@ -347,7 +439,8 @@
category = APP_PERMISSIONS_FRAGMENT_VIEWED__CATEGORY__ALLOWED_FOREGROUND;
}
- logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(), category);
+ logAppPermissionsFragmentViewEntry(sessionId, viewId, preference.getKey(),
+ category);
}
PreferenceCategory denied = findPreference(Category.DENIED.getCategoryName());
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SettingsWithLargeHeader.java b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SettingsWithLargeHeader.java
index e857fd9..cbd4a0c 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SettingsWithLargeHeader.java
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SettingsWithLargeHeader.java
@@ -41,7 +41,7 @@
*/
public abstract class SettingsWithLargeHeader extends PermissionsFrameFragment {
static final String HEADER_KEY = " HEADER_PREFERENCE";
- static final int HEADER_SORT_FIRST = -1;
+ private static final int HEADER_SORT_FIRST = -2;
private View mHeader;
private LargeHeaderPreference mHeaderPreference;
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SmartIconLoadPackagePermissionPreference.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SmartIconLoadPackagePermissionPreference.kt
index 209bdba..07c6776 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SmartIconLoadPackagePermissionPreference.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/handheld/SmartIconLoadPackagePermissionPreference.kt
@@ -39,7 +39,7 @@
* @param user The user whose package icon will be retrieved
* @param context The current context
*/
-open class SmartIconLoadPackagePermissionPreference @JvmOverloads constructor(
+open class SmartIconLoadPackagePermissionPreference constructor(
private val app: Application,
private val packageName: String,
private val user: UserHandle,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
index 9d57b73..5043f01 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionGroupsViewModel.kt
@@ -16,15 +16,21 @@
package com.android.permissioncontroller.permission.ui.model
-import android.app.Application
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_IGNORED
+import android.app.AppOpsManager.OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED
import android.os.Bundle
import android.os.UserHandle
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
+import com.android.permissioncontroller.PermissionControllerApplication
import com.android.permissioncontroller.R
import com.android.permissioncontroller.permission.data.AppPermGroupUiInfoLiveData
+import com.android.permissioncontroller.permission.data.AutoRevokeStateLiveData
+import com.android.permissioncontroller.permission.data.LightPackageInfoLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData
import com.android.permissioncontroller.permission.data.PackagePermissionsLiveData.Companion.NON_RUNTIME_NORMAL_PERMS
import com.android.permissioncontroller.permission.data.SmartUpdateMediatorLiveData
@@ -33,34 +39,29 @@
import com.android.permissioncontroller.permission.ui.Category
import com.android.permissioncontroller.permission.utils.KotlinUtils
import com.android.permissioncontroller.permission.utils.Utils
+import kotlinx.coroutines.Dispatchers.IO
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
/**
* ViewModel for the AppPermissionGroupsFragment. Has a liveData with the UI information for all
* permission groups that this package requests runtime permissions from
*
- * @param app The current application
* @param packageName The name of the package this viewModel is representing
* @param user The user of the package this viewModel is representing
*/
class AppPermissionGroupsViewModel(
- app: Application,
private val packageName: String,
private val user: UserHandle
) : ViewModel() {
- val packagePermGroupsLiveData = PackagePermGroupsLiveData(app, packageName, user)
-
/**
* LiveData whose data is a map of grant category (either allowed or denied) to a list
* of permission group names that match the key, and two booleans representing if this is a
- * system group, and, if it is allowed in the foreground only.
+ * system group, and a subtitle resource ID, if applicable.
*/
- inner class PackagePermGroupsLiveData(
- private val app: Application,
- private val packageName: String,
- private val user: UserHandle
- ) : SmartUpdateMediatorLiveData<@JvmSuppressWildcards
- Map<Category, List<Triple<String, Boolean, Boolean>>>>() {
+ val packagePermGroupsLiveData = object : SmartUpdateMediatorLiveData<@JvmSuppressWildcards
+ Map<Category, List<GroupUiInfo>>>() {
private val packagePermsLiveData =
PackagePermissionsLiveData[packageName, user]
@@ -89,7 +90,7 @@
}
val groupGrantStates = mutableMapOf<Category,
- MutableList<Triple<String, Boolean, Boolean>>>()
+ MutableList<GroupUiInfo>>()
groupGrantStates[Category.ALLOWED] = mutableListOf()
groupGrantStates[Category.ASK] = mutableListOf()
groupGrantStates[Category.DENIED] = mutableListOf()
@@ -99,15 +100,16 @@
appPermGroupUiInfoLiveDatas[groupName]?.value?.let { uiInfo ->
when (uiInfo.permGrantState) {
PermGrantState.PERMS_ALLOWED -> groupGrantStates[Category.ALLOWED]!!.add(
- Triple(groupName, isSystem, false))
+ GroupUiInfo(groupName, isSystem))
PermGrantState.PERMS_ALLOWED_ALWAYS -> groupGrantStates[
- Category.ALLOWED]!!.add(Triple(groupName, isSystem, false))
+ Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem))
PermGrantState.PERMS_ALLOWED_FOREGROUND_ONLY -> groupGrantStates[
- Category.ALLOWED]!!.add(Triple(groupName, isSystem, true))
+ Category.ALLOWED]!!.add(GroupUiInfo(groupName, isSystem,
+ isForeground = true))
PermGrantState.PERMS_DENIED -> groupGrantStates[Category.DENIED]!!.add(
- Triple(groupName, isSystem, false))
+ GroupUiInfo(groupName, isSystem))
PermGrantState.PERMS_ASK -> groupGrantStates[Category.ASK]!!.add(
- Triple(groupName, isSystem, false))
+ GroupUiInfo(groupName, isSystem))
}
}
}
@@ -138,6 +140,28 @@
}
}
+ data class GroupUiInfo(
+ val groupName: String,
+ val isSystem: Boolean = false,
+ val isForeground: Boolean = false
+ )
+
+ val autoRevokeLiveData = AutoRevokeStateLiveData[packageName, user]
+
+ fun setAutoRevoke(enabled: Boolean) {
+ GlobalScope.launch(IO) {
+ val aom = PermissionControllerApplication.get()
+ .getSystemService(AppOpsManager::class.java)!!
+ val packageInfo = LightPackageInfoLiveData[packageName, user].getInitializedValue()
+ val mode = if (enabled) {
+ MODE_ALLOWED
+ } else {
+ MODE_IGNORED
+ }
+ aom.setUidMode(OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, packageInfo.uid, mode)
+ }
+ }
+
fun showExtraPerms(fragment: Fragment, args: Bundle) {
fragment.findNavController().navigate(R.id.perm_groups_to_extra, args)
}
@@ -150,18 +174,16 @@
/**
* Factory for an AppPermissionGroupsViewModel
*
- * @param app The current application
* @param packageName The name of the package this viewModel is representing
* @param user The user of the package this viewModel is representing
*/
class AppPermissionGroupsViewModelFactory(
- private val app: Application,
private val packageName: String,
private val user: UserHandle
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
- return AppPermissionGroupsViewModel(app, packageName, user) as T
+ return AppPermissionGroupsViewModel(packageName, user) as T
}
}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
index aa9de50..9d06a90 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/ui/model/AppPermissionViewModel.kt
@@ -118,10 +118,6 @@
private var lightAppPermGroup: LightAppPermGroup? = null
/**
- * A livedata which computes the state of the radio buttons
- */
- val buttonStateLiveData = AppPermButtonStateLiveData()
- /**
* A livedata which determines which detail string, if any, should be shown
*/
val detailResIdLiveData = MutableLiveData<Pair<Int, Int?>>()
@@ -139,7 +135,10 @@
constructor() : this(false, true, false, null)
}
- inner class AppPermButtonStateLiveData
+ /**
+ * A livedata which computes the state of the radio buttons
+ */
+ val buttonStateLiveData = object
: SmartUpdateMediatorLiveData<@JvmSuppressWildcards Map<ButtonType, ButtonState>>() {
private val appPermGroupLiveData = LightAppPermGroupLiveData[packageName, permGroupName,
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
index fd6f7a8..d77e7e9 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/AndroidUtils.kt
@@ -26,7 +26,7 @@
import android.os.UserHandle
/**
- * Gets an [Application] instance form a regular [Context]
+ * Gets an [Application] instance from a regular [Context]
*/
val Context.application: Application get() = when (this) {
is Activity -> application
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
index e7b346a..a1fac05 100644
--- a/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
+++ b/PermissionController/src/com/android/permissioncontroller/permission/utils/KotlinUtils.kt
@@ -257,7 +257,12 @@
*
* @return The package's icon, or null, if the package does not exist
*/
- fun getBadgedPackageIcon(app: Application, packageName: String, user: UserHandle): Drawable? {
+ @JvmOverloads
+ fun getBadgedPackageIcon(
+ app: Application,
+ packageName: String,
+ user: UserHandle
+ ): Drawable? {
return try {
val userContext = Utils.getUserContext(app, user)
val appInfo = userContext.packageManager.getApplicationInfo(packageName, 0)
@@ -444,6 +449,7 @@
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ newFlags = newFlags.clearFlag(PackageManager.FLAG_PERMISSION_AUTO_REVOKED)
// If we newly grant background access to the fine location, double-guess the user some
// time later if this was really the right choice.
@@ -453,9 +459,9 @@
val bgPerm = group.permissions[perm.backgroundPermission]
triggerLocationAccessCheck = bgPerm?.isGrantedIncludingAppOp == true
} else if (perm.name == ACCESS_BACKGROUND_LOCATION) {
- val fgPerm = group.permissions[ACCESS_FINE_LOCATION]
- triggerLocationAccessCheck = fgPerm?.isGrantedIncludingAppOp == true
- }
+ val fgPerm = group.permissions[ACCESS_FINE_LOCATION]
+ triggerLocationAccessCheck = fgPerm?.isGrantedIncludingAppOp == true
+ }
if (triggerLocationAccessCheck) {
// trigger location access check
LocationAccessCheck(app, null).checkLocationAccessSoon()
@@ -544,7 +550,7 @@
val isBackgroundPerm = permName in group.backgroundPermNames
if (isBackgroundPerm == revokeBackground) {
val (newPerm, shouldKill) =
- revokeRuntimePermission(app, perm, userFixed, oneTime, group)
+ revokeRuntimePermission(app, perm, userFixed, oneTime, group)
newPerms[newPerm.name] = newPerm
shouldKillForAnyPermission = shouldKillForAnyPermission || shouldKill
}
@@ -616,10 +622,10 @@
// Update the permission flags.
// Take a note that the user fixed the permission, if applicable.
newFlags = if (userFixed) newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
- else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
+ else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_USER_FIXED)
newFlags = newFlags.setFlag(PackageManager.FLAG_PERMISSION_USER_SET)
newFlags = if (oneTime) newFlags.setFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
- else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
+ else newFlags.clearFlag(PackageManager.FLAG_PERMISSION_ONE_TIME)
if (perm.flags != newFlags) {
val flagMask = PackageManager.FLAG_PERMISSION_USER_SET or