OSDN Git Service

AppOpsDetails: Display all missing ops
[android-x86/packages-apps-Settings.git] / src / com / android / settings / applications / AppOpsState.java
index 07e955d..b3d344e 100644 (file)
@@ -1,5 +1,6 @@
 /**
  * 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
@@ -16,6 +17,7 @@
 
 package com.android.settings.applications;
 
+import android.app.Activity;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -23,13 +25,14 @@ import android.content.pm.PackageInfo;
 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 android.text.format.DateUtils;
-
 import android.util.Log;
 import android.util.SparseArray;
+
 import com.android.settings.R;
 
 import java.io.File;
@@ -38,7 +41,9 @@ import java.util.ArrayList;
 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";
@@ -52,12 +57,15 @@ public class 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 {
@@ -167,7 +175,7 @@ public class AppOpsState {
                     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,
@@ -180,6 +188,7 @@ public class AppOpsState {
                     false,
                     false,
                     false,
+                    false,
                     false }
             );
 
@@ -193,7 +202,11 @@ public class AppOpsState {
                     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,
@@ -203,12 +216,74 @@ public class AppOpsState {
                     false,
                     false,
                     false,
-                    false }
+                    false,
+                    true,
+                    true,
+                    true,
+                    true }
+            );
+
+    public static final OpsTemplate RUN_IN_BACKGROUND_TEMPLATE = new OpsTemplate(
+            new int[] { AppOpsManager.OP_RUN_IN_BACKGROUND },
+            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
+            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
     };
 
     /**
@@ -306,6 +381,7 @@ public class AppOpsState {
                 = new ArrayList<AppOpsManager.OpEntry>();
         private final AppEntry mApp;
         private final int mSwitchOrder;
+        private int mOverriddenPrimaryMode = -1;
 
         public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app,
                 int switchOrder) {
@@ -363,31 +439,68 @@ public class AppOpsState {
             return mOps.get(pos);
         }
 
+        public int getPrimaryOpMode() {
+            return mOverriddenPrimaryMode >= 0 ? mOverriddenPrimaryMode : mOps.get(0).getMode();
+        }
+
+        public void overridePrimaryOpMode(int mode) {
+            mOverriddenPrimaryMode = mode;
+        }
+
         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);
             }
         }
 
@@ -418,9 +531,9 @@ public class AppOpsState {
     }
 
     /**
-     * Perform alphabetical comparison of application entry objects.
+     * Perform app op state comparison of application entry objects.
      */
-    public static final Comparator<AppOpEntry> APP_OP_COMPARATOR = new Comparator<AppOpEntry>() {
+    public static final Comparator<AppOpEntry> RECENCY_COMPARATOR = new Comparator<AppOpEntry>() {
         private final Collator sCollator = Collator.getInstance();
         @Override
         public int compare(AppOpEntry object1, AppOpEntry object2) {
@@ -440,6 +553,18 @@ public class AppOpsState {
         }
     };
 
+    /**
+     * Perform alphabetical comparison of application entry objects.
+     */
+    public static final Comparator<AppOpEntry> LABEL_COMPARATOR = new Comparator<AppOpEntry>() {
+        private final Collator sCollator = Collator.getInstance();
+        @Override
+        public int compare(AppOpEntry object1, AppOpEntry object2) {
+            return sCollator.compare(object1.getAppEntry().getLabel(),
+                    object2.getAppEntry().getLabel());
+        }
+    };
+
     private void addOp(List<AppOpEntry> entries, AppOpsManager.PackageOps pkgOps,
             AppEntry appEntry, AppOpsManager.OpEntry opEntry, boolean allowMerge, int switchOrder) {
         if (allowMerge && entries.size() > 0) {
@@ -466,24 +591,43 @@ public class AppOpsState {
         entries.add(entry);
     }
 
+    public AppOpsManager getAppOpsManager() {
+        return mAppOps;
+    }
+
     public List<AppOpEntry> buildState(OpsTemplate tpl) {
-        return buildState(tpl, 0, null);
+        return buildState(tpl, 0, null, RECENCY_COMPARATOR);
     }
 
     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);
@@ -491,7 +635,35 @@ public class AppOpsState {
         return appEntry;
     }
 
-    public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) {
+    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>();
@@ -500,7 +672,25 @@ public class AppOpsState {
         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)) {
@@ -511,6 +701,9 @@ public class AppOpsState {
             }
         }
 
+        // 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);
@@ -521,12 +714,21 @@ public class AppOpsState {
         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()]);
                 }
@@ -549,7 +751,7 @@ public class AppOpsState {
         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;
             }
@@ -558,7 +760,7 @@ public class AppOpsState {
             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");
@@ -583,7 +785,7 @@ public class AppOpsState {
 
                         }
                         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()]);
@@ -593,7 +795,7 @@ public class AppOpsState {
         }
 
         // Sort the list.
-        Collections.sort(entries, APP_OP_COMPARATOR);
+        Collections.sort(entries, comparator);
 
         // Done!
         return entries;