blob: 808afa8e3088eb41f688c54b2d7cc9a342c971c6 [file] [log] [blame]
Dianne Hackborn18b64f42013-01-18 10:52:38 -08001/**
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy
6 * of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16
17package com.android.settings.applications;
18
19import android.app.AppOpsManager;
20import android.content.Context;
21import android.content.pm.ApplicationInfo;
22import android.content.pm.PackageInfo;
23import android.content.pm.PackageManager;
24import android.content.pm.PackageManager.NameNotFoundException;
25import android.content.res.Resources;
26import android.graphics.drawable.Drawable;
27import android.os.Parcel;
28import android.os.Parcelable;
29import android.text.format.DateUtils;
30
Dianne Hackborn0dd99022013-01-24 19:14:26 -080031import android.util.Log;
32import android.util.SparseArray;
Dianne Hackborn18b64f42013-01-18 10:52:38 -080033import com.android.settings.R;
34
35import java.io.File;
36import java.text.Collator;
37import java.util.ArrayList;
38import java.util.Collections;
39import java.util.Comparator;
40import java.util.HashMap;
41import java.util.List;
42
43public class AppOpsState {
Dianne Hackborn0dd99022013-01-24 19:14:26 -080044 static final String TAG = "AppOpsState";
45 static final boolean DEBUG = false;
46
Dianne Hackborn18b64f42013-01-18 10:52:38 -080047 final Context mContext;
48 final AppOpsManager mAppOps;
49 final PackageManager mPm;
Dianne Hackborn0dd99022013-01-24 19:14:26 -080050 final CharSequence[] mOpNames;
Dianne Hackborn18b64f42013-01-18 10:52:38 -080051
52 List<AppOpEntry> mApps;
53
54 public AppOpsState(Context context) {
55 mContext = context;
56 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
57 mPm = context.getPackageManager();
Dianne Hackborn0dd99022013-01-24 19:14:26 -080058 mOpNames = context.getResources().getTextArray(R.array.app_ops_names);
Dianne Hackborn18b64f42013-01-18 10:52:38 -080059 }
60
61 public static class OpsTemplate implements Parcelable {
62 public final int[] ops;
Dianne Hackborn18b64f42013-01-18 10:52:38 -080063
Dianne Hackborn0dd99022013-01-24 19:14:26 -080064 public OpsTemplate(int[] _ops) {
Dianne Hackborn18b64f42013-01-18 10:52:38 -080065 ops = _ops;
Dianne Hackborn18b64f42013-01-18 10:52:38 -080066 }
67
68 OpsTemplate(Parcel src) {
69 ops = src.createIntArray();
Dianne Hackborn18b64f42013-01-18 10:52:38 -080070 }
71
72 @Override
73 public int describeContents() {
74 return 0;
75 }
76
77 @Override
78 public void writeToParcel(Parcel dest, int flags) {
79 dest.writeIntArray(ops);
Dianne Hackborn18b64f42013-01-18 10:52:38 -080080 }
81
82 public static final Creator<OpsTemplate> CREATOR = new Creator<OpsTemplate>() {
83 @Override public OpsTemplate createFromParcel(Parcel source) {
84 return new OpsTemplate(source);
85 }
86
87 @Override public OpsTemplate[] newArray(int size) {
88 return new OpsTemplate[size];
89 }
90 };
91 }
92
93 public static final OpsTemplate LOCATION_TEMPLATE = new OpsTemplate(
94 new int[] { AppOpsManager.OP_COARSE_LOCATION,
95 AppOpsManager.OP_FINE_LOCATION,
Dianne Hackborn0dd99022013-01-24 19:14:26 -080096 AppOpsManager.OP_GPS,
97 AppOpsManager.OP_WIFI_SCAN }
Dianne Hackborn18b64f42013-01-18 10:52:38 -080098 );
99
100 public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate(
101 new int[] { AppOpsManager.OP_READ_CONTACTS,
102 AppOpsManager.OP_WRITE_CONTACTS,
103 AppOpsManager.OP_READ_CALL_LOG,
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800104 AppOpsManager.OP_WRITE_CALL_LOG,
105 AppOpsManager.OP_READ_CALENDAR,
106 AppOpsManager.OP_WRITE_CALENDAR }
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800107 );
108
109 public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate(
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800110 new int[] { AppOpsManager.OP_VIBRATE }
111 );
112
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800113 public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] {
114 LOCATION_TEMPLATE, PERSONAL_TEMPLATE, DEVICE_TEMPLATE
115 };
116
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800117 /**
118 * This class holds the per-item data in our Loader.
119 */
120 public static class AppEntry {
121 private final AppOpsState mState;
122 private final ApplicationInfo mInfo;
123 private final File mApkFile;
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800124 private final SparseArray<AppOpsManager.OpEntry> mOps
125 = new SparseArray<AppOpsManager.OpEntry>();
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800126 private String mLabel;
127 private Drawable mIcon;
128 private boolean mMounted;
129
130 public AppEntry(AppOpsState state, ApplicationInfo info) {
131 mState = state;
132 mInfo = info;
133 mApkFile = new File(info.sourceDir);
134 }
135
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800136 public void addOp(AppOpsManager.OpEntry op) {
137 mOps.put(op.getOp(), op);
138 }
139
140 public boolean hasOp(int op) {
141 return mOps.indexOfKey(op) >= 0;
142 }
143
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800144 public ApplicationInfo getApplicationInfo() {
145 return mInfo;
146 }
147
148 public String getLabel() {
149 return mLabel;
150 }
151
152 public Drawable getIcon() {
153 if (mIcon == null) {
154 if (mApkFile.exists()) {
155 mIcon = mInfo.loadIcon(mState.mPm);
156 return mIcon;
157 } else {
158 mMounted = false;
159 }
160 } else if (!mMounted) {
161 // If the app wasn't mounted but is now mounted, reload
162 // its icon.
163 if (mApkFile.exists()) {
164 mMounted = true;
165 mIcon = mInfo.loadIcon(mState.mPm);
166 return mIcon;
167 }
168 } else {
169 return mIcon;
170 }
171
172 return mState.mContext.getResources().getDrawable(
173 android.R.drawable.sym_def_app_icon);
174 }
175
176 @Override public String toString() {
177 return mLabel;
178 }
179
180 void loadLabel(Context context) {
181 if (mLabel == null || !mMounted) {
182 if (!mApkFile.exists()) {
183 mMounted = false;
184 mLabel = mInfo.packageName;
185 } else {
186 mMounted = true;
187 CharSequence label = mInfo.loadLabel(context.getPackageManager());
188 mLabel = label != null ? label.toString() : mInfo.packageName;
189 }
190 }
191 }
192 }
193
194 /**
195 * This class holds the per-item data in our Loader.
196 */
197 public static class AppOpEntry {
198 private final AppOpsManager.PackageOps mPkgOps;
199 private final ArrayList<AppOpsManager.OpEntry> mOps
200 = new ArrayList<AppOpsManager.OpEntry>();
201 private final AppEntry mApp;
202
203 public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app) {
204 mPkgOps = pkg;
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800205 mApp = app;
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800206 mApp.addOp(op);
207 mOps.add(op);
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800208 }
209
210 public void addOp(AppOpsManager.OpEntry op) {
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800211 mApp.addOp(op);
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800212 for (int i=0; i<mOps.size(); i++) {
213 AppOpsManager.OpEntry pos = mOps.get(i);
214 if (pos.isRunning() != op.isRunning()) {
215 if (op.isRunning()) {
216 mOps.add(i, op);
217 return;
218 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800219 continue;
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800220 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800221 if (pos.getTime() < op.getTime()) {
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800222 mOps.add(i, op);
223 return;
224 }
225 }
226 mOps.add(op);
227 }
228
229 public AppEntry getAppEntry() {
230 return mApp;
231 }
232
233 public AppOpsManager.PackageOps getPackageOps() {
234 return mPkgOps;
235 }
236
237 public int getNumOpEntry() {
238 return mOps.size();
239 }
240
241 public AppOpsManager.OpEntry getOpEntry(int pos) {
242 return mOps.get(pos);
243 }
244
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800245 public CharSequence getLabelText(AppOpsState state) {
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800246 if (getNumOpEntry() == 1) {
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800247 return state.mOpNames[getOpEntry(0).getOp()];
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800248 } else {
249 StringBuilder builder = new StringBuilder();
250 for (int i=0; i<getNumOpEntry(); i++) {
251 if (i > 0) {
252 builder.append(", ");
253 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800254 builder.append(state.mOpNames[getOpEntry(i).getOp()]);
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800255 }
256 return builder.toString();
257 }
258 }
259
260 public CharSequence getTimeText(Resources res) {
261 if (isRunning()) {
262 return res.getText(R.string.app_ops_running);
263 }
264 if (getTime() > 0) {
265 return DateUtils.getRelativeTimeSpanString(getTime(),
266 System.currentTimeMillis(),
267 DateUtils.MINUTE_IN_MILLIS,
268 DateUtils.FORMAT_ABBREV_RELATIVE);
269 }
270 return "";
271 }
272
273 public boolean isRunning() {
274 return mOps.get(0).isRunning();
275 }
276
277 public long getTime() {
278 return mOps.get(0).getTime();
279 }
280
281 @Override public String toString() {
282 return mApp.getLabel();
283 }
284 }
285
286 /**
287 * Perform alphabetical comparison of application entry objects.
288 */
289 public static final Comparator<AppOpEntry> APP_OP_COMPARATOR = new Comparator<AppOpEntry>() {
290 private final Collator sCollator = Collator.getInstance();
291 @Override
292 public int compare(AppOpEntry object1, AppOpEntry object2) {
293 if (object1.isRunning() != object2.isRunning()) {
294 // Currently running ops go first.
295 return object1.isRunning() ? -1 : 1;
296 }
297 if (object1.getTime() != object2.getTime()) {
298 // More recent times go first.
299 return object1.getTime() > object2.getTime() ? -1 : 1;
300 }
301 return sCollator.compare(object1.getAppEntry().getLabel(),
302 object2.getAppEntry().getLabel());
303 }
304 };
305
306 private void addOp(List<AppOpEntry> entries, AppOpsManager.PackageOps pkgOps,
307 AppEntry appEntry, AppOpsManager.OpEntry opEntry) {
308 if (entries.size() > 0) {
309 AppOpEntry last = entries.get(entries.size()-1);
310 if (last.getAppEntry() == appEntry) {
311 boolean lastExe = last.getTime() != 0;
312 boolean entryExe = opEntry.getTime() != 0;
313 if (lastExe == entryExe) {
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800314 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package "
315 + pkgOps.getPackageName() + ": append to " + last);
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800316 last.addOp(opEntry);
317 return;
318 }
319 }
320 }
321 AppOpEntry entry = new AppOpEntry(pkgOps, opEntry, appEntry);
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800322 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package "
323 + pkgOps.getPackageName() + ": making new " + entry);
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800324 entries.add(entry);
325 }
326
327 public List<AppOpEntry> buildState(OpsTemplate tpl) {
328 return buildState(tpl, 0, null);
329 }
330
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800331 private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries,
332 final String packageName, ApplicationInfo appInfo) {
333 AppEntry appEntry = appEntries.get(packageName);
334 if (appEntry == null) {
335 if (appInfo == null) {
336 try {
337 appInfo = mPm.getApplicationInfo(packageName,
338 PackageManager.GET_DISABLED_COMPONENTS
339 | PackageManager.GET_UNINSTALLED_PACKAGES);
340 } catch (PackageManager.NameNotFoundException e) {
341 Log.w(TAG, "Unable to find info for package " + packageName);
342 return null;
343 }
344 }
345 appEntry = new AppEntry(this, appInfo);
346 appEntry.loadLabel(context);
347 appEntries.put(packageName, appEntry);
348 }
349 return appEntry;
350 }
351
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800352 public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) {
353 final Context context = mContext;
354
355 final HashMap<String, AppEntry> appEntries = new HashMap<String, AppEntry>();
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800356 List<AppOpEntry> entries = new ArrayList<AppOpEntry>();
357
358 ArrayList<String> perms = new ArrayList<String>();
359 ArrayList<Integer> permOps = new ArrayList<Integer>();
360 for (int i=0; i<tpl.ops.length; i++) {
361 String perm = AppOpsManager.opToPermission(tpl.ops[i]);
362 if (!perms.contains(perm)) {
363 perms.add(perm);
364 permOps.add(tpl.ops[i]);
365 }
366 }
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800367
368 List<AppOpsManager.PackageOps> pkgs;
369 if (packageName != null) {
370 pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops);
371 } else {
372 pkgs = mAppOps.getPackagesForOps(tpl.ops);
373 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800374
375 if (pkgs != null) {
376 for (int i=0; i<pkgs.size(); i++) {
377 AppOpsManager.PackageOps pkgOps = pkgs.get(i);
378 AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null);
379 if (appEntry == null) {
380 continue;
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800381 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800382 for (int j=0; j<pkgOps.getOps().size(); j++) {
383 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j);
384 addOp(entries, pkgOps, appEntry, opEntry);
385 }
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800386 }
387 }
388
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800389 List<PackageInfo> apps;
390 if (packageName != null) {
391 apps = new ArrayList<PackageInfo>();
392 try {
393 PackageInfo pi = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS);
394 apps.add(pi);
395 } catch (NameNotFoundException e) {
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800396 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800397 } else {
398 String[] permsArray = new String[perms.size()];
399 perms.toArray(permsArray);
400 apps = mPm.getPackagesHoldingPermissions(permsArray, 0);
401 }
402 for (int i=0; i<apps.size(); i++) {
403 PackageInfo appInfo = apps.get(i);
404 AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName,
405 appInfo.applicationInfo);
406 if (appEntry == null) {
407 continue;
408 }
409 List<AppOpsManager.OpEntry> dummyOps = null;
410 AppOpsManager.PackageOps pkgOps = null;
411 for (int j=0; j<appInfo.requestedPermissions.length; j++) {
412 if (appInfo.requestedPermissionsFlags != null) {
413 if ((appInfo.requestedPermissionsFlags[j]
414 & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) {
415 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm "
416 + appInfo.requestedPermissions[j] + " not granted; skipping");
417 break;
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800418 }
419 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800420 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + ": requested perm "
421 + appInfo.requestedPermissions[j]);
422 for (int k=0; k<perms.size(); k++) {
423 if (!perms.get(k).equals(appInfo.requestedPermissions[j])) {
424 continue;
425 }
426 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm " + perms.get(k)
427 + " has op " + permOps.get(k) + ": " + appEntry.hasOp(permOps.get(k)));
428 if (appEntry.hasOp(permOps.get(k))) {
429 continue;
430 }
431 if (dummyOps == null) {
432 dummyOps = new ArrayList<AppOpsManager.OpEntry>();
433 pkgOps = new AppOpsManager.PackageOps(
434 appInfo.packageName, appInfo.applicationInfo.uid, dummyOps);
435
436 }
437 AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry(
438 permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0);
439 dummyOps.add(opEntry);
440 addOp(entries, pkgOps, appEntry, opEntry);
441 }
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800442 }
443 }
444
445 // Sort the list.
446 Collections.sort(entries, APP_OP_COMPARATOR);
447
448 // Done!
449 return entries;
450 }
Dianne Hackborn0dd99022013-01-24 19:14:26 -0800451
452 public CharSequence getLabelText(AppOpsManager.OpEntry op) {
453 return mOpNames[op.getOp()];
454 }
455
456 public CharSequence getTimeText(AppOpsManager.OpEntry op) {
457 if (op.isRunning()) {
458 return mContext.getResources().getText(R.string.app_ops_running);
459 }
460 if (op.getTime() > 0) {
461 return DateUtils.getRelativeTimeSpanString(op.getTime(),
462 System.currentTimeMillis(),
463 DateUtils.MINUTE_IN_MILLIS,
464 DateUtils.FORMAT_ABBREV_RELATIVE);
465 }
466 return mContext.getResources().getText(R.string.app_ops_never_used);
467 }
468
Dianne Hackborn18b64f42013-01-18 10:52:38 -0800469}