AppOpsState.java revision 27daaab633a80be50863e6539e947db674090662
1/** 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 31import android.util.Log; 32import android.util.SparseArray; 33import 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 { 44 static final String TAG = "AppOpsState"; 45 static final boolean DEBUG = false; 46 47 final Context mContext; 48 final AppOpsManager mAppOps; 49 final PackageManager mPm; 50 final CharSequence[] mOpNames; 51 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(); 58 mOpNames = context.getResources().getTextArray(R.array.app_ops_names); 59 } 60 61 public static class OpsTemplate implements Parcelable { 62 public final int[] ops; 63 public final boolean[] showPerms; 64 65 public OpsTemplate(int[] _ops, boolean[] _showPerms) { 66 ops = _ops; 67 showPerms = _showPerms; 68 } 69 70 OpsTemplate(Parcel src) { 71 ops = src.createIntArray(); 72 showPerms = src.createBooleanArray(); 73 } 74 75 @Override 76 public int describeContents() { 77 return 0; 78 } 79 80 @Override 81 public void writeToParcel(Parcel dest, int flags) { 82 dest.writeIntArray(ops); 83 dest.writeBooleanArray(showPerms); 84 } 85 86 public static final Creator<OpsTemplate> CREATOR = new Creator<OpsTemplate>() { 87 @Override public OpsTemplate createFromParcel(Parcel source) { 88 return new OpsTemplate(source); 89 } 90 91 @Override public OpsTemplate[] newArray(int size) { 92 return new OpsTemplate[size]; 93 } 94 }; 95 } 96 97 public static final OpsTemplate LOCATION_TEMPLATE = new OpsTemplate( 98 new int[] { AppOpsManager.OP_COARSE_LOCATION, 99 AppOpsManager.OP_FINE_LOCATION, 100 AppOpsManager.OP_GPS, 101 AppOpsManager.OP_WIFI_SCAN, 102 AppOpsManager.OP_NEIGHBORING_CELLS }, 103 new boolean[] { true, 104 true, 105 false, 106 false, 107 false } 108 ); 109 110 public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate( 111 new int[] { AppOpsManager.OP_READ_CONTACTS, 112 AppOpsManager.OP_WRITE_CONTACTS, 113 AppOpsManager.OP_READ_CALL_LOG, 114 AppOpsManager.OP_WRITE_CALL_LOG, 115 AppOpsManager.OP_READ_CALENDAR, 116 AppOpsManager.OP_WRITE_CALENDAR }, 117 new boolean[] { true, 118 true, 119 true, 120 true, 121 true, 122 true } 123 ); 124 125 public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate( 126 new int[] { AppOpsManager.OP_VIBRATE, 127 AppOpsManager.OP_POST_NOTIFICATION, 128 AppOpsManager.OP_CALL_PHONE }, 129 new boolean[] { false, 130 false, 131 true } 132 ); 133 134 public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] { 135 LOCATION_TEMPLATE, PERSONAL_TEMPLATE, DEVICE_TEMPLATE 136 }; 137 138 /** 139 * This class holds the per-item data in our Loader. 140 */ 141 public static class AppEntry { 142 private final AppOpsState mState; 143 private final ApplicationInfo mInfo; 144 private final File mApkFile; 145 private final SparseArray<AppOpsManager.OpEntry> mOps 146 = new SparseArray<AppOpsManager.OpEntry>(); 147 private final SparseArray<AppOpEntry> mOpSwitches 148 = new SparseArray<AppOpEntry>(); 149 private String mLabel; 150 private Drawable mIcon; 151 private boolean mMounted; 152 153 public AppEntry(AppOpsState state, ApplicationInfo info) { 154 mState = state; 155 mInfo = info; 156 mApkFile = new File(info.sourceDir); 157 } 158 159 public void addOp(AppOpEntry entry, AppOpsManager.OpEntry op) { 160 mOps.put(op.getOp(), op); 161 mOpSwitches.put(AppOpsManager.opToSwitch(op.getOp()), entry); 162 } 163 164 public boolean hasOp(int op) { 165 return mOps.indexOfKey(op) >= 0; 166 } 167 168 public AppOpEntry getOpSwitch(int op) { 169 return mOpSwitches.get(AppOpsManager.opToSwitch(op)); 170 } 171 172 public ApplicationInfo getApplicationInfo() { 173 return mInfo; 174 } 175 176 public String getLabel() { 177 return mLabel; 178 } 179 180 public Drawable getIcon() { 181 if (mIcon == null) { 182 if (mApkFile.exists()) { 183 mIcon = mInfo.loadIcon(mState.mPm); 184 return mIcon; 185 } else { 186 mMounted = false; 187 } 188 } else if (!mMounted) { 189 // If the app wasn't mounted but is now mounted, reload 190 // its icon. 191 if (mApkFile.exists()) { 192 mMounted = true; 193 mIcon = mInfo.loadIcon(mState.mPm); 194 return mIcon; 195 } 196 } else { 197 return mIcon; 198 } 199 200 return mState.mContext.getResources().getDrawable( 201 android.R.drawable.sym_def_app_icon); 202 } 203 204 @Override public String toString() { 205 return mLabel; 206 } 207 208 void loadLabel(Context context) { 209 if (mLabel == null || !mMounted) { 210 if (!mApkFile.exists()) { 211 mMounted = false; 212 mLabel = mInfo.packageName; 213 } else { 214 mMounted = true; 215 CharSequence label = mInfo.loadLabel(context.getPackageManager()); 216 mLabel = label != null ? label.toString() : mInfo.packageName; 217 } 218 } 219 } 220 } 221 222 /** 223 * This class holds the per-item data in our Loader. 224 */ 225 public static class AppOpEntry { 226 private final AppOpsManager.PackageOps mPkgOps; 227 private final ArrayList<AppOpsManager.OpEntry> mOps 228 = new ArrayList<AppOpsManager.OpEntry>(); 229 private final ArrayList<AppOpsManager.OpEntry> mBriefOps 230 = new ArrayList<AppOpsManager.OpEntry>(); 231 private final AppEntry mApp; 232 233 public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app, 234 boolean brief) { 235 mPkgOps = pkg; 236 mApp = app; 237 mApp.addOp(this, op); 238 mOps.add(op); 239 if (brief) { 240 mBriefOps.add(op); 241 } 242 } 243 244 private static void addOp(ArrayList<AppOpsManager.OpEntry> list, AppOpsManager.OpEntry op) { 245 for (int i=0; i<list.size(); i++) { 246 AppOpsManager.OpEntry pos = list.get(i); 247 if (pos.isRunning() != op.isRunning()) { 248 if (op.isRunning()) { 249 list.add(i, op); 250 return; 251 } 252 continue; 253 } 254 if (pos.getTime() < op.getTime()) { 255 list.add(i, op); 256 return; 257 } 258 } 259 list.add(op); 260 } 261 262 public void addOp(AppOpsManager.OpEntry op, boolean brief) { 263 mApp.addOp(this, op); 264 addOp(mOps, op); 265 if (brief) { 266 addOp(mBriefOps, op); 267 } 268 } 269 270 public AppEntry getAppEntry() { 271 return mApp; 272 } 273 274 public AppOpsManager.PackageOps getPackageOps() { 275 return mPkgOps; 276 } 277 278 public int getNumOpEntry() { 279 return mOps.size(); 280 } 281 282 public AppOpsManager.OpEntry getOpEntry(int pos) { 283 return mOps.get(pos); 284 } 285 286 private CharSequence getLabelText(ArrayList<AppOpsManager.OpEntry> ops, 287 AppOpsState state) { 288 if (ops.size() == 1) { 289 return state.mOpNames[ops.get(0).getOp()]; 290 } else { 291 StringBuilder builder = new StringBuilder(); 292 for (int i=0; i<ops.size(); i++) { 293 if (i > 0) { 294 builder.append(", "); 295 } 296 builder.append(state.mOpNames[ops.get(i).getOp()]); 297 } 298 return builder.toString(); 299 } 300 } 301 302 public CharSequence getLabelText(AppOpsState state) { 303 return getLabelText(mOps, state); 304 } 305 306 public CharSequence getBriefLabelText(AppOpsState state) { 307 if (mBriefOps.size() > 0) { 308 return getLabelText(mBriefOps, state); 309 } else { 310 return getLabelText(mOps, state); 311 } 312 } 313 314 public CharSequence getTimeText(Resources res) { 315 if (isRunning()) { 316 return res.getText(R.string.app_ops_running); 317 } 318 if (getTime() > 0) { 319 return DateUtils.getRelativeTimeSpanString(getTime(), 320 System.currentTimeMillis(), 321 DateUtils.MINUTE_IN_MILLIS, 322 DateUtils.FORMAT_ABBREV_RELATIVE); 323 } 324 return ""; 325 } 326 327 public boolean isRunning() { 328 return mOps.get(0).isRunning(); 329 } 330 331 public long getTime() { 332 return mOps.get(0).getTime(); 333 } 334 335 @Override public String toString() { 336 return mApp.getLabel(); 337 } 338 } 339 340 /** 341 * Perform alphabetical comparison of application entry objects. 342 */ 343 public static final Comparator<AppOpEntry> APP_OP_COMPARATOR = new Comparator<AppOpEntry>() { 344 private final Collator sCollator = Collator.getInstance(); 345 @Override 346 public int compare(AppOpEntry object1, AppOpEntry object2) { 347 if (object1.isRunning() != object2.isRunning()) { 348 // Currently running ops go first. 349 return object1.isRunning() ? -1 : 1; 350 } 351 if (object1.getTime() != object2.getTime()) { 352 // More recent times go first. 353 return object1.getTime() > object2.getTime() ? -1 : 1; 354 } 355 return sCollator.compare(object1.getAppEntry().getLabel(), 356 object2.getAppEntry().getLabel()); 357 } 358 }; 359 360 private void addOp(List<AppOpEntry> entries, AppOpsManager.PackageOps pkgOps, 361 AppEntry appEntry, AppOpsManager.OpEntry opEntry, boolean brief, boolean allowMerge) { 362 if (allowMerge && entries.size() > 0) { 363 AppOpEntry last = entries.get(entries.size()-1); 364 if (last.getAppEntry() == appEntry) { 365 boolean lastExe = last.getTime() != 0; 366 boolean entryExe = opEntry.getTime() != 0; 367 if (lastExe == entryExe) { 368 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 369 + pkgOps.getPackageName() + ": append to " + last); 370 last.addOp(opEntry, brief); 371 return; 372 } 373 } 374 } 375 AppOpEntry entry = appEntry.getOpSwitch(opEntry.getOp()); 376 if (entry != null) { 377 entry.addOp(opEntry, brief); 378 return; 379 } 380 entry = new AppOpEntry(pkgOps, opEntry, appEntry, brief); 381 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 382 + pkgOps.getPackageName() + ": making new " + entry); 383 entries.add(entry); 384 } 385 386 public List<AppOpEntry> buildState(OpsTemplate tpl) { 387 return buildState(tpl, 0, null); 388 } 389 390 private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries, 391 final String packageName, ApplicationInfo appInfo) { 392 AppEntry appEntry = appEntries.get(packageName); 393 if (appEntry == null) { 394 if (appInfo == null) { 395 try { 396 appInfo = mPm.getApplicationInfo(packageName, 397 PackageManager.GET_DISABLED_COMPONENTS 398 | PackageManager.GET_UNINSTALLED_PACKAGES); 399 } catch (PackageManager.NameNotFoundException e) { 400 Log.w(TAG, "Unable to find info for package " + packageName); 401 return null; 402 } 403 } 404 appEntry = new AppEntry(this, appInfo); 405 appEntry.loadLabel(context); 406 appEntries.put(packageName, appEntry); 407 } 408 return appEntry; 409 } 410 411 public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) { 412 final Context context = mContext; 413 414 final HashMap<String, AppEntry> appEntries = new HashMap<String, AppEntry>(); 415 final List<AppOpEntry> entries = new ArrayList<AppOpEntry>(); 416 417 final ArrayList<String> perms = new ArrayList<String>(); 418 final ArrayList<Integer> permOps = new ArrayList<Integer>(); 419 final boolean[] brief = new boolean[AppOpsManager._NUM_OP]; 420 for (int i=0; i<tpl.ops.length; i++) { 421 brief[tpl.ops[i]] = tpl.showPerms[i]; 422 if (tpl.showPerms[i]) { 423 String perm = AppOpsManager.opToPermission(tpl.ops[i]); 424 if (perm != null && !perms.contains(perm)) { 425 perms.add(perm); 426 permOps.add(tpl.ops[i]); 427 } 428 } 429 } 430 431 List<AppOpsManager.PackageOps> pkgs; 432 if (packageName != null) { 433 pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops); 434 } else { 435 pkgs = mAppOps.getPackagesForOps(tpl.ops); 436 } 437 438 if (pkgs != null) { 439 for (int i=0; i<pkgs.size(); i++) { 440 AppOpsManager.PackageOps pkgOps = pkgs.get(i); 441 AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null); 442 if (appEntry == null) { 443 continue; 444 } 445 for (int j=0; j<pkgOps.getOps().size(); j++) { 446 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j); 447 addOp(entries, pkgOps, appEntry, opEntry, brief[opEntry.getOp()], 448 packageName == null); 449 } 450 } 451 } 452 453 List<PackageInfo> apps; 454 if (packageName != null) { 455 apps = new ArrayList<PackageInfo>(); 456 try { 457 PackageInfo pi = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); 458 apps.add(pi); 459 } catch (NameNotFoundException e) { 460 } 461 } else { 462 String[] permsArray = new String[perms.size()]; 463 perms.toArray(permsArray); 464 apps = mPm.getPackagesHoldingPermissions(permsArray, 0); 465 } 466 for (int i=0; i<apps.size(); i++) { 467 PackageInfo appInfo = apps.get(i); 468 AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName, 469 appInfo.applicationInfo); 470 if (appEntry == null) { 471 continue; 472 } 473 List<AppOpsManager.OpEntry> dummyOps = null; 474 AppOpsManager.PackageOps pkgOps = null; 475 if (appInfo.requestedPermissions != null) { 476 for (int j=0; j<appInfo.requestedPermissions.length; j++) { 477 if (appInfo.requestedPermissionsFlags != null) { 478 if ((appInfo.requestedPermissionsFlags[j] 479 & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { 480 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm " 481 + appInfo.requestedPermissions[j] + " not granted; skipping"); 482 break; 483 } 484 } 485 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + ": requested perm " 486 + appInfo.requestedPermissions[j]); 487 for (int k=0; k<perms.size(); k++) { 488 if (!perms.get(k).equals(appInfo.requestedPermissions[j])) { 489 continue; 490 } 491 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm " + perms.get(k) 492 + " has op " + permOps.get(k) + ": " + appEntry.hasOp(permOps.get(k))); 493 if (appEntry.hasOp(permOps.get(k))) { 494 continue; 495 } 496 if (dummyOps == null) { 497 dummyOps = new ArrayList<AppOpsManager.OpEntry>(); 498 pkgOps = new AppOpsManager.PackageOps( 499 appInfo.packageName, appInfo.applicationInfo.uid, dummyOps); 500 501 } 502 AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry( 503 permOps.get(k), AppOpsManager.MODE_ALLOWED, 0, 0, 0); 504 dummyOps.add(opEntry); 505 addOp(entries, pkgOps, appEntry, opEntry, brief[opEntry.getOp()], 506 packageName == null); 507 } 508 } 509 } 510 } 511 512 // Sort the list. 513 Collections.sort(entries, APP_OP_COMPARATOR); 514 515 // Done! 516 return entries; 517 } 518 519 public CharSequence getLabelText(AppOpsManager.OpEntry op) { 520 return mOpNames[op.getOp()]; 521 } 522 523 public CharSequence getTimeText(AppOpsManager.OpEntry op) { 524 if (op.isRunning()) { 525 return mContext.getResources().getText(R.string.app_ops_running); 526 } 527 if (op.getTime() > 0) { 528 return DateUtils.getRelativeTimeSpanString(op.getTime(), 529 System.currentTimeMillis(), 530 DateUtils.MINUTE_IN_MILLIS, 531 DateUtils.FORMAT_ABBREV_RELATIVE); 532 } 533 return mContext.getResources().getText(R.string.app_ops_never_used); 534 } 535 536} 537