AppOpsState.java revision 0dd9902c89e7b705fd73158aadf1bf27843ad201
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 64 public OpsTemplate(int[] _ops) { 65 ops = _ops; 66 } 67 68 OpsTemplate(Parcel src) { 69 ops = src.createIntArray(); 70 } 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); 80 } 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, 96 AppOpsManager.OP_GPS, 97 AppOpsManager.OP_WIFI_SCAN } 98 ); 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, 104 AppOpsManager.OP_WRITE_CALL_LOG, 105 AppOpsManager.OP_READ_CALENDAR, 106 AppOpsManager.OP_WRITE_CALENDAR } 107 ); 108 109 public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate( 110 new int[] { AppOpsManager.OP_VIBRATE } 111 ); 112 113 public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] { 114 LOCATION_TEMPLATE, PERSONAL_TEMPLATE, DEVICE_TEMPLATE 115 }; 116 117 /** 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; 124 private final SparseArray<AppOpsManager.OpEntry> mOps 125 = new SparseArray<AppOpsManager.OpEntry>(); 126 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 136 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 144 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; 205 mApp = app; 206 mApp.addOp(op); 207 mOps.add(op); 208 } 209 210 public void addOp(AppOpsManager.OpEntry op) { 211 mApp.addOp(op); 212 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 } 219 continue; 220 } 221 if (pos.getTime() < op.getTime()) { 222 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 245 public CharSequence getLabelText(AppOpsState state) { 246 if (getNumOpEntry() == 1) { 247 return state.mOpNames[getOpEntry(0).getOp()]; 248 } else { 249 StringBuilder builder = new StringBuilder(); 250 for (int i=0; i<getNumOpEntry(); i++) { 251 if (i > 0) { 252 builder.append(", "); 253 } 254 builder.append(state.mOpNames[getOpEntry(i).getOp()]); 255 } 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) { 314 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 315 + pkgOps.getPackageName() + ": append to " + last); 316 last.addOp(opEntry); 317 return; 318 } 319 } 320 } 321 AppOpEntry entry = new AppOpEntry(pkgOps, opEntry, appEntry); 322 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 323 + pkgOps.getPackageName() + ": making new " + entry); 324 entries.add(entry); 325 } 326 327 public List<AppOpEntry> buildState(OpsTemplate tpl) { 328 return buildState(tpl, 0, null); 329 } 330 331 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 352 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>(); 356 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 } 367 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 } 374 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; 381 } 382 for (int j=0; j<pkgOps.getOps().size(); j++) { 383 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j); 384 addOp(entries, pkgOps, appEntry, opEntry); 385 } 386 } 387 } 388 389 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) { 396 } 397 } 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; 418 } 419 } 420 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 } 442 } 443 } 444 445 // Sort the list. 446 Collections.sort(entries, APP_OP_COMPARATOR); 447 448 // Done! 449 return entries; 450 } 451 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 469} 470