/**
* Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2018 The LineageOS 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
package com.android.settings.applications;
+import android.app.Activity;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
+import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
public class AppOpsState {
static final String TAG = "AppOpsState";
List<AppOpEntry> mApps;
+ private SharedPreferences mPreferences;
+
public AppOpsState(Context context) {
mContext = context;
mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
mPm = context.getPackageManager();
- mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries);
- mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels);
+ mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries_cm);
+ mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels_cm);
+ mPreferences = context.getSharedPreferences("appops_manager", Activity.MODE_PRIVATE);
}
public static class OpsTemplate implements Parcelable {
AppOpsManager.OP_AUDIO_ALARM_VOLUME,
AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME,
AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME,
- AppOpsManager.OP_MUTE_MICROPHONE},
+ AppOpsManager.OP_MUTE_MICROPHONE },
new boolean[] { false,
true,
true,
AppOpsManager.OP_PROJECT_MEDIA,
AppOpsManager.OP_ACTIVATE_VPN,
AppOpsManager.OP_ASSIST_STRUCTURE,
- AppOpsManager.OP_ASSIST_SCREENSHOT},
+ AppOpsManager.OP_ASSIST_SCREENSHOT,
+ AppOpsManager.OP_CHANGE_WIFI_STATE,
+ AppOpsManager.OP_BLUETOOTH_CHANGE,
+ AppOpsManager.OP_NFC_CHANGE,
+ AppOpsManager.OP_DATA_CONNECT_CHANGE },
new boolean[] { false,
true,
true,
false,
false,
false,
- false }
+ false,
+ true,
+ true,
+ true,
+ true }
);
public static final OpsTemplate RUN_IN_BACKGROUND_TEMPLATE = new OpsTemplate(
new boolean[] { false }
);
+ public static final OpsTemplate BOOTUP_TEMPLATE = new OpsTemplate(
+ new int[] { AppOpsManager.OP_BOOT_COMPLETED },
+ new boolean[] { true }
+ );
+
+ public static final OpsTemplate SU_TEMPLATE = new OpsTemplate(
+ new int[] { AppOpsManager.OP_SU },
+ new boolean[] { false }
+ );
+
+ // this template should contain all ops which are not part of any other template in
+ // ALL_TEMPLATES
+ public static final OpsTemplate REMAINING_TEMPLATE = new OpsTemplate(
+ new int[] { AppOpsManager.OP_GET_USAGE_STATS,
+ AppOpsManager.OP_TOAST_WINDOW,
+ AppOpsManager.OP_WRITE_WALLPAPER,
+ AppOpsManager.OP_READ_PHONE_STATE,
+ AppOpsManager.OP_ADD_VOICEMAIL,
+ AppOpsManager.OP_USE_SIP,
+ AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
+ AppOpsManager.OP_USE_FINGERPRINT,
+ AppOpsManager.OP_BODY_SENSORS,
+ AppOpsManager.OP_READ_CELL_BROADCASTS,
+ AppOpsManager.OP_MOCK_LOCATION,
+ AppOpsManager.OP_READ_EXTERNAL_STORAGE,
+ AppOpsManager.OP_WRITE_EXTERNAL_STORAGE,
+ AppOpsManager.OP_TURN_SCREEN_ON,
+ AppOpsManager.OP_GET_ACCOUNTS },
+ new boolean[] { true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true,
+ true }
+ );
+
public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] {
LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE,
- MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE
+ MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE,
+ BOOTUP_TEMPLATE, SU_TEMPLATE
+ };
+
+ // this template contains all permissions grouped by templates
+ public static final OpsTemplate[] ALL_PERMS_TEMPLATES = new OpsTemplate[] {
+ LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE,
+ MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE,
+ BOOTUP_TEMPLATE, SU_TEMPLATE, REMAINING_TEMPLATE
};
/**
}
private CharSequence getCombinedText(ArrayList<AppOpsManager.OpEntry> ops,
- CharSequence[] items) {
- if (ops.size() == 1) {
- return items[ops.get(0).getOp()];
- } else {
- StringBuilder builder = new StringBuilder();
- for (int i=0; i<ops.size(); i++) {
- if (i > 0) {
- builder.append(", ");
- }
- builder.append(items[ops.get(i).getOp()]);
+ CharSequence[] items, Resources res, boolean withTerseCounts) {
+ StringBuilder builder = new StringBuilder();
+ for (int i=0; i<ops.size(); i++) {
+ if (i > 0) {
+ builder.append(", ");
+ }
+ AppOpsManager.OpEntry op = ops.get(i);
+ int count = op.getAllowedCount() + op.getIgnoredCount();
+
+ if (withTerseCounts && count > 0) {
+ String quantity = res.getQuantityString(R.plurals.app_ops_count,
+ count, count);
+ builder.append(res.getString(R.string.app_ops_entry_summary,
+ items[op.getOp()], quantity));
+ } else {
+ builder.append(items[op.getOp()]);
}
- return builder.toString();
}
+ return builder.toString();
+ }
+
+ public CharSequence getCountsText(Resources res) {
+ AppOpsManager.OpEntry op = mOps.get(0);
+ int allowed = op.getAllowedCount();
+ int denied = op.getIgnoredCount();
+
+ if (allowed == 0 && denied == 0) {
+ return null;
+ }
+
+ CharSequence allowedQuantity = res.getQuantityString(R.plurals.app_ops_count,
+ allowed, allowed);
+ CharSequence deniedQuantity = res.getQuantityString(R.plurals.app_ops_count,
+ denied, denied);
+
+ if (denied == 0) {
+ return res.getString(R.string.app_ops_allowed_count, allowedQuantity);
+ } else if (allowed == 0) {
+ return res.getString(R.string.app_ops_ignored_count, deniedQuantity);
+ }
+ return res.getString(R.string.app_ops_both_count, allowedQuantity, deniedQuantity);
}
public CharSequence getSummaryText(AppOpsState state) {
- return getCombinedText(mOps, state.mOpSummaries);
+ return getCombinedText(mOps, state.mOpSummaries, state.mContext.getResources(), true);
}
public CharSequence getSwitchText(AppOpsState state) {
+ final Resources res = state.mContext.getResources();
if (mSwitchOps.size() > 0) {
- return getCombinedText(mSwitchOps, state.mOpLabels);
+ return getCombinedText(mSwitchOps, state.mOpLabels, res, false);
} else {
- return getCombinedText(mOps, state.mOpLabels);
+ return getCombinedText(mOps, state.mOpLabels, res, false);
}
}
}
private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries,
- final String packageName, ApplicationInfo appInfo) {
+ final String packageName, ApplicationInfo appInfo, boolean applyFilters) {
+
+ if (appInfo == null) {
+ try {
+ appInfo = mPm.getApplicationInfo(packageName,
+ PackageManager.GET_DISABLED_COMPONENTS
+ | PackageManager.GET_UNINSTALLED_PACKAGES);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, "Unable to find info for package " + packageName);
+ return null;
+ }
+ }
+
+ if (applyFilters) {
+ // Hide user apps if needed
+ if (!shouldShowUserApps() &&
+ (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ return null;
+ }
+ // Hide system apps if needed
+ if (!shouldShowSystemApps() &&
+ (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ return null;
+ }
+ }
+
AppEntry appEntry = appEntries.get(packageName);
if (appEntry == null) {
- if (appInfo == null) {
- try {
- appInfo = mPm.getApplicationInfo(packageName,
- PackageManager.GET_DISABLED_COMPONENTS
- | PackageManager.GET_UNINSTALLED_PACKAGES);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Unable to find info for package " + packageName);
- return null;
- }
- }
appEntry = new AppEntry(this, appInfo);
appEntry.loadLabel(context);
appEntries.put(packageName, appEntry);
return appEntry;
}
- public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) {
- return buildState(tpl, uid, packageName, RECENCY_COMPARATOR);
+ private boolean shouldShowUserApps() {
+ return mPreferences.getBoolean("show_user_apps", true);
+ }
+
+ private boolean shouldShowSystemApps() {
+ return mPreferences.getBoolean("show_system_apps", true);
+ }
+
+ public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName,
+ boolean privacyGuard) {
+ return buildState(tpl, uid, packageName, RECENCY_COMPARATOR, privacyGuard);
}
public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName,
Comparator<AppOpEntry> comparator) {
+ return buildState(tpl, uid, packageName, comparator, false);
+ }
+
+ private boolean isPrivacyGuardOp(int op) {
+ for (int privacyGuardOp : AppOpsManager.PRIVACY_GUARD_OP_STATES) {
+ if (privacyGuardOp == op) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName,
+ Comparator<AppOpEntry> comparator, boolean privacyGuard) {
final Context context = mContext;
final HashMap<String, AppEntry> appEntries = new HashMap<String, AppEntry>();
final ArrayList<String> perms = new ArrayList<String>();
final ArrayList<Integer> permOps = new ArrayList<Integer>();
final int[] opToOrder = new int[AppOpsManager._NUM_OP];
+
+ final Set<Integer> privacyGuardOps = new HashSet<>();
+
for (int i=0; i<tpl.ops.length; i++) {
+ if (privacyGuard && isPrivacyGuardOp(tpl.ops[i])) {
+ // If there's a permission for this Privacy Guard OP, then
+ // we don't have to treat it in a special way. The application
+ // should have the permission declared if it uses it, so we
+ // will add this later when we query PackageManager
+ String perm = AppOpsManager.opToPermission(tpl.ops[i]);
+ if (perm != null) {
+ if (DEBUG) Log.d(TAG, "Adding " + AppOpsManager.opToName(tpl.ops[i])
+ + " (" + tpl.ops[i] + ") to privacyGuardOps");
+ privacyGuardOps.add(tpl.ops[i]);
+ } else {
+ if (DEBUG) Log.d(TAG, "Not adding " + AppOpsManager.opToName(tpl.ops[i])
+ + " (" + tpl.ops[i] + ") with perm " + perm + " to privacyGuardOps");
+ }
+ }
if (tpl.showPerms[i]) {
String perm = AppOpsManager.opToPermission(tpl.ops[i]);
if (perm != null && !perms.contains(perm)) {
}
}
+ // Whether to apply hide user / system app filters
+ final boolean applyFilters = (packageName == null);
+
List<AppOpsManager.PackageOps> pkgs;
if (packageName != null) {
pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops);
if (pkgs != null) {
for (int i=0; i<pkgs.size(); i++) {
AppOpsManager.PackageOps pkgOps = pkgs.get(i);
- AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null);
+ AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null,
+ applyFilters);
if (appEntry == null) {
continue;
}
for (int j=0; j<pkgOps.getOps().size(); j++) {
AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j);
+ if (privacyGuard && privacyGuardOps.contains(opEntry.getOp())) {
+ // This OP is here because the user enabled Privacy Guard
+ // for this application.
+ if (DEBUG) Log.d(TAG, "Not adding "
+ + AppOpsManager.opToName(opEntry.getOp())
+ + " (" + opEntry.getOp() + ")");
+ continue;
+ }
addOp(entries, pkgOps, appEntry, opEntry, packageName == null,
packageName == null ? 0 : opToOrder[opEntry.getOp()]);
}
for (int i=0; i<apps.size(); i++) {
PackageInfo appInfo = apps.get(i);
AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName,
- appInfo.applicationInfo);
+ appInfo.applicationInfo, applyFilters);
if (appEntry == null) {
continue;
}
if (appInfo.requestedPermissions != null) {
for (int j=0; j<appInfo.requestedPermissions.length; j++) {
if (appInfo.requestedPermissionsFlags != null) {
- if ((appInfo.requestedPermissionsFlags[j]
+ if (!privacyGuard && (appInfo.requestedPermissionsFlags[j]
& PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm "
+ appInfo.requestedPermissions[j] + " not granted; skipping");
}
AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry(
- permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0, -1, null);
+ permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0, -1, null, 0, 0);
dummyOps.add(opEntry);
addOp(entries, pkgOps, appEntry, opEntry, packageName == null,
packageName == null ? 0 : opToOrder[opEntry.getOp()]);