2 * Copyright (C) 2013 The Android Open Source Project
3 * Copyright (C) 2017-2018 The LineageOS Project
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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
18 package com.android.settings.applications;
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;
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;
47 import java.util.HashMap;
48 import java.util.List;
49 import java.util.StringJoiner;
51 public class AppOpsDetails extends SettingsPreferenceFragment {
52 static final String TAG = "AppOpsDetails";
54 public static final String ARG_PACKAGE_NAME = "package";
56 private AppOpsState mState;
57 private PackageManager mPm;
58 private AppOpsManager mAppOps;
59 private PackageInfo mPackageInfo;
60 private PreferenceScreen mPreferenceScreen;
62 private final int MODE_ALLOWED = 0;
63 private final int MODE_IGNORED = 1;
64 private final int MODE_ASK = 2;
66 private final String[] MODE_ENTRIES = {
67 String.valueOf(MODE_ALLOWED),
68 String.valueOf(MODE_IGNORED),
69 String.valueOf(MODE_ASK)
72 private int modeToPosition (int mode) {
74 case AppOpsManager.MODE_ALLOWED:
76 case AppOpsManager.MODE_IGNORED:
78 case AppOpsManager.MODE_ASK:
85 private int positionToMode (int position) {
88 return AppOpsManager.MODE_ALLOWED;
90 return AppOpsManager.MODE_IGNORED;
92 return AppOpsManager.MODE_ASK;
95 return AppOpsManager.MODE_IGNORED;
98 private static HashMap<Integer, Integer> OP_ICONS = new HashMap<>();
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);
121 private boolean isPlatformSigned() {
122 final int match = mPm.checkSignatures("android", mPackageInfo.packageName);
123 return match >= PackageManager.SIGNATURE_MATCH;
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;
134 label = appInfo.loadLabel(mPm).toString();
135 } catch (Throwable t) {
136 Log.e(TAG, "Error loading application label for " + appLabel, t);
140 AppHeader.createAppHeader(this, icon, label, appInfo.packageName, uid);
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();
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);
165 private boolean refreshUi() {
166 if (mPackageInfo == null) {
170 mPreferenceScreen.removeAll();
171 setAppHeader(mPackageInfo);
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) {
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);
185 Drawable icon = getIconByPermission(perm);
186 if (icon == null && op != 0 && OP_ICONS.containsKey(op)) {
187 icon = getActivity().getDrawable(OP_ICONS.get(op));
190 final AppOpsManager.OpEntry firstOp = entry.getOpEntry(0);
191 final int switchOp = AppOpsManager.opToSwitch(firstOp.getOp());
193 // ListPreference for 3 states: ask, allow, deny
194 if (AppOpsManager.isStrictOp(switchOp)) {
195 ListPreference listPref = getListPrefForEntry(entry, icon);
196 mPreferenceScreen.addPreference(listPref);
198 SwitchPreference switchPref = getSwitchPrefForEntry(entry, icon);
199 mPreferenceScreen.addPreference(switchPref);
204 if (mPreferenceScreen.getPreferenceCount() == 0) {
205 Preference noBlockablePermissionsPref = getNoBlockablePermissionsPref();
206 mPreferenceScreen.addPreference(noBlockablePermissionsPref);
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
217 if (isPlatformSigned()) {
218 return new AppOpsState.OpsTemplate[]{ AppOpsState.SU_TEMPLATE };
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;
231 allTemplates[i] = tpl;
237 private Drawable getIconByPermission(String perm) {
238 Drawable icon = null;
241 PermissionInfo pi = mPm.getPermissionInfo(perm, 0);
242 if (pi.group != null) {
243 PermissionGroupInfo pgi = mPm.getPermissionGroupInfo(pi.group, 0);
245 icon = pgi.loadIcon(mPm);
248 } catch (NameNotFoundException e) {
254 private ListPreference getListPrefForEntry(final AppOpsState.AppOpEntry entry, Drawable icon) {
255 final Resources res = getActivity().getResources();
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);
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() {
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);
293 private SwitchPreference getSwitchPrefForEntry(final AppOpsState.AppOpEntry entry,
295 final Resources res = getActivity().getResources();
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);
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() {
314 public boolean onPreferenceChange(Preference preference,
316 Boolean isChecked = (Boolean) newValue;
317 mAppOps.setMode(switchOp, uid, pkgName,
318 isChecked ? AppOpsManager.MODE_ALLOWED
319 : AppOpsManager.MODE_IGNORED);
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);
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);
342 /** Called when the activity is first created. */
344 public void onCreate(Bundle icicle) {
345 super.onCreate(icicle);
347 mState = new AppOpsState(getActivity());
348 mPm = getActivity().getPackageManager();
349 mAppOps = (AppOpsManager)getActivity().getSystemService(Context.APP_OPS_SERVICE);
350 mPreferenceScreen = getPreferenceManager().createPreferenceScreen(getActivity());
353 setPreferenceScreen(mPreferenceScreen);
354 setHasOptionsMenu(true);
358 protected int getMetricsCategory() {
359 return MetricsEvent.APP_OPS_DETAILS;
363 public void onResume() {
366 setIntentAndFinish(true, true);
370 private String getSummary(CharSequence... lines) {
371 StringJoiner sj = new StringJoiner("\n");
372 for (CharSequence line : lines) {
373 if (!TextUtils.isEmpty(line)) {
377 return sj.toString();