Resurrect the ReviewOngoingPermissionsFragment
Resurrect the fragment, its data model, and allow the
PermissionController to handle its intent
Test: Manual
Bug: 159998363
Change-Id: I9a95528e333982e4f80a8c8805bedb58d6f77bf6
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f8856ae..ccf18b8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -122,6 +122,7 @@
<action android:name="android.intent.action.MANAGE_APP_PERMISSION" />
<action android:name="android.intent.action.MANAGE_PERMISSION_APPS" />
<action android:name="android.intent.action.MANAGE_PERMISSIONS" />
+ <action android:name="android.intent.action.REVIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -132,6 +133,7 @@
android:theme="@android:style/Theme.NoDisplay">
<intent-filter android:priority="1">
<action android:name="com.android.permissioncontroller.settingssearch.action.MANAGE_PERMISSION_APPS" />
+ <action android:name="com.android.permissioncontroller.settingssearch.action.REVIEW_PERMISSION_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
@@ -160,6 +162,17 @@
android:excludeFromRecents="true"
android:theme="@style/PermissionDialog.FilterTouches" />
+ <activity android:name="com.android.permissioncontroller.permission.debug.ReviewOngoingUsageActivity"
+ android:excludeFromRecents="true"
+ android:theme="@style/PermissionDialog"
+ android:launchMode="singleInstance"
+ android:permission="android.permission.GRANT_RUNTIME_PERMISSIONS" >
+ <intent-filter android:priority="1">
+ <action android:name="android.intent.action.REVIEW_ONGOING_PERMISSION_USAGE" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
+
<activity android:name="com.android.permissioncontroller.permission.ui.ReviewAccessibilityServicesActivity"
android:excludeFromRecents="true"
android:theme="@style/PermissionDialog.FilterTouches"
diff --git a/res/layout/ongoing_usage_dialog_content.xml b/res/layout/ongoing_usage_dialog_content.xml
new file mode 100644
index 0000000..219b4f9
--- /dev/null
+++ b/res/layout/ongoing_usage_dialog_content.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2019 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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/PermissionUsageDialogContainerScrollView">
+
+ <LinearLayout
+ android:id="@+id/dialog_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ style="@style/PermissionUsageDialogContainerLayout">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/PermissionUsageDialogTitle"/>
+
+ <LinearLayout
+ android:id="@+id/items_container"
+ style="@style/PermissionUsageDialogItemsContainer"/>
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/res/layout/ongoing_usage_dialog_item.xml b/res/layout/ongoing_usage_dialog_item.xml
new file mode 100644
index 0000000..37ab9b4
--- /dev/null
+++ b/res/layout/ongoing_usage_dialog_item.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ style="@style/PermissionUsageDialogItemContainer">
+
+ <FrameLayout
+ style="@style/PermissionUsageDialogItemIconFrame">
+
+ <ImageView
+ android:id="@+id/app_icon"
+ style="@style/PermissionUsageDialogItemIcon"/>
+
+ </FrameLayout>
+
+ <TextView
+ android:id="@+id/app_name"
+ style="@style/PermissionUsageDialogItemAppName"/>
+
+ <LinearLayout
+ android:id="@+id/icons"
+ style="@style/PermissionUsageDialogItemIconsContainer"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9180ca9..a0ad216 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -740,6 +740,21 @@
<!-- Label for the button to set an application as the default application [CHAR LIMIT=20] -->
<string name="request_role_set_as_default">Set as default</string>
+ <!-- Action for accepting the Ongoing usage dialog [CHAR LIMIT=10]-->
+ <string name="ongoing_usage_dialog_ok">Got it</string>
+
+ <!-- Action on Ongoing usage dialog to open the Privacy settings. [CHAR LIMIT=23]-->
+ <string name="ongoing_usage_dialog_open_settings">Privacy settings</string>
+
+ <!-- Title for Ongoing usage dialog title when multiple apps are using app ops [CHAR LIMIT=NONE] -->
+ <string name="ongoing_usage_dialog_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string>
+
+ <!-- Separator for permissions. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_usage_dialog_separator">,\u0020</string>
+
+ <!-- Separator for permissions, before last type. Include spaces before and after if needed [CHAR LIMIT=10] -->
+ <string name="ongoing_usage_dialog_last_separator">\u0020and\u0020</string>
+
<!-- Keyword in the Settings app's search functionality that can be used to find links to the default app management screens [CHAR LIMIT=none] -->
<string name="default_app_search_keyword">default apps</string>
diff --git a/src/com/android/permissioncontroller/permission/debug/AppPermissionUsage.java b/src/com/android/permissioncontroller/permission/debug/AppPermissionUsage.java
new file mode 100644
index 0000000..e168ee8
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/AppPermissionUsage.java
@@ -0,0 +1,259 @@
+/*
+ * 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.debug;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.permission.model.AppPermissionGroup;
+import com.android.permissioncontroller.permission.model.Permission;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Stats for permission usage of an app. This data is for a given time period,
+ * i.e. does not contain the full history.
+ */
+public final class AppPermissionUsage {
+ private final @NonNull List<GroupUsage> mGroupUsages = new ArrayList<>();
+ private final @NonNull PermissionApp mPermissionApp;
+
+ private AppPermissionUsage(@NonNull PermissionApp permissionApp,
+ @NonNull List<AppPermissionGroup> groups, @Nullable PackageOps lastUsage,
+ @Nullable HistoricalPackageOps historicalUsage) {
+ mPermissionApp = permissionApp;
+ final int groupCount = groups.size();
+ for (int i = 0; i < groupCount; i++) {
+ final AppPermissionGroup group = groups.get(i);
+ mGroupUsages.add(new GroupUsage(group, lastUsage, historicalUsage));
+ }
+ }
+
+ public @NonNull PermissionApp getApp() {
+ return mPermissionApp;
+ }
+
+ public @NonNull String getPackageName() {
+ return mPermissionApp.getPackageName();
+ }
+
+ public int getUid() {
+ return mPermissionApp.getUid();
+ }
+
+ public long getLastAccessTime() {
+ long lastAccessTime = 0;
+ final int permissionCount = mGroupUsages.size();
+ for (int i = 0; i < permissionCount; i++) {
+ final GroupUsage groupUsage = mGroupUsages.get(i);
+ lastAccessTime = Math.max(lastAccessTime, groupUsage.getLastAccessTime());
+ }
+ return lastAccessTime;
+ }
+
+ public long getAccessCount() {
+ long accessCount = 0;
+ final int permissionCount = mGroupUsages.size();
+ for (int i = 0; i < permissionCount; i++) {
+ final GroupUsage permission = mGroupUsages.get(i);
+ accessCount += permission.getAccessCount();
+ }
+ return accessCount;
+ }
+
+ public @NonNull List<GroupUsage> getGroupUsages() {
+ return mGroupUsages;
+ }
+
+ /**
+ * Stats for permission usage of a permission group. This data is for a
+ * given time period, i.e. does not contain the full history.
+ */
+ public static class GroupUsage {
+ private final @NonNull AppPermissionGroup mGroup;
+ private final @Nullable PackageOps mLastUsage;
+ private final @Nullable HistoricalPackageOps mHistoricalUsage;
+
+ GroupUsage(@NonNull AppPermissionGroup group, @Nullable PackageOps lastUsage,
+ @Nullable HistoricalPackageOps historicalUsage) {
+ mGroup = group;
+ mLastUsage = lastUsage;
+ mHistoricalUsage = historicalUsage;
+ }
+
+ public long getLastAccessTime() {
+ if (mLastUsage == null) {
+ return 0;
+ }
+ return lastAccessAggregate(
+ (op) -> op.getLastAccessTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+
+ public long getLastAccessForegroundTime() {
+ if (mLastUsage == null) {
+ return 0;
+ }
+ return lastAccessAggregate(
+ (op) -> op.getLastAccessForegroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+
+ public long getLastAccessBackgroundTime() {
+ if (mLastUsage == null) {
+ return 0;
+ }
+ return lastAccessAggregate(
+ (op) -> op.getLastAccessBackgroundTime(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+
+ public long getForegroundAccessCount() {
+ if (mHistoricalUsage == null) {
+ return 0;
+ }
+ return extractAggregate((HistoricalOp op)
+ -> op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+
+ public long getBackgroundAccessCount() {
+ if (mHistoricalUsage == null) {
+ return 0;
+ }
+ return extractAggregate((HistoricalOp op)
+ -> op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED));
+ }
+
+ public long getAccessCount() {
+ if (mHistoricalUsage == null) {
+ return 0;
+ }
+ return extractAggregate((HistoricalOp op) ->
+ op.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+ + op.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+ );
+ }
+
+ public long getAccessDuration() {
+ if (mHistoricalUsage == null) {
+ return 0;
+ }
+ return extractAggregate((HistoricalOp op) ->
+ op.getForegroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+ + op.getBackgroundAccessDuration(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+ );
+ }
+
+ public boolean isRunning() {
+ if (mLastUsage == null) {
+ return false;
+ }
+ final ArrayList<Permission> permissions = mGroup.getPermissions();
+ final int permissionCount = permissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ final Permission permission = permissions.get(i);
+ final String opName = permission.getAppOp();
+ final List<OpEntry> ops = mLastUsage.getOps();
+ final int opCount = ops.size();
+ for (int j = 0; j < opCount; j++) {
+ final OpEntry op = ops.get(j);
+ if (op.getOpStr().equals(opName) && op.isRunning()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private long extractAggregate(@NonNull Function<HistoricalOp, Long> extractor) {
+ long aggregate = 0;
+ final ArrayList<Permission> permissions = mGroup.getPermissions();
+ final int permissionCount = permissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ final Permission permission = permissions.get(i);
+ final String opName = permission.getAppOp();
+ final HistoricalOp historicalOp = mHistoricalUsage.getOp(opName);
+ if (historicalOp != null) {
+ aggregate += extractor.apply(historicalOp);
+ }
+ }
+ return aggregate;
+ }
+
+ private long lastAccessAggregate(@NonNull Function<OpEntry, Long> extractor) {
+ long aggregate = 0;
+ final ArrayList<Permission> permissions = mGroup.getPermissions();
+ final int permissionCount = permissions.size();
+ for (int permissionNum = 0; permissionNum < permissionCount; permissionNum++) {
+ final Permission permission = permissions.get(permissionNum);
+ final String opName = permission.getAppOp();
+ final List<OpEntry> ops = mLastUsage.getOps();
+ final int opCount = ops.size();
+ for (int opNum = 0; opNum < opCount; opNum++) {
+ final OpEntry op = ops.get(opNum);
+ if (op.getOpStr().equals(opName)) {
+ aggregate = Math.max(aggregate, extractor.apply(op));
+ }
+ }
+ }
+ return aggregate;
+ }
+
+ public @NonNull AppPermissionGroup getGroup() {
+ return mGroup;
+ }
+ }
+
+ public static class Builder {
+ private final @NonNull List<AppPermissionGroup> mGroups = new ArrayList<>();
+ private final @NonNull PermissionApp mPermissionApp;
+ private @Nullable PackageOps mLastUsage;
+ private @Nullable HistoricalPackageOps mHistoricalUsage;
+
+ public Builder(@NonNull PermissionApp permissionApp) {
+ mPermissionApp = permissionApp;
+ }
+
+ public @NonNull Builder addGroup(@NonNull AppPermissionGroup group) {
+ mGroups.add(group);
+ return this;
+ }
+
+ public @NonNull Builder setLastUsage(@Nullable PackageOps lastUsage) {
+ mLastUsage = lastUsage;
+ return this;
+ }
+
+ public @NonNull Builder setHistoricalUsage(@Nullable HistoricalPackageOps historicalUsage) {
+ mHistoricalUsage = historicalUsage;
+ return this;
+ }
+
+ public @NonNull AppPermissionUsage build() {
+ if (mGroups.isEmpty()) {
+ throw new IllegalStateException("mGroups cannot be empty.");
+ }
+ return new AppPermissionUsage(mPermissionApp, mGroups, mLastUsage, mHistoricalUsage);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java b/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
new file mode 100644
index 0000000..d45164e
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/PermissionUsages.java
@@ -0,0 +1,342 @@
+/*
+ * 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.debug;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.AppOpsManager.PackageOps;
+import android.app.LoaderManager;
+import android.app.LoaderManager.LoaderCallbacks;
+import android.content.AsyncTaskLoader;
+import android.content.Context;
+import android.content.Loader;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.permissioncontroller.permission.debug.AppPermissionUsage.Builder;
+import com.android.permissioncontroller.permission.model.AppPermissionGroup;
+import com.android.permissioncontroller.permission.model.Permission;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+import com.android.permissioncontroller.permission.model.legacy.PermissionGroup;
+import com.android.permissioncontroller.permission.model.legacy.PermissionGroups;
+import com.android.permissioncontroller.permission.utils.Utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Loads all permission usages for a set of apps and permission groups.
+ */
+public final class PermissionUsages implements LoaderCallbacks<List<AppPermissionUsage>> {
+ public static final int USAGE_FLAG_LAST = 1 << 0;
+ public static final int USAGE_FLAG_HISTORICAL = 1 << 2;
+
+ private final ArrayList<AppPermissionUsage> mUsages = new ArrayList<>();
+ private final @NonNull Context mContext;
+
+ private static final String KEY_FILTER_UID = "KEY_FILTER_UID";
+ private static final String KEY_FILTER_PACKAGE_NAME = "KEY_FILTER_PACKAGE_NAME";
+ private static final String KEY_FILTER_PERMISSION_GROUP = "KEY_FILTER_PERMISSION_GROUP";
+ private static final String KEY_FILTER_BEGIN_TIME_MILLIS = "KEY_FILTER_BEGIN_TIME_MILLIS";
+ private static final String KEY_FILTER_END_TIME_MILLIS = "KEY_FILTER_END_TIME_MILLIS";
+ private static final String KEY_USAGE_FLAGS = "KEY_USAGE_FLAGS";
+ private static final String KEY_GET_UI_INFO = "KEY_GET_UI_INFO";
+ private static final String KEY_GET_NON_PLATFORM_PERMISSIONS =
+ "KEY_GET_NON_PLATFORM_PERMISSIONS";
+
+ private @Nullable PermissionsUsagesChangeCallback mCallback;
+
+ public interface PermissionsUsagesChangeCallback {
+ void onPermissionUsagesChanged();
+ }
+
+ public PermissionUsages(@NonNull Context context) {
+ mContext = context;
+ }
+
+ public void load(@Nullable String filterPackageName,
+ @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
+ long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
+ boolean getUiInfo, boolean getNonPlatformPermissions,
+ @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
+ load(Process.INVALID_UID, filterPackageName, filterPermissionGroups, filterBeginTimeMillis,
+ filterEndTimeMillis, usageFlags, loaderManager, getUiInfo,
+ getNonPlatformPermissions, callback, sync);
+ }
+
+ public void load(int filterUid, @Nullable String filterPackageName,
+ @Nullable String[] filterPermissionGroups, long filterBeginTimeMillis,
+ long filterEndTimeMillis, int usageFlags, @NonNull LoaderManager loaderManager,
+ boolean getUiInfo, boolean getNonPlatformPermissions,
+ @NonNull PermissionsUsagesChangeCallback callback, boolean sync) {
+ mCallback = callback;
+ final Bundle args = new Bundle();
+ args.putInt(KEY_FILTER_UID, filterUid);
+ args.putString(KEY_FILTER_PACKAGE_NAME, filterPackageName);
+ args.putStringArray(KEY_FILTER_PERMISSION_GROUP, filterPermissionGroups);
+ args.putLong(KEY_FILTER_BEGIN_TIME_MILLIS, filterBeginTimeMillis);
+ args.putLong(KEY_FILTER_END_TIME_MILLIS, filterEndTimeMillis);
+ args.putInt(KEY_USAGE_FLAGS, usageFlags);
+ args.putBoolean(KEY_GET_UI_INFO, getUiInfo);
+ args.putBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS, getNonPlatformPermissions);
+ if (sync) {
+ final UsageLoader loader = new UsageLoader(mContext, args);
+ final List<AppPermissionUsage> usages = loader.loadInBackground();
+ onLoadFinished(loader, usages);
+ } else {
+ loaderManager.restartLoader(1, args, this);
+ }
+ }
+
+ @Override
+ public Loader<List<AppPermissionUsage>> onCreateLoader(int id, Bundle args) {
+ return new UsageLoader(mContext, args);
+ }
+
+ @Override
+ public void onLoadFinished(@NonNull Loader<List<AppPermissionUsage>> loader,
+ List<AppPermissionUsage> usages) {
+ mUsages.clear();
+ mUsages.addAll(usages);
+ if (mCallback != null) {
+ mCallback.onPermissionUsagesChanged();
+ }
+ }
+
+ @Override
+ public void onLoaderReset(@NonNull Loader<List<AppPermissionUsage>> loader) {
+ mUsages.clear();
+ mCallback.onPermissionUsagesChanged();
+ }
+
+ public @NonNull List<AppPermissionUsage> getUsages() {
+ return mUsages;
+ }
+
+ public void stopLoader(@NonNull LoaderManager loaderManager) {
+ loaderManager.destroyLoader(1);
+ }
+
+ public static @Nullable AppPermissionUsage.GroupUsage loadLastGroupUsage(
+ @NonNull Context context, @NonNull AppPermissionGroup group) {
+ final ArraySet<String> opNames = new ArraySet<>();
+ final List<Permission> permissions = group.getPermissions();
+ final int permCount = permissions.size();
+ for (int i = 0; i < permCount; i++) {
+ final Permission permission = permissions.get(i);
+ final String opName = permission.getAppOp();
+ if (opName != null) {
+ opNames.add(opName);
+ }
+ }
+ final String[] opNamesArray = opNames.toArray(new String[opNames.size()]);
+ final List<PackageOps> usageOps = context.getSystemService(AppOpsManager.class)
+ .getOpsForPackage(group.getApp().applicationInfo.uid,
+ group.getApp().packageName, opNamesArray);
+ if (usageOps == null || usageOps.isEmpty()) {
+ return null;
+ }
+ return new AppPermissionUsage.GroupUsage(group, usageOps.get(0), null);
+ }
+
+ private static final class UsageLoader extends AsyncTaskLoader<List<AppPermissionUsage>> {
+ private final int mFilterUid;
+ private @Nullable String mFilterPackageName;
+ private @Nullable String[] mFilterPermissionGroups;
+ private final long mFilterBeginTimeMillis;
+ private final long mFilterEndTimeMillis;
+ private final int mUsageFlags;
+ private final boolean mGetUiInfo;
+ private final boolean mGetNonPlatformPermissions;
+
+ UsageLoader(@NonNull Context context, @NonNull Bundle args) {
+ super(context);
+ mFilterUid = args.getInt(KEY_FILTER_UID);
+ mFilterPackageName = args.getString(KEY_FILTER_PACKAGE_NAME);
+ mFilterPermissionGroups = args.getStringArray(KEY_FILTER_PERMISSION_GROUP);
+ mFilterBeginTimeMillis = args.getLong(KEY_FILTER_BEGIN_TIME_MILLIS);
+ mFilterEndTimeMillis = args.getLong(KEY_FILTER_END_TIME_MILLIS);
+ mUsageFlags = args.getInt(KEY_USAGE_FLAGS);
+ mGetUiInfo = args.getBoolean(KEY_GET_UI_INFO);
+ mGetNonPlatformPermissions = args.getBoolean(KEY_GET_NON_PLATFORM_PERMISSIONS);
+ }
+
+ @Override
+ protected void onStartLoading() {
+ forceLoad();
+ }
+
+ @Override
+ public @NonNull List<AppPermissionUsage> loadInBackground() {
+ final List<PermissionGroup> groups = PermissionGroups.getPermissionGroups(
+ getContext(), this::isLoadInBackgroundCanceled, mGetUiInfo,
+ mGetNonPlatformPermissions, mFilterPermissionGroups, mFilterPackageName);
+ if (!Utils.isPermissionUsageIconEnabled()) {
+ return Collections.emptyList();
+ }
+
+ if (groups.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final List<AppPermissionUsage> usages = new ArrayList<>();
+ final ArraySet<String> opNames = new ArraySet<>();
+ final ArrayMap<Pair<Integer, String>, AppPermissionUsage.Builder> usageBuilders =
+ new ArrayMap<>();
+
+ final int groupCount = groups.size();
+ for (int groupIdx = 0; groupIdx < groupCount; groupIdx++) {
+ final PermissionGroup group = groups.get(groupIdx);
+ // Filter out third party permissions
+ if (!group.getDeclaringPackage().equals(Utils.OS_PKG)) {
+ continue;
+ }
+
+ groups.add(group);
+
+ final List<PermissionApp> permissionApps = group.getPermissionApps().getApps();
+ final int appCount = permissionApps.size();
+ for (int appIdx = 0; appIdx < appCount; appIdx++) {
+ final PermissionApp permissionApp = permissionApps.get(appIdx);
+ if (mFilterUid != Process.INVALID_UID
+ && permissionApp.getAppInfo().uid != mFilterUid) {
+ continue;
+ }
+
+ final AppPermissionGroup appPermGroup = permissionApp.getPermissionGroup();
+ if (!Utils.shouldShowPermission(getContext(), appPermGroup)) {
+ continue;
+ }
+ final Pair<Integer, String> usageKey = Pair.create(permissionApp.getUid(),
+ permissionApp.getPackageName());
+ AppPermissionUsage.Builder usageBuilder = usageBuilders.get(usageKey);
+ if (usageBuilder == null) {
+ usageBuilder = new Builder(permissionApp);
+ usageBuilders.put(usageKey, usageBuilder);
+ }
+ usageBuilder.addGroup(appPermGroup);
+ final List<Permission> permissions = appPermGroup.getPermissions();
+ final int permCount = permissions.size();
+ for (int permIdx = 0; permIdx < permCount; permIdx++) {
+ final Permission permission = permissions.get(permIdx);
+ final String opName = permission.getAppOp();
+ if (opName != null) {
+ opNames.add(opName);
+ }
+ }
+ }
+ }
+
+ if (usageBuilders.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final AppOpsManager appOpsManager = getContext().getSystemService(AppOpsManager.class);
+
+ // Get last usage data and put in a map for a quick lookup.
+ final ArrayMap<Pair<Integer, String>, PackageOps> lastUsages =
+ new ArrayMap<>(usageBuilders.size());
+ final String[] opNamesArray = opNames.toArray(new String[opNames.size()]);
+ if ((mUsageFlags & USAGE_FLAG_LAST) != 0) {
+ final List<PackageOps> usageOps;
+ if (mFilterPackageName != null || mFilterUid != Process.INVALID_UID) {
+ usageOps = appOpsManager.getOpsForPackage(mFilterUid, mFilterPackageName,
+ opNamesArray);
+ } else {
+ usageOps = appOpsManager.getPackagesForOps(opNamesArray);
+ }
+ if (usageOps != null && !usageOps.isEmpty()) {
+ final int usageOpsCount = usageOps.size();
+ for (int i = 0; i < usageOpsCount; i++) {
+ final PackageOps usageOp = usageOps.get(i);
+ lastUsages.put(Pair.create(usageOp.getUid(), usageOp.getPackageName()),
+ usageOp);
+ }
+ }
+ }
+
+ if (isLoadInBackgroundCanceled()) {
+ return Collections.emptyList();
+ }
+
+ // Get historical usage data and put in a map for a quick lookup
+ final ArrayMap<Pair<Integer, String>, HistoricalPackageOps> historicalUsages =
+ new ArrayMap<>(usageBuilders.size());
+ if ((mUsageFlags & USAGE_FLAG_HISTORICAL) != 0) {
+ final AtomicReference<HistoricalOps> historicalOpsRef = new AtomicReference<>();
+ final CountDownLatch latch = new CountDownLatch(1);
+ final HistoricalOpsRequest request = new HistoricalOpsRequest.Builder(
+ mFilterBeginTimeMillis, mFilterEndTimeMillis)
+ .setUid(mFilterUid)
+ .setPackageName(mFilterPackageName)
+ .setOpNames(new ArrayList<>(opNames))
+ .setFlags(AppOpsManager.OP_FLAGS_ALL_TRUSTED)
+ .build();
+ appOpsManager.getHistoricalOps(request, Runnable::run,
+ (HistoricalOps ops) -> {
+ historicalOpsRef.set(ops);
+ latch.countDown();
+ });
+ try {
+ latch.await(5, TimeUnit.DAYS);
+ } catch (InterruptedException ignored) { }
+
+ final HistoricalOps historicalOps = historicalOpsRef.get();
+ if (historicalOps != null) {
+ final int uidCount = historicalOps.getUidCount();
+ for (int i = 0; i < uidCount; i++) {
+ final HistoricalUidOps uidOps = historicalOps.getUidOpsAt(i);
+ final int packageCount = uidOps.getPackageCount();
+ for (int j = 0; j < packageCount; j++) {
+ final HistoricalPackageOps packageOps = uidOps.getPackageOpsAt(j);
+ historicalUsages.put(
+ Pair.create(uidOps.getUid(), packageOps.getPackageName()),
+ packageOps);
+ }
+ }
+ }
+ }
+
+ // Construct the historical usages based on data we fetched
+ final int builderCount = usageBuilders.size();
+ for (int i = 0; i < builderCount; i++) {
+ final Pair<Integer, String> key = usageBuilders.keyAt(i);
+ final Builder usageBuilder = usageBuilders.valueAt(i);
+ final PackageOps lastUsage = lastUsages.get(key);
+ usageBuilder.setLastUsage(lastUsage);
+ final HistoricalPackageOps historicalUsage = historicalUsages.get(key);
+ usageBuilder.setHistoricalUsage(historicalUsage);
+ usages.add(usageBuilder.build());
+ }
+
+ return usages;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageActivity.java b/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageActivity.java
new file mode 100644
index 0000000..c48b10f
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageActivity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.debug;
+
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.permissioncontroller.DeviceUtils;
+
+/**
+ * A dialog listing the currently uses of camera, microphone, and location.
+ */
+public final class ReviewOngoingUsageActivity extends FragmentActivity {
+
+ // Number of milliseconds in the past to look for accesses if nothing was specified.
+ private static final long DEFAULT_MILLIS = 5000;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+ long numMillis = getIntent().getLongExtra(Intent.EXTRA_DURATION_MILLIS, DEFAULT_MILLIS);
+ getSupportFragmentManager().beginTransaction().replace(android.R.id.content,
+ ReviewOngoingUsageFragment.newInstance(numMillis)).commit();
+ }
+
+
+ @Override
+ public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ // in automotive mode, there's no system wide back button, so need to add that
+ if (DeviceUtils.isAuto(this)) {
+ onBackPressed();
+ } else {
+ finish();
+ }
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageFragment.java b/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageFragment.java
new file mode 100644
index 0000000..0174ec4
--- /dev/null
+++ b/src/com/android/permissioncontroller/permission/debug/ReviewOngoingUsageFragment.java
@@ -0,0 +1,249 @@
+/*
+ * 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.debug;
+
+import static android.Manifest.permission_group.CAMERA;
+import static android.Manifest.permission_group.LOCATION;
+import static android.Manifest.permission_group.MICROPHONE;
+
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_DISMISS;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_LINE_ITEM;
+import static com.android.permissioncontroller.PermissionControllerStatsLog.PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_PRIVACY_SETTINGS;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.permissioncontroller.PermissionControllerStatsLog;
+import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.permission.debug.AppPermissionUsage.GroupUsage;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps;
+import com.android.permissioncontroller.permission.model.legacy.PermissionApps.PermissionApp;
+import com.android.permissioncontroller.permission.utils.Utils;
+
+import java.text.Collator;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A dialog listing the currently uses of camera, microphone, and location.
+ */
+public class ReviewOngoingUsageFragment extends PreferenceFragmentCompat {
+
+ private @NonNull PermissionUsages mPermissionUsages;
+ private @Nullable AlertDialog mDialog;
+ private long mStartTime;
+
+ /**
+ * @return A new {@link ReviewOngoingUsageFragment}
+ */
+ public static ReviewOngoingUsageFragment newInstance(long numMillis) {
+ ReviewOngoingUsageFragment fragment = new ReviewOngoingUsageFragment();
+ Bundle arguments = new Bundle();
+ arguments.putLong(Intent.EXTRA_DURATION_MILLIS, numMillis);
+ fragment.setArguments(arguments);
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (!Utils.isPermissionUsageIconEnabled()) {
+ getActivity().finish();
+ return;
+ }
+
+ long numMillis = getArguments().getLong(Intent.EXTRA_DURATION_MILLIS);
+
+ mPermissionUsages = new PermissionUsages(getActivity());
+ mStartTime = Math.max(System.currentTimeMillis() - numMillis, Instant.EPOCH.toEpochMilli());
+ mPermissionUsages.load(null, new String[]{CAMERA, LOCATION, MICROPHONE}, mStartTime,
+ Long.MAX_VALUE, PermissionUsages.USAGE_FLAG_LAST, getActivity().getLoaderManager(),
+ false, false, this::onPermissionUsagesLoaded, false);
+ }
+
+ private void onPermissionUsagesLoaded() {
+ if (getActivity() == null) {
+ return;
+ }
+
+ List<AppPermissionUsage> appPermissionUsages = mPermissionUsages.getUsages();
+
+ List<Pair<AppPermissionUsage, List<GroupUsage>>> usages = new ArrayList<>();
+ ArrayList<PermissionApp> permApps = new ArrayList<>();
+ int numApps = appPermissionUsages.size();
+ for (int appNum = 0; appNum < numApps; appNum++) {
+ AppPermissionUsage appUsage = appPermissionUsages.get(appNum);
+
+ List<GroupUsage> usedGroups = new ArrayList<>();
+ List<GroupUsage> appGroups = appUsage.getGroupUsages();
+ int numGroups = appGroups.size();
+ for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+ GroupUsage groupUsage = appGroups.get(groupNum);
+ String groupName = groupUsage.getGroup().getName();
+
+ if (groupUsage.getLastAccessTime() < mStartTime && !groupUsage.isRunning()) {
+ continue;
+ }
+ if (!Utils.isGroupOrBgGroupUserSensitive(groupUsage.getGroup())) {
+ continue;
+ }
+
+ usedGroups.add(appGroups.get(groupNum));
+ }
+
+ if (!usedGroups.isEmpty()) {
+ usages.add(Pair.create(appUsage, usedGroups));
+ permApps.add(appUsage.getApp());
+ }
+ }
+
+ if (usages.isEmpty()) {
+ getActivity().finish();
+ return;
+ }
+
+ new PermissionApps.AppDataLoader(getActivity(), () -> showDialog(usages))
+ .execute(permApps.toArray(new PermissionApps.PermissionApp[permApps.size()]));
+ }
+
+ private void showDialog(@NonNull List<Pair<AppPermissionUsage, List<GroupUsage>>> usages) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
+ .setView(createDialogView(usages))
+ .setPositiveButton(R.string.ongoing_usage_dialog_ok, (dialog, which) ->
+ PermissionControllerStatsLog.write(PRIVACY_INDICATORS_INTERACTED,
+ PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_DISMISS, null))
+ .setOnDismissListener((dialog) -> getActivity().finish());
+ setNeutralButton(builder);
+ mDialog = builder.create();
+ mDialog.show();
+ }
+
+ protected void setNeutralButton(AlertDialog.Builder builder) {
+ builder.setNeutralButton(R.string.ongoing_usage_dialog_open_settings, (dialog, which) -> {
+ PermissionControllerStatsLog.write(PRIVACY_INDICATORS_INTERACTED,
+ PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_PRIVACY_SETTINGS, null);
+ startActivity(new Intent(Settings.ACTION_PRIVACY_SETTINGS).putExtra(
+ Intent.EXTRA_DURATION_MILLIS, TimeUnit.MINUTES.toMillis(1)));
+ });
+ }
+
+ private @NonNull View createDialogView(
+ @NonNull List<Pair<AppPermissionUsage, List<GroupUsage>>> usages) {
+ Context context = getActivity();
+ LayoutInflater inflater = LayoutInflater.from(context);
+ View contentView = inflater.inflate(R.layout.ongoing_usage_dialog_content, null);
+ ViewGroup appsList = contentView.requireViewById(R.id.items_container);
+
+ // Compute all of the permission group labels that were used.
+ ArraySet<String> usedGroups = new ArraySet<>();
+ int numUsages = usages.size();
+ for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+ List<GroupUsage> groups = usages.get(usageNum).second;
+ int numGroups = groups.size();
+ for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+ usedGroups.add(groups.get(groupNum).getGroup().getLabel().toString().toLowerCase());
+ }
+ }
+
+ // Add the layout for each app.
+ for (int usageNum = 0; usageNum < numUsages; usageNum++) {
+ Pair<AppPermissionUsage, List<GroupUsage>> usage = usages.get(usageNum);
+ PermissionApp app = usage.first.getApp();
+ List<GroupUsage> groups = usage.second;
+
+ View itemView = inflater.inflate(R.layout.ongoing_usage_dialog_item, appsList, false);
+
+ ((TextView) itemView.requireViewById(R.id.app_name)).setText(app.getLabel());
+ ((ImageView) itemView.requireViewById(R.id.app_icon)).setImageDrawable(app.getIcon());
+
+ // Add the icons for the groups this app used as long as multiple groups were used by
+ // some app.
+ if (usedGroups.size() > 1) {
+ ViewGroup iconFrame = itemView.requireViewById(R.id.icons);
+ int numGroups = usages.get(usageNum).second.size();
+ for (int groupNum = 0; groupNum < numGroups; groupNum++) {
+ ViewGroup group = (ViewGroup) inflater.inflate(R.layout.image_view, null);
+ ((ImageView) group.requireViewById(R.id.icon)).setImageDrawable(
+ Utils.applyTint(context, groups.get(groupNum).getGroup().getIconResId(),
+ android.R.attr.colorControlNormal));
+ iconFrame.addView(group);
+ }
+ iconFrame.setVisibility(View.VISIBLE);
+ }
+
+ itemView.setOnClickListener((v) -> {
+ String packageName = app.getPackageName();
+ PermissionControllerStatsLog.write(PRIVACY_INDICATORS_INTERACTED,
+ PRIVACY_INDICATORS_INTERACTED__TYPE__DIALOG_LINE_ITEM, packageName);
+ UserHandle user = UserHandle.getUserHandleForUid(app.getUid());
+ Intent intent = new Intent(Intent.ACTION_MANAGE_APP_PERMISSIONS);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+ intent.putExtra(Intent.EXTRA_USER, user);
+ context.startActivity(intent);
+ mDialog.dismiss();
+ });
+
+ appsList.addView(itemView);
+ }
+
+ // Set the title of the dialog based on all of the permissions used.
+ StringBuilder titleBuilder = new StringBuilder();
+ int numGroups = usedGroups.size();
+ List<String> sortedGroups = new ArrayList<>(usedGroups);
+ Collator collator = Collator.getInstance(
+ getResources().getConfiguration().getLocales().get(0));
+ sortedGroups.sort(collator);
+ for (int i = 0; i < numGroups; i++) {
+ titleBuilder.append(sortedGroups.get(i));
+ if (i < numGroups - 2) {
+ titleBuilder.append(getString(R.string.ongoing_usage_dialog_separator));
+ } else if (i < numGroups - 1) {
+ titleBuilder.append(getString(R.string.ongoing_usage_dialog_last_separator));
+ }
+ }
+
+ ((TextView) contentView.requireViewById(R.id.title)).setText(
+ getString(R.string.ongoing_usage_dialog_title, titleBuilder.toString()));
+
+ return contentView;
+ }
+
+ @Override
+ public void onCreatePreferences(Bundle bundle, String s) {
+ // empty
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java b/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
index e837ade..e493e08 100644
--- a/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
+++ b/src/com/android/permissioncontroller/permission/model/legacy/PermissionApps.java
@@ -421,6 +421,19 @@
return mAppPermissionGroup;
}
+ /**
+ * Load this app's label and icon if they were not previously loaded.
+ *
+ * @param appDataCache the cache of already-loaded labels and icons.
+ */
+ public void loadLabelAndIcon(@NonNull AppDataCache appDataCache) {
+ if (mInfo.packageName.equals(mLabel) || mIcon == null) {
+ Pair<String, Drawable> appData = appDataCache.getAppData(getUid(), mInfo);
+ mLabel = appData.first;
+ mIcon = appData.second;
+ }
+ }
+
@Override
public int compareTo(PermissionApp another) {
final int result = mLabel.compareTo(another.mLabel);
@@ -520,4 +533,33 @@
public interface Callback {
void onPermissionsLoaded(PermissionApps permissionApps);
}
+
+ /**
+ * Class used to asynchronously load apps' labels and icons.
+ */
+ public static class AppDataLoader extends AsyncTask<PermissionApp, Void, Void> {
+
+ private final Context mContext;
+ private final Runnable mCallback;
+
+ public AppDataLoader(Context context, Runnable callback) {
+ mContext = context;
+ mCallback = callback;
+ }
+
+ @Override
+ protected Void doInBackground(PermissionApp... args) {
+ AppDataCache appDataCache = new AppDataCache(mContext.getPackageManager(), mContext);
+ int numArgs = args.length;
+ for (int i = 0; i < numArgs; i++) {
+ args[i].loadLabelAndIcon(appDataCache);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ mCallback.run();
+ }
+ }
}
diff --git a/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java b/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
index da1039d..6b951c9 100644
--- a/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
+++ b/src/com/android/permissioncontroller/permission/model/legacy/PermissionGroup.java
@@ -78,6 +78,13 @@
return mGranted;
}
+ /**
+ * @return The PermissionApps object for this permission group.
+ */
+ public PermissionApps getPermissionApps() {
+ return mPermApps;
+ }
+
@Override
public int compareTo(PermissionGroup another) {
return mLabel.toString().compareTo(another.mLabel.toString());
diff --git a/src/com/android/permissioncontroller/permission/utils/Utils.java b/src/com/android/permissioncontroller/permission/utils/Utils.java
index 65fdd59..01123ba 100644
--- a/src/com/android/permissioncontroller/permission/utils/Utils.java
+++ b/src/com/android/permissioncontroller/permission/utils/Utils.java
@@ -1141,4 +1141,15 @@
return false;
}
}
+
+ /**
+ * Whether the Permissions Hub is enabled.
+ *
+ * @return whether the Permissions Hub is enabled.
+ */
+ public static boolean isPermissionUsageIconEnabled() {
+ return true;
+ //return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ // PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+ }
}