OSDN Git Service

AppOpsDetails: Display all missing ops
[android-x86/packages-apps-Settings.git] / src / com / android / settings / applications / AppOpsDetails.java
1 /**
2  * Copyright (C) 2013 The Android Open Source Project
3  * Copyright (C) 2017-2018 The LineageOS Project
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6  * use this file except in compliance with the License. You may obtain a copy
7  * of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14  * License for the specific language governing permissions and limitations
15  * under the License.
16  */
17
18 package com.android.settings.applications;
19
20 import android.app.Activity;
21 import android.app.AppOpsManager;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.pm.PermissionGroupInfo;
29 import android.content.pm.PermissionInfo;
30 import android.content.res.Resources;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.support.v14.preference.SwitchPreference;
34 import android.support.v7.preference.ListPreference;
35 import android.support.v7.preference.Preference;
36 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
37 import android.support.v7.preference.PreferenceScreen;
38 import android.text.TextUtils;
39 import android.util.Log;
40
41 import com.android.internal.logging.MetricsProto.MetricsEvent;
42 import com.android.settings.AppHeader;
43 import com.android.settings.R;
44 import com.android.settings.SettingsActivity;
45 import com.android.settings.SettingsPreferenceFragment;
46
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.StringJoiner;
50
51 public class AppOpsDetails extends SettingsPreferenceFragment {
52     static final String TAG = "AppOpsDetails";
53
54     public static final String ARG_PACKAGE_NAME = "package";
55
56     private AppOpsState mState;
57     private PackageManager mPm;
58     private AppOpsManager mAppOps;
59     private PackageInfo mPackageInfo;
60     private PreferenceScreen mPreferenceScreen;
61
62     private final int MODE_ALLOWED = 0;
63     private final int MODE_IGNORED = 1;
64     private final int MODE_ASK     = 2;
65
66     private final String[] MODE_ENTRIES = {
67             String.valueOf(MODE_ALLOWED),
68             String.valueOf(MODE_IGNORED),
69             String.valueOf(MODE_ASK)
70     };
71
72     private int modeToPosition (int mode) {
73         switch(mode) {
74         case AppOpsManager.MODE_ALLOWED:
75             return MODE_ALLOWED;
76         case AppOpsManager.MODE_IGNORED:
77             return MODE_IGNORED;
78         case AppOpsManager.MODE_ASK:
79             return MODE_ASK;
80         };
81
82         return MODE_IGNORED;
83     }
84
85     private int positionToMode (int position) {
86         switch(position) {
87         case MODE_ALLOWED:
88             return AppOpsManager.MODE_ALLOWED;
89         case MODE_IGNORED:
90             return AppOpsManager.MODE_IGNORED;
91         case MODE_ASK:
92             return AppOpsManager.MODE_ASK;
93         };
94
95         return AppOpsManager.MODE_IGNORED;
96     }
97
98     private static HashMap<Integer, Integer> OP_ICONS = new HashMap<>();
99
100     static {
101         OP_ICONS.put(AppOpsManager.OP_BOOT_COMPLETED, R.drawable.ic_perm_boot);
102         OP_ICONS.put(AppOpsManager.OP_CHANGE_WIFI_STATE, R.drawable.ic_perm_wifi);
103         OP_ICONS.put(AppOpsManager.OP_DATA_CONNECT_CHANGE, R.drawable.ic_perm_data);
104         OP_ICONS.put(AppOpsManager.OP_GPS, R.drawable.ic_perm_location);
105         OP_ICONS.put(AppOpsManager.OP_MUTE_MICROPHONE, R.drawable.ic_perm_microphone);
106         OP_ICONS.put(AppOpsManager.OP_NFC_CHANGE, R.drawable.ic_perm_nfc);
107         OP_ICONS.put(AppOpsManager.OP_POST_NOTIFICATION, R.drawable.ic_perm_notifications);
108         OP_ICONS.put(AppOpsManager.OP_READ_CLIPBOARD, R.drawable.ic_perm_clipboard);
109         OP_ICONS.put(AppOpsManager.OP_RUN_IN_BACKGROUND, R.drawable.ic_perm_background);
110         OP_ICONS.put(AppOpsManager.OP_SU, R.drawable.ic_perm_su);
111         OP_ICONS.put(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, R.drawable.ic_perm_drawontop);
112         OP_ICONS.put(AppOpsManager.OP_TAKE_AUDIO_FOCUS, R.drawable.ic_perm_audio);
113         OP_ICONS.put(AppOpsManager.OP_VIBRATE, R.drawable.ic_perm_vibrate);
114         OP_ICONS.put(AppOpsManager.OP_WAKE_LOCK, R.drawable.ic_perm_nosleep);
115         OP_ICONS.put(AppOpsManager.OP_WIFI_SCAN, R.drawable.ic_perm_wifi);
116         OP_ICONS.put(AppOpsManager.OP_WRITE_CLIPBOARD, R.drawable.ic_perm_clipboard);
117         OP_ICONS.put(AppOpsManager.OP_WRITE_SETTINGS, R.drawable.ic_perm_settings);
118         OP_ICONS.put(AppOpsManager.OP_WRITE_SMS , R.drawable.ic_perm_sms);
119     }
120
121     private boolean isPlatformSigned() {
122         final int match = mPm.checkSignatures("android", mPackageInfo.packageName);
123         return match >= PackageManager.SIGNATURE_MATCH;
124     }
125
126     // Utility method to set application label and icon.
127     private void setAppHeader(PackageInfo pkgInfo) {
128         ApplicationInfo appInfo = pkgInfo.applicationInfo;
129         String appLabel = mPm.getApplicationLabel(appInfo).toString();
130         Drawable icon = mPm.getApplicationIcon(appInfo);
131         int uid = appInfo.uid;
132         String label;
133         try {
134             label = appInfo.loadLabel(mPm).toString();
135         } catch (Throwable t) {
136             Log.e(TAG, "Error loading application label for " + appLabel, t);
137             label = appLabel;
138         }
139
140         AppHeader.createAppHeader(this, icon, label, appInfo.packageName, uid);
141     }
142
143     private String retrieveAppEntry() {
144         final Bundle args = getArguments();
145         String packageName = (args != null) ? args.getString(ARG_PACKAGE_NAME) : null;
146         if (packageName == null) {
147             Intent intent = (args == null) ?
148                     getActivity().getIntent() : (Intent) args.getParcelable("intent");
149             if (intent != null) {
150                 packageName = intent.getData().getSchemeSpecificPart();
151             }
152         }
153         try {
154             mPackageInfo = mPm.getPackageInfo(packageName,
155                     PackageManager.GET_DISABLED_COMPONENTS |
156                     PackageManager.GET_UNINSTALLED_PACKAGES);
157         } catch (NameNotFoundException e) {
158             Log.e(TAG, "Exception when retrieving package:" + packageName, e);
159             mPackageInfo = null;
160         }
161
162         return packageName;
163     }
164
165     private boolean refreshUi() {
166         if (mPackageInfo == null) {
167             return false;
168         }
169
170         mPreferenceScreen.removeAll();
171         setAppHeader(mPackageInfo);
172
173         AppOpsState.OpsTemplate[] allTemplates = getTemplates();
174         for (AppOpsState.OpsTemplate tpl : allTemplates) {
175             List<AppOpsState.AppOpEntry> entries = mState.buildState(tpl,
176                     mPackageInfo.applicationInfo.uid, mPackageInfo.packageName, true);
177             for (final AppOpsState.AppOpEntry entry : entries) {
178                 String perm = null;
179                 int op = 0;
180                 // Find the first permission with a known name
181                 for (int i = 0; i < entry.getNumOpEntry() && perm == null; i++) {
182                     op = entry.getOpEntry(i).getOp();
183                     perm = AppOpsManager.opToPermission(op);
184                 }
185                 Drawable icon = getIconByPermission(perm);
186                 if (icon == null && op != 0 && OP_ICONS.containsKey(op)) {
187                     icon = getActivity().getDrawable(OP_ICONS.get(op));
188                 }
189
190                 final AppOpsManager.OpEntry firstOp = entry.getOpEntry(0);
191                 final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp());
192
193                 // ListPreference for 3 states: ask, allow, deny
194                 if (AppOpsManager.isStrictOp(switchOp)) {
195                     ListPreference listPref = getListPrefForEntry(entry, icon);
196                     mPreferenceScreen.addPreference(listPref);
197                 } else {
198                     SwitchPreference switchPref = getSwitchPrefForEntry(entry, icon);
199                     mPreferenceScreen.addPreference(switchPref);
200                 }
201             }
202         }
203
204         if (mPreferenceScreen.getPreferenceCount() == 0) {
205             Preference noBlockablePermissionsPref = getNoBlockablePermissionsPref();
206             mPreferenceScreen.addPreference(noBlockablePermissionsPref);
207         }
208
209         return true;
210     }
211
212     private AppOpsState.OpsTemplate[] getTemplates() {
213         /* If we are platform signed, only show the root switch, this
214          * one is safe to toggle while other permission-based ones could
215          * certainly cause system-wide problems
216          */
217         if (isPlatformSigned()) {
218             return new AppOpsState.OpsTemplate[]{ AppOpsState.SU_TEMPLATE };
219         }
220
221         int length = AppOpsState.ALL_PERMS_TEMPLATES.length;
222         AppOpsState.OpsTemplate[] allTemplates = new AppOpsState.OpsTemplate[length];
223         // Loop all existing templates and set the visibility of each perm to true
224         for (int i = 0; i < length; i++) {
225             AppOpsState.OpsTemplate tpl = AppOpsState.ALL_PERMS_TEMPLATES[i];
226             for (int j = 0; j < tpl.ops.length; j++) {
227                 // we only want to use the template's orderings, not the visibility
228                 tpl.showPerms[j] = true;
229             }
230
231             allTemplates[i] = tpl;
232         }
233
234         return allTemplates;
235     }
236
237     private Drawable getIconByPermission(String perm) {
238         Drawable icon = null;
239         if (perm != null) {
240             try {
241                 PermissionInfo pi = mPm.getPermissionInfo(perm, 0);
242                 if (pi.group != null) {
243                     PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(pi.group, 0);
244                     if (pgi.icon != 0) {
245                         icon = pgi.loadIcon(mPm);
246                     }
247                 }
248             } catch (NameNotFoundException e) {
249             }
250         }
251         return icon;
252     }
253
254     private ListPreference getListPrefForEntry(final AppOpsState.AppOpEntry entry, Drawable icon) {
255         final Resources res = getActivity().getResources();
256
257         final AppOpsManager.OpEntry firstOp = entry.getOpEntry(0);
258         final AppOpsManager.PackageOps pkgOps = entry.getPackageOps();
259         final int uid = pkgOps.getUid();
260         final String pkgName = pkgOps.getPackageName();
261         final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp());
262         final int mode = mAppOps.checkOpNoThrow(switchOp, uid, pkgName);
263         final CharSequence opName = entry.getSwitchText(mState);
264
265         ListPreference listPref = new ListPreference(getActivity());
266         listPref.setLayoutResource(R.layout.preference_appops);
267         listPref.setIcon(icon);
268         listPref.setTitle(opName);
269         listPref.setDialogTitle(opName);
270         listPref.setEntries(R.array.app_ops_permissions);
271         listPref.setEntryValues(MODE_ENTRIES);
272         listPref.setValueIndex(modeToPosition(mode));
273         String summary = getSummary(listPref.getEntry(), entry.getCountsText(res),
274                 entry.getTimeText(res, true));
275         listPref.setSummary(summary);
276         listPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
277             @Override
278             public boolean onPreferenceChange(Preference preference, Object newValue) {
279                 ListPreference listPref = (ListPreference) preference;
280                 String value = newValue.toString();
281                 int selectedIndex = listPref.findIndexOfValue(value);
282                 mAppOps.setMode(switchOp, uid, pkgName, positionToMode(selectedIndex));
283                 String summary = getSummary(listPref.getEntries()[selectedIndex],
284                         entry.getCountsText(res), entry.getTimeText(res, true));
285                 listPref.setSummary(summary);
286                 return true;
287             }
288         });
289
290         return listPref;
291     }
292
293     private SwitchPreference getSwitchPrefForEntry(final AppOpsState.AppOpEntry entry,
294                                                    Drawable icon) {
295         final Resources res = getActivity().getResources();
296
297         final AppOpsManager.OpEntry firstOp = entry.getOpEntry(0);
298         final AppOpsManager.PackageOps pkgOps = entry.getPackageOps();
299         final int uid = pkgOps.getUid();
300         final String pkgName = pkgOps.getPackageName();
301         final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp());
302         final int mode = mAppOps.checkOpNoThrow(switchOp, uid, pkgName);
303         final CharSequence opName = entry.getSwitchText(mState);
304
305         SwitchPreference switchPref = new SwitchPreference(getActivity());
306         switchPref.setLayoutResource(R.layout.preference_appops);
307         switchPref.setIcon(icon);
308         switchPref.setTitle(opName);
309         String summary = getSummary(entry.getCountsText(res), entry.getTimeText(res, true));
310         switchPref.setSummary(summary);
311         switchPref.setChecked(mode == AppOpsManager.MODE_ALLOWED);
312         switchPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
313             @Override
314             public boolean onPreferenceChange(Preference preference,
315                                               Object newValue) {
316                 Boolean isChecked = (Boolean) newValue;
317                 mAppOps.setMode(switchOp, uid, pkgName,
318                         isChecked ? AppOpsManager.MODE_ALLOWED
319                                 : AppOpsManager.MODE_IGNORED);
320                 return true;
321             }
322         });
323
324         return switchPref;
325     }
326
327     private Preference getNoBlockablePermissionsPref() {
328         Preference emptyPref = new Preference(getActivity());
329         emptyPref.setTitle(R.string.app_ops_no_blockable_permissions);
330         emptyPref.setSelectable(false);
331         emptyPref.setEnabled(false);
332         return emptyPref;
333     }
334
335     private void setIntentAndFinish(boolean finish, boolean appChanged) {
336         Intent intent = new Intent();
337         intent.putExtra(ManageApplications.APP_CHG, appChanged);
338         SettingsActivity sa = (SettingsActivity)getActivity();
339         sa.finishPreferencePanel(this, Activity.RESULT_OK, intent);
340     }
341
342     /** Called when the activity is first created. */
343     @Override
344     public void onCreate(Bundle icicle) {
345         super.onCreate(icicle);
346
347         mState = new AppOpsState(getActivity());
348         mPm = getActivity().getPackageManager();
349         mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE);
350         mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity());
351         retrieveAppEntry();
352
353         setPreferenceScreen(mPreferenceScreen);
354         setHasOptionsMenu(true);
355     }
356
357     @Override
358     protected int getMetricsCategory() {
359         return MetricsEvent.APP_OPS_DETAILS;
360     }
361
362     @Override
363     public void onResume() {
364         super.onResume();
365         if (!refreshUi()) {
366             setIntentAndFinish(true, true);
367         }
368     }
369
370     private String getSummary(CharSequence... lines) {
371         StringJoiner sj = new StringJoiner("\n");
372         for (CharSequence line : lines) {
373             if (!TextUtils.isEmpty(line)) {
374                 sj.add(line);
375             }
376         }
377         return sj.toString();
378     }
379 }