1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.applications.appops; 18 19 import android.app.AppOpsManager; 20 import android.content.Context; 21 import android.content.pm.ApplicationInfo; 22 import android.content.pm.PackageInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.PackageManager.NameNotFoundException; 25 import android.content.res.Resources; 26 import android.graphics.drawable.Drawable; 27 import android.os.Parcel; 28 import android.os.Parcelable; 29 import android.text.format.DateUtils; 30 import android.util.Log; 31 import android.util.SparseArray; 32 33 import com.android.settings.R; 34 35 import java.io.File; 36 import java.text.Collator; 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.Comparator; 40 import java.util.HashMap; 41 import java.util.List; 42 43 public 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[] mOpSummaries; 51 final CharSequence[] mOpLabels; 52 AppOpsState(Context context)53 public AppOpsState(Context context) { 54 mContext = context; 55 mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); 56 mPm = context.getPackageManager(); 57 mOpSummaries = context.getResources().getTextArray(R.array.app_ops_summaries); 58 mOpLabels = context.getResources().getTextArray(R.array.app_ops_labels); 59 } 60 61 public static class OpsTemplate implements Parcelable { 62 public final int[] ops; 63 public final boolean[] showPerms; 64 OpsTemplate(int[] _ops, boolean[] _showPerms)65 public OpsTemplate(int[] _ops, boolean[] _showPerms) { 66 ops = _ops; 67 showPerms = _showPerms; 68 } 69 OpsTemplate(Parcel src)70 OpsTemplate(Parcel src) { 71 ops = src.createIntArray(); 72 showPerms = src.createBooleanArray(); 73 } 74 75 @Override describeContents()76 public int describeContents() { 77 return 0; 78 } 79 80 @Override writeToParcel(Parcel dest, int flags)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 AppOpsManager.OP_MONITOR_LOCATION, 104 AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION }, 105 new boolean[] { true, 106 true, 107 false, 108 false, 109 false, 110 false, 111 false } 112 ); 113 114 public static final OpsTemplate PERSONAL_TEMPLATE = new OpsTemplate( 115 new int[] { AppOpsManager.OP_READ_CONTACTS, 116 AppOpsManager.OP_WRITE_CONTACTS, 117 AppOpsManager.OP_READ_CALL_LOG, 118 AppOpsManager.OP_WRITE_CALL_LOG, 119 AppOpsManager.OP_READ_CALENDAR, 120 AppOpsManager.OP_WRITE_CALENDAR, 121 AppOpsManager.OP_READ_CLIPBOARD, 122 AppOpsManager.OP_WRITE_CLIPBOARD }, 123 new boolean[] { true, 124 true, 125 true, 126 true, 127 true, 128 true, 129 false, 130 false } 131 ); 132 133 public static final OpsTemplate MESSAGING_TEMPLATE = new OpsTemplate( 134 new int[] { AppOpsManager.OP_READ_SMS, 135 AppOpsManager.OP_RECEIVE_SMS, 136 AppOpsManager.OP_RECEIVE_EMERGECY_SMS, 137 AppOpsManager.OP_RECEIVE_MMS, 138 AppOpsManager.OP_RECEIVE_WAP_PUSH, 139 AppOpsManager.OP_WRITE_SMS, 140 AppOpsManager.OP_SEND_SMS, 141 AppOpsManager.OP_READ_ICC_SMS, 142 AppOpsManager.OP_WRITE_ICC_SMS }, 143 new boolean[] { true, 144 true, 145 true, 146 true, 147 true, 148 true, 149 true, 150 true, 151 true } 152 ); 153 154 public static final OpsTemplate MEDIA_TEMPLATE = new OpsTemplate( 155 new int[] { AppOpsManager.OP_VIBRATE, 156 AppOpsManager.OP_CAMERA, 157 AppOpsManager.OP_RECORD_AUDIO, 158 AppOpsManager.OP_PLAY_AUDIO, 159 AppOpsManager.OP_TAKE_MEDIA_BUTTONS, 160 AppOpsManager.OP_TAKE_AUDIO_FOCUS, 161 AppOpsManager.OP_AUDIO_MASTER_VOLUME, 162 AppOpsManager.OP_AUDIO_VOICE_VOLUME, 163 AppOpsManager.OP_AUDIO_RING_VOLUME, 164 AppOpsManager.OP_AUDIO_MEDIA_VOLUME, 165 AppOpsManager.OP_AUDIO_ALARM_VOLUME, 166 AppOpsManager.OP_AUDIO_NOTIFICATION_VOLUME, 167 AppOpsManager.OP_AUDIO_BLUETOOTH_VOLUME, 168 AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME, 169 AppOpsManager.OP_MUTE_MICROPHONE}, 170 new boolean[] { false, 171 true, 172 true, 173 false, 174 false, 175 false, 176 false, 177 false, 178 false, 179 false, 180 false, 181 false, 182 false, 183 false } 184 ); 185 186 public static final OpsTemplate DEVICE_TEMPLATE = new OpsTemplate( 187 new int[] { AppOpsManager.OP_POST_NOTIFICATION, 188 AppOpsManager.OP_ACCESS_NOTIFICATIONS, 189 AppOpsManager.OP_CALL_PHONE, 190 AppOpsManager.OP_WRITE_SETTINGS, 191 AppOpsManager.OP_SYSTEM_ALERT_WINDOW, 192 AppOpsManager.OP_WAKE_LOCK, 193 AppOpsManager.OP_PROJECT_MEDIA, 194 AppOpsManager.OP_ACTIVATE_VPN, 195 AppOpsManager.OP_ASSIST_STRUCTURE, 196 AppOpsManager.OP_ASSIST_SCREENSHOT}, 197 new boolean[] { false, 198 true, 199 true, 200 true, 201 true, 202 true, 203 false, 204 false, 205 false, 206 false } 207 ); 208 209 public static final OpsTemplate RUN_IN_BACKGROUND_TEMPLATE = new OpsTemplate( 210 new int[] { AppOpsManager.OP_RUN_IN_BACKGROUND }, 211 new boolean[] { false } 212 ); 213 214 public static final OpsTemplate[] ALL_TEMPLATES = new OpsTemplate[] { 215 LOCATION_TEMPLATE, PERSONAL_TEMPLATE, MESSAGING_TEMPLATE, 216 MEDIA_TEMPLATE, DEVICE_TEMPLATE, RUN_IN_BACKGROUND_TEMPLATE 217 }; 218 219 /** 220 * This class holds the per-item data in our Loader. 221 */ 222 public static class AppEntry { 223 private final AppOpsState mState; 224 private final ApplicationInfo mInfo; 225 private final File mApkFile; 226 private final SparseArray<AppOpsManager.OpEntry> mOps 227 = new SparseArray<AppOpsManager.OpEntry>(); 228 private final SparseArray<AppOpEntry> mOpSwitches 229 = new SparseArray<AppOpEntry>(); 230 private String mLabel; 231 private Drawable mIcon; 232 private boolean mMounted; 233 AppEntry(AppOpsState state, ApplicationInfo info)234 public AppEntry(AppOpsState state, ApplicationInfo info) { 235 mState = state; 236 mInfo = info; 237 mApkFile = new File(info.sourceDir); 238 } 239 addOp(AppOpEntry entry, AppOpsManager.OpEntry op)240 public void addOp(AppOpEntry entry, AppOpsManager.OpEntry op) { 241 mOps.put(op.getOp(), op); 242 mOpSwitches.put(AppOpsManager.opToSwitch(op.getOp()), entry); 243 } 244 hasOp(int op)245 public boolean hasOp(int op) { 246 return mOps.indexOfKey(op) >= 0; 247 } 248 getOpSwitch(int op)249 public AppOpEntry getOpSwitch(int op) { 250 return mOpSwitches.get(AppOpsManager.opToSwitch(op)); 251 } 252 getApplicationInfo()253 public ApplicationInfo getApplicationInfo() { 254 return mInfo; 255 } 256 getLabel()257 public String getLabel() { 258 return mLabel; 259 } 260 getIcon()261 public Drawable getIcon() { 262 if (mIcon == null) { 263 if (mApkFile.exists()) { 264 mIcon = mInfo.loadIcon(mState.mPm); 265 return mIcon; 266 } else { 267 mMounted = false; 268 } 269 } else if (!mMounted) { 270 // If the app wasn't mounted but is now mounted, reload 271 // its icon. 272 if (mApkFile.exists()) { 273 mMounted = true; 274 mIcon = mInfo.loadIcon(mState.mPm); 275 return mIcon; 276 } 277 } else { 278 return mIcon; 279 } 280 281 return mState.mContext.getDrawable( 282 android.R.drawable.sym_def_app_icon); 283 } 284 toString()285 @Override public String toString() { 286 return mLabel; 287 } 288 loadLabel(Context context)289 void loadLabel(Context context) { 290 if (mLabel == null || !mMounted) { 291 if (!mApkFile.exists()) { 292 mMounted = false; 293 mLabel = mInfo.packageName; 294 } else { 295 mMounted = true; 296 CharSequence label = mInfo.loadLabel(context.getPackageManager()); 297 mLabel = label != null ? label.toString() : mInfo.packageName; 298 } 299 } 300 } 301 } 302 303 /** 304 * This class holds the per-item data in our Loader. 305 */ 306 public static class AppOpEntry { 307 private final AppOpsManager.PackageOps mPkgOps; 308 private final ArrayList<AppOpsManager.OpEntry> mOps 309 = new ArrayList<AppOpsManager.OpEntry>(); 310 private final ArrayList<AppOpsManager.OpEntry> mSwitchOps 311 = new ArrayList<AppOpsManager.OpEntry>(); 312 private final AppEntry mApp; 313 private final int mSwitchOrder; 314 private int mOverriddenPrimaryMode = -1; 315 AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app, int switchOrder)316 public AppOpEntry(AppOpsManager.PackageOps pkg, AppOpsManager.OpEntry op, AppEntry app, 317 int switchOrder) { 318 mPkgOps = pkg; 319 mApp = app; 320 mSwitchOrder = switchOrder; 321 mApp.addOp(this, op); 322 mOps.add(op); 323 mSwitchOps.add(op); 324 } 325 addOp(ArrayList<AppOpsManager.OpEntry> list, AppOpsManager.OpEntry op)326 private static void addOp(ArrayList<AppOpsManager.OpEntry> list, AppOpsManager.OpEntry op) { 327 for (int i=0; i<list.size(); i++) { 328 AppOpsManager.OpEntry pos = list.get(i); 329 if (pos.isRunning() != op.isRunning()) { 330 if (op.isRunning()) { 331 list.add(i, op); 332 return; 333 } 334 continue; 335 } 336 if (pos.getTime() < op.getTime()) { 337 list.add(i, op); 338 return; 339 } 340 } 341 list.add(op); 342 } 343 addOp(AppOpsManager.OpEntry op)344 public void addOp(AppOpsManager.OpEntry op) { 345 mApp.addOp(this, op); 346 addOp(mOps, op); 347 if (mApp.getOpSwitch(AppOpsManager.opToSwitch(op.getOp())) == null) { 348 addOp(mSwitchOps, op); 349 } 350 } 351 getAppEntry()352 public AppEntry getAppEntry() { 353 return mApp; 354 } 355 getSwitchOrder()356 public int getSwitchOrder() { 357 return mSwitchOrder; 358 } 359 getPackageOps()360 public AppOpsManager.PackageOps getPackageOps() { 361 return mPkgOps; 362 } 363 getNumOpEntry()364 public int getNumOpEntry() { 365 return mOps.size(); 366 } 367 getOpEntry(int pos)368 public AppOpsManager.OpEntry getOpEntry(int pos) { 369 return mOps.get(pos); 370 } 371 getPrimaryOpMode()372 public int getPrimaryOpMode() { 373 return mOverriddenPrimaryMode >= 0 ? mOverriddenPrimaryMode : mOps.get(0).getMode(); 374 } 375 overridePrimaryOpMode(int mode)376 public void overridePrimaryOpMode(int mode) { 377 mOverriddenPrimaryMode = mode; 378 } 379 getCombinedText(ArrayList<AppOpsManager.OpEntry> ops, CharSequence[] items)380 private CharSequence getCombinedText(ArrayList<AppOpsManager.OpEntry> ops, 381 CharSequence[] items) { 382 if (ops.size() == 1) { 383 return items[ops.get(0).getOp()]; 384 } else { 385 StringBuilder builder = new StringBuilder(); 386 for (int i=0; i<ops.size(); i++) { 387 if (i > 0) { 388 builder.append(", "); 389 } 390 builder.append(items[ops.get(i).getOp()]); 391 } 392 return builder.toString(); 393 } 394 } 395 getSummaryText(AppOpsState state)396 public CharSequence getSummaryText(AppOpsState state) { 397 return getCombinedText(mOps, state.mOpSummaries); 398 } 399 getSwitchText(AppOpsState state)400 public CharSequence getSwitchText(AppOpsState state) { 401 if (mSwitchOps.size() > 0) { 402 return getCombinedText(mSwitchOps, state.mOpLabels); 403 } else { 404 return getCombinedText(mOps, state.mOpLabels); 405 } 406 } 407 getTimeText(Resources res, boolean showEmptyText)408 public CharSequence getTimeText(Resources res, boolean showEmptyText) { 409 if (isRunning()) { 410 return res.getText(R.string.app_ops_running); 411 } 412 if (getTime() > 0) { 413 return DateUtils.getRelativeTimeSpanString(getTime(), 414 System.currentTimeMillis(), 415 DateUtils.MINUTE_IN_MILLIS, 416 DateUtils.FORMAT_ABBREV_RELATIVE); 417 } 418 return showEmptyText ? res.getText(R.string.app_ops_never_used) : ""; 419 } 420 isRunning()421 public boolean isRunning() { 422 return mOps.get(0).isRunning(); 423 } 424 getTime()425 public long getTime() { 426 return mOps.get(0).getTime(); 427 } 428 toString()429 @Override public String toString() { 430 return mApp.getLabel(); 431 } 432 } 433 434 /** 435 * Perform app op state comparison of application entry objects. 436 */ 437 public static final Comparator<AppOpEntry> RECENCY_COMPARATOR = new Comparator<AppOpEntry>() { 438 private final Collator sCollator = Collator.getInstance(); 439 @Override 440 public int compare(AppOpEntry object1, AppOpEntry object2) { 441 if (object1.getSwitchOrder() != object2.getSwitchOrder()) { 442 return object1.getSwitchOrder() < object2.getSwitchOrder() ? -1 : 1; 443 } 444 if (object1.isRunning() != object2.isRunning()) { 445 // Currently running ops go first. 446 return object1.isRunning() ? -1 : 1; 447 } 448 if (object1.getTime() != object2.getTime()) { 449 // More recent times go first. 450 return object1.getTime() > object2.getTime() ? -1 : 1; 451 } 452 return sCollator.compare(object1.getAppEntry().getLabel(), 453 object2.getAppEntry().getLabel()); 454 } 455 }; 456 457 /** 458 * Perform alphabetical comparison of application entry objects. 459 */ 460 public static final Comparator<AppOpEntry> LABEL_COMPARATOR = new Comparator<AppOpEntry>() { 461 private final Collator sCollator = Collator.getInstance(); 462 @Override 463 public int compare(AppOpEntry object1, AppOpEntry object2) { 464 return sCollator.compare(object1.getAppEntry().getLabel(), 465 object2.getAppEntry().getLabel()); 466 } 467 }; 468 addOp(List<AppOpEntry> entries, AppOpsManager.PackageOps pkgOps, AppEntry appEntry, AppOpsManager.OpEntry opEntry, boolean allowMerge, int switchOrder)469 private void addOp(List<AppOpEntry> entries, AppOpsManager.PackageOps pkgOps, 470 AppEntry appEntry, AppOpsManager.OpEntry opEntry, boolean allowMerge, int switchOrder) { 471 if (allowMerge && entries.size() > 0) { 472 AppOpEntry last = entries.get(entries.size()-1); 473 if (last.getAppEntry() == appEntry) { 474 boolean lastExe = last.getTime() != 0; 475 boolean entryExe = opEntry.getTime() != 0; 476 if (lastExe == entryExe) { 477 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 478 + pkgOps.getPackageName() + ": append to " + last); 479 last.addOp(opEntry); 480 return; 481 } 482 } 483 } 484 AppOpEntry entry = appEntry.getOpSwitch(opEntry.getOp()); 485 if (entry != null) { 486 entry.addOp(opEntry); 487 return; 488 } 489 entry = new AppOpEntry(pkgOps, opEntry, appEntry, switchOrder); 490 if (DEBUG) Log.d(TAG, "Add op " + opEntry.getOp() + " to package " 491 + pkgOps.getPackageName() + ": making new " + entry); 492 entries.add(entry); 493 } 494 getAppOpsManager()495 public AppOpsManager getAppOpsManager() { 496 return mAppOps; 497 } 498 buildState(OpsTemplate tpl)499 public List<AppOpEntry> buildState(OpsTemplate tpl) { 500 return buildState(tpl, 0, null, RECENCY_COMPARATOR); 501 } 502 getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries, final String packageName, ApplicationInfo appInfo)503 private AppEntry getAppEntry(final Context context, final HashMap<String, AppEntry> appEntries, 504 final String packageName, ApplicationInfo appInfo) { 505 AppEntry appEntry = appEntries.get(packageName); 506 if (appEntry == null) { 507 if (appInfo == null) { 508 try { 509 appInfo = mPm.getApplicationInfo(packageName, 510 PackageManager.MATCH_DISABLED_COMPONENTS 511 | PackageManager.MATCH_ANY_USER); 512 } catch (PackageManager.NameNotFoundException e) { 513 Log.w(TAG, "Unable to find info for package " + packageName); 514 return null; 515 } 516 } 517 appEntry = new AppEntry(this, appInfo); 518 appEntry.loadLabel(context); 519 appEntries.put(packageName, appEntry); 520 } 521 return appEntry; 522 } 523 buildState(OpsTemplate tpl, int uid, String packageName)524 public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName) { 525 return buildState(tpl, uid, packageName, RECENCY_COMPARATOR); 526 } 527 buildState(OpsTemplate tpl, int uid, String packageName, Comparator<AppOpEntry> comparator)528 public List<AppOpEntry> buildState(OpsTemplate tpl, int uid, String packageName, 529 Comparator<AppOpEntry> comparator) { 530 final Context context = mContext; 531 532 final HashMap<String, AppEntry> appEntries = new HashMap<String, AppEntry>(); 533 final List<AppOpEntry> entries = new ArrayList<AppOpEntry>(); 534 535 final ArrayList<String> perms = new ArrayList<String>(); 536 final ArrayList<Integer> permOps = new ArrayList<Integer>(); 537 final int[] opToOrder = new int[AppOpsManager._NUM_OP]; 538 for (int i=0; i<tpl.ops.length; i++) { 539 if (tpl.showPerms[i]) { 540 String perm = AppOpsManager.opToPermission(tpl.ops[i]); 541 if (perm != null && !perms.contains(perm)) { 542 perms.add(perm); 543 permOps.add(tpl.ops[i]); 544 opToOrder[tpl.ops[i]] = i; 545 } 546 } 547 } 548 549 List<AppOpsManager.PackageOps> pkgs; 550 if (packageName != null) { 551 pkgs = mAppOps.getOpsForPackage(uid, packageName, tpl.ops); 552 } else { 553 pkgs = mAppOps.getPackagesForOps(tpl.ops); 554 } 555 556 if (pkgs != null) { 557 for (int i=0; i<pkgs.size(); i++) { 558 AppOpsManager.PackageOps pkgOps = pkgs.get(i); 559 AppEntry appEntry = getAppEntry(context, appEntries, pkgOps.getPackageName(), null); 560 if (appEntry == null) { 561 continue; 562 } 563 for (int j=0; j<pkgOps.getOps().size(); j++) { 564 AppOpsManager.OpEntry opEntry = pkgOps.getOps().get(j); 565 addOp(entries, pkgOps, appEntry, opEntry, packageName == null, 566 packageName == null ? 0 : opToOrder[opEntry.getOp()]); 567 } 568 } 569 } 570 571 List<PackageInfo> apps; 572 if (packageName != null) { 573 apps = new ArrayList<PackageInfo>(); 574 try { 575 PackageInfo pi = mPm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS); 576 apps.add(pi); 577 } catch (NameNotFoundException e) { 578 } 579 } else { 580 String[] permsArray = new String[perms.size()]; 581 perms.toArray(permsArray); 582 apps = mPm.getPackagesHoldingPermissions(permsArray, 0); 583 } 584 for (int i=0; i<apps.size(); i++) { 585 PackageInfo appInfo = apps.get(i); 586 AppEntry appEntry = getAppEntry(context, appEntries, appInfo.packageName, 587 appInfo.applicationInfo); 588 if (appEntry == null) { 589 continue; 590 } 591 List<AppOpsManager.OpEntry> stubOps = null; 592 AppOpsManager.PackageOps pkgOps = null; 593 if (appInfo.requestedPermissions != null) { 594 for (int j=0; j<appInfo.requestedPermissions.length; j++) { 595 if (appInfo.requestedPermissionsFlags != null) { 596 if ((appInfo.requestedPermissionsFlags[j] 597 & PackageInfo.REQUESTED_PERMISSION_GRANTED) == 0) { 598 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm " 599 + appInfo.requestedPermissions[j] + " not granted; skipping"); 600 continue; 601 } 602 } 603 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + ": requested perm " 604 + appInfo.requestedPermissions[j]); 605 for (int k=0; k<perms.size(); k++) { 606 if (!perms.get(k).equals(appInfo.requestedPermissions[j])) { 607 continue; 608 } 609 if (DEBUG) Log.d(TAG, "Pkg " + appInfo.packageName + " perm " + perms.get(k) 610 + " has op " + permOps.get(k) + ": " + appEntry.hasOp(permOps.get(k))); 611 if (appEntry.hasOp(permOps.get(k))) { 612 continue; 613 } 614 if (stubOps == null) { 615 stubOps = new ArrayList<AppOpsManager.OpEntry>(); 616 pkgOps = new AppOpsManager.PackageOps( 617 appInfo.packageName, appInfo.applicationInfo.uid, stubOps); 618 619 } 620 AppOpsManager.OpEntry opEntry = new AppOpsManager.OpEntry( 621 permOps.get(k), AppOpsManager.MODE_ALLOWED, Collections.emptyMap()); 622 stubOps.add(opEntry); 623 addOp(entries, pkgOps, appEntry, opEntry, packageName == null, 624 packageName == null ? 0 : opToOrder[opEntry.getOp()]); 625 } 626 } 627 } 628 } 629 630 // Sort the list. 631 Collections.sort(entries, comparator); 632 633 // Done! 634 return entries; 635 } 636 } 637