1 /* 2 * Copyright (C) 2022 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.server.am; 18 19 import static android.Manifest.permission.ACCESS_FINE_LOCATION; 20 import static android.Manifest.permission.CAMERA; 21 import static android.Manifest.permission.RECORD_AUDIO; 22 import static android.app.AppOpsManager.OPSTR_CAMERA; 23 import static android.app.AppOpsManager.OPSTR_FINE_LOCATION; 24 import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO; 25 import static android.app.AppOpsManager.OP_NONE; 26 import static android.app.AppOpsManager.opToPublicName; 27 import static android.app.AppOpsManager.strOpToOp; 28 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 29 import static android.os.Process.SYSTEM_UID; 30 31 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 32 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 33 import static com.android.server.am.AppBatteryExemptionTracker.DEFAULT_NAME; 34 import static com.android.server.am.AppRestrictionController.DEVICE_CONFIG_SUBNAMESPACE_PREFIX; 35 import static com.android.server.am.BaseAppStateTracker.STATE_TYPE_PERMISSION; 36 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.app.AppOpsManager; 40 import android.content.Context; 41 import android.content.pm.ApplicationInfo; 42 import android.content.pm.PackageManager.OnPermissionsChangedListener; 43 import android.content.pm.PackageManagerInternal; 44 import android.os.Handler; 45 import android.os.Message; 46 import android.os.RemoteException; 47 import android.os.SystemClock; 48 import android.os.UserHandle; 49 import android.permission.PermissionManager; 50 import android.provider.DeviceConfig; 51 import android.text.TextUtils; 52 import android.util.ArraySet; 53 import android.util.Pair; 54 import android.util.Slog; 55 import android.util.SparseArray; 56 57 import com.android.internal.annotations.GuardedBy; 58 import com.android.internal.app.IAppOpsCallback; 59 import com.android.internal.app.IAppOpsService; 60 import com.android.server.am.AppPermissionTracker.AppPermissionPolicy; 61 import com.android.server.am.AppRestrictionController.TrackerType; 62 import com.android.server.pm.permission.PermissionManagerServiceInternal; 63 64 import java.io.PrintWriter; 65 import java.lang.reflect.Constructor; 66 import java.util.ArrayList; 67 import java.util.Arrays; 68 import java.util.List; 69 import java.util.Objects; 70 71 /** 72 * The tracker for monitoring selected permission state of apps. 73 */ 74 final class AppPermissionTracker extends BaseAppStateTracker<AppPermissionPolicy> 75 implements OnPermissionsChangedListener { 76 static final String TAG = TAG_WITH_CLASS_NAME ? "AppPermissionTracker" : TAG_AM; 77 78 static final boolean DEBUG_PERMISSION_TRACKER = false; 79 80 private final MyHandler mHandler; 81 82 /** 83 * Keep a new instance of callback for each appop we're monitoring, 84 * as the AppOpsService doesn't support monitoring multiple appops with single callback 85 * instance (except the ALL_OPS case). 86 */ 87 @GuardedBy("mAppOpsCallbacks") 88 private final SparseArray<MyAppOpsCallback> mAppOpsCallbacks = new SparseArray<>(); 89 90 @GuardedBy("mLock") 91 private SparseArray<ArraySet<UidGrantedPermissionState>> mUidGrantedPermissionsInMonitor = 92 new SparseArray<>(); 93 94 private volatile boolean mLockedBootCompleted = false; 95 AppPermissionTracker(Context context, AppRestrictionController controller)96 AppPermissionTracker(Context context, AppRestrictionController controller) { 97 this(context, controller, null, null); 98 } 99 AppPermissionTracker(Context context, AppRestrictionController controller, Constructor<? extends Injector<AppPermissionPolicy>> injector, Object outerContext)100 AppPermissionTracker(Context context, AppRestrictionController controller, 101 Constructor<? extends Injector<AppPermissionPolicy>> injector, Object outerContext) { 102 super(context, controller, injector, outerContext); 103 mHandler = new MyHandler(this); 104 mInjector.setPolicy(new AppPermissionPolicy(mInjector, this)); 105 } 106 107 @Override getType()108 @TrackerType int getType() { 109 return AppRestrictionController.TRACKER_TYPE_PERMISSION; 110 } 111 112 @Override onPermissionsChanged(int uid)113 public void onPermissionsChanged(int uid) { 114 mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget(); 115 } 116 handleAppOpsInit()117 private void handleAppOpsInit() { 118 final ArrayList<Integer> ops = new ArrayList<>(); 119 final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); 120 for (int i = 0; i < permissions.length; i++) { 121 final Pair<String, Integer> pair = permissions[i]; 122 if (pair.second != OP_NONE) { 123 ops.add(pair.second); 124 } 125 } 126 startWatchingMode(ops.toArray(new Integer[ops.size()])); 127 } 128 handlePermissionsInit()129 private void handlePermissionsInit() { 130 final int[] allUsers = mInjector.getUserManagerInternal().getUserIds(); 131 final PackageManagerInternal pmi = mInjector.getPackageManagerInternal(); 132 final PermissionManagerServiceInternal pm = mInjector.getPermissionManagerServiceInternal(); 133 final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); 134 final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms = 135 mUidGrantedPermissionsInMonitor; 136 for (int userId : allUsers) { 137 final List<ApplicationInfo> apps = pmi.getInstalledApplications(0, userId, SYSTEM_UID); 138 if (apps == null) { 139 continue; 140 } 141 final long now = SystemClock.elapsedRealtime(); 142 for (int i = 0, size = apps.size(); i < size; i++) { 143 final ApplicationInfo ai = apps.get(i); 144 for (Pair<String, Integer> permission : permissions) { 145 final UidGrantedPermissionState state = new UidGrantedPermissionState( 146 ai.uid, permission.first, permission.second); 147 if (!state.isGranted()) { 148 // No need to track it. 149 continue; 150 } 151 synchronized (mLock) { 152 ArraySet<UidGrantedPermissionState> grantedPermissions = 153 uidPerms.get(ai.uid); 154 if (grantedPermissions == null) { 155 grantedPermissions = new ArraySet<UidGrantedPermissionState>(); 156 uidPerms.put(ai.uid, grantedPermissions); 157 // This UID has at least one active permission-in-interest now, 158 // let the listeners know. 159 notifyListenersOnStateChange(ai.uid, DEFAULT_NAME, true, now, 160 STATE_TYPE_PERMISSION); 161 } 162 grantedPermissions.add(state); 163 } 164 } 165 } 166 } 167 } 168 handleAppOpsDestroy()169 private void handleAppOpsDestroy() { 170 stopWatchingMode(); 171 } 172 handlePermissionsDestroy()173 private void handlePermissionsDestroy() { 174 synchronized (mLock) { 175 final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms = 176 mUidGrantedPermissionsInMonitor; 177 final long now = SystemClock.elapsedRealtime(); 178 for (int i = 0, size = uidPerms.size(); i < size; i++) { 179 final int uid = uidPerms.keyAt(i); 180 final ArraySet<UidGrantedPermissionState> grantedPermissions = uidPerms.valueAt(i); 181 if (grantedPermissions.size() > 0) { 182 notifyListenersOnStateChange(uid, DEFAULT_NAME, false, now, 183 STATE_TYPE_PERMISSION); 184 } 185 } 186 uidPerms.clear(); 187 } 188 } 189 handleOpChanged(int op, int uid, String packageName)190 private void handleOpChanged(int op, int uid, String packageName) { 191 if (DEBUG_PERMISSION_TRACKER) { 192 final IAppOpsService appOpsService = mInjector.getIAppOpsService(); 193 try { 194 final int mode = appOpsService.checkOperation(op, uid, packageName); 195 Slog.i(TAG, "onOpChanged: " + opToPublicName(op) 196 + " " + UserHandle.formatUid(uid) 197 + " " + packageName + " " + mode); 198 } catch (RemoteException e) { 199 // Intra-process call, should never happen. 200 } 201 } 202 final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); 203 if (permissions != null && permissions.length > 0) { 204 for (int i = 0; i < permissions.length; i++) { 205 final Pair<String, Integer> pair = permissions[i]; 206 if (pair.second != op) { 207 continue; 208 } 209 final UidGrantedPermissionState state = 210 new UidGrantedPermissionState(uid, pair.first, op); 211 synchronized (mLock) { 212 handlePermissionsChangedLocked(uid, new UidGrantedPermissionState[] {state}); 213 } 214 break; 215 } 216 } 217 } 218 handlePermissionsChanged(int uid)219 private void handlePermissionsChanged(int uid) { 220 if (DEBUG_PERMISSION_TRACKER) { 221 Slog.i(TAG, "handlePermissionsChanged " + UserHandle.formatUid(uid)); 222 } 223 final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); 224 if (permissions != null && permissions.length > 0) { 225 final PermissionManagerServiceInternal pm = 226 mInjector.getPermissionManagerServiceInternal(); 227 final UidGrantedPermissionState[] states = 228 new UidGrantedPermissionState[permissions.length]; 229 for (int i = 0; i < permissions.length; i++) { 230 final Pair<String, Integer> pair = permissions[i]; 231 states[i] = new UidGrantedPermissionState(uid, pair.first, pair.second); 232 if (DEBUG_PERMISSION_TRACKER) { 233 Slog.i(TAG, states[i].toString()); 234 } 235 } 236 synchronized (mLock) { 237 handlePermissionsChangedLocked(uid, states); 238 } 239 } 240 } 241 242 @GuardedBy("mLock") handlePermissionsChangedLocked(int uid, UidGrantedPermissionState[] states)243 private void handlePermissionsChangedLocked(int uid, UidGrantedPermissionState[] states) { 244 final int index = mUidGrantedPermissionsInMonitor.indexOfKey(uid); 245 ArraySet<UidGrantedPermissionState> grantedPermissions = index >= 0 246 ? mUidGrantedPermissionsInMonitor.valueAt(index) : null; 247 final long now = SystemClock.elapsedRealtime(); 248 for (int i = 0; i < states.length; i++) { 249 final boolean granted = states[i].isGranted(); 250 boolean changed = false; 251 if (granted) { 252 if (grantedPermissions == null) { 253 grantedPermissions = new ArraySet<>(); 254 mUidGrantedPermissionsInMonitor.put(uid, grantedPermissions); 255 changed = true; 256 } 257 grantedPermissions.add(states[i]); 258 } else if (grantedPermissions != null && !grantedPermissions.isEmpty()) { 259 if (grantedPermissions.remove(states[i]) && grantedPermissions.isEmpty()) { 260 mUidGrantedPermissionsInMonitor.removeAt(index); 261 changed = true; 262 } 263 } 264 if (changed) { 265 notifyListenersOnStateChange(uid, DEFAULT_NAME, granted, now, 266 STATE_TYPE_PERMISSION); 267 } 268 } 269 } 270 271 /** 272 * Represents the grant state of a permission + appop of the given UID. 273 */ 274 private class UidGrantedPermissionState { 275 final int mUid; 276 final @Nullable String mPermission; 277 final int mAppOp; 278 279 private boolean mPermissionGranted; 280 private boolean mAppOpAllowed; 281 UidGrantedPermissionState(int uid, @Nullable String permission, int appOp)282 UidGrantedPermissionState(int uid, @Nullable String permission, int appOp) { 283 mUid = uid; 284 mPermission = permission; 285 mAppOp = appOp; 286 updatePermissionState(); 287 updateAppOps(); 288 } 289 updatePermissionState()290 void updatePermissionState() { 291 if (TextUtils.isEmpty(mPermission)) { 292 mPermissionGranted = true; 293 return; 294 } 295 mPermissionGranted = mInjector.getPermissionManagerServiceInternal() 296 .checkUidPermission(mUid, mPermission) == PERMISSION_GRANTED; 297 } 298 updateAppOps()299 void updateAppOps() { 300 if (mAppOp == OP_NONE) { 301 mAppOpAllowed = true; 302 return; 303 } 304 final String[] packages = mInjector.getPackageManager().getPackagesForUid(mUid); 305 if (packages != null) { 306 final IAppOpsService appOpsService = mInjector.getIAppOpsService(); 307 for (String pkg : packages) { 308 try { 309 final int mode = appOpsService.checkOperation(mAppOp, mUid, pkg); 310 if (mode == AppOpsManager.MODE_ALLOWED) { 311 mAppOpAllowed = true; 312 return; 313 } 314 } catch (RemoteException e) { 315 // Intra-process call, should never happen. 316 } 317 } 318 } 319 mAppOpAllowed = false; 320 } 321 isGranted()322 boolean isGranted() { 323 return mPermissionGranted && mAppOpAllowed; 324 } 325 326 @Override equals(Object other)327 public boolean equals(Object other) { 328 if (other == null || !(other instanceof UidGrantedPermissionState)) { 329 return false; 330 } 331 final UidGrantedPermissionState otherState = (UidGrantedPermissionState) other; 332 return mUid == otherState.mUid && mAppOp == otherState.mAppOp 333 && Objects.equals(mPermission, otherState.mPermission); 334 } 335 336 @Override hashCode()337 public int hashCode() { 338 return (Integer.hashCode(mUid) * 31 + Integer.hashCode(mAppOp)) * 31 339 + (mPermission == null ? 0 : mPermission.hashCode()); 340 } 341 342 @Override toString()343 public String toString() { 344 String s = "UidGrantedPermissionState{" 345 + System.identityHashCode(this) + " " 346 + UserHandle.formatUid(mUid) + ": "; 347 final boolean emptyPermissionName = TextUtils.isEmpty(mPermission); 348 if (!emptyPermissionName) { 349 s += mPermission + "=" + mPermissionGranted; 350 } 351 if (mAppOp != OP_NONE) { 352 if (!emptyPermissionName) { 353 s += ","; 354 } 355 s += opToPublicName(mAppOp) + "=" + mAppOpAllowed; 356 } 357 s += "}"; 358 return s; 359 } 360 } 361 startWatchingMode(@onNull Integer[] ops)362 private void startWatchingMode(@NonNull Integer[] ops) { 363 synchronized (mAppOpsCallbacks) { 364 stopWatchingMode(); 365 final IAppOpsService appOpsService = mInjector.getIAppOpsService(); 366 try { 367 for (int op: ops) { 368 final MyAppOpsCallback cb = new MyAppOpsCallback(); 369 mAppOpsCallbacks.put(op, cb); 370 appOpsService.startWatchingModeWithFlags(op, null, 371 AppOpsManager.WATCH_FOREGROUND_CHANGES, cb); 372 } 373 } catch (RemoteException e) { 374 // Intra-process call, should never happen. 375 } 376 } 377 } 378 stopWatchingMode()379 private void stopWatchingMode() { 380 synchronized (mAppOpsCallbacks) { 381 final IAppOpsService appOpsService = mInjector.getIAppOpsService(); 382 for (int i = mAppOpsCallbacks.size() - 1; i >= 0; i--) { 383 try { 384 appOpsService.stopWatchingMode(mAppOpsCallbacks.valueAt(i)); 385 } catch (RemoteException e) { 386 // Intra-process call, should never happen. 387 } 388 } 389 mAppOpsCallbacks.clear(); 390 } 391 } 392 393 private class MyAppOpsCallback extends IAppOpsCallback.Stub { 394 @Override opChanged(int op, int uid, String packageName)395 public void opChanged(int op, int uid, String packageName) { 396 mHandler.obtainMessage(MyHandler.MSG_APPOPS_CHANGED, op, uid, packageName) 397 .sendToTarget(); 398 } 399 } 400 401 private static class MyHandler extends Handler { 402 static final int MSG_PERMISSIONS_INIT = 0; 403 static final int MSG_PERMISSIONS_DESTROY = 1; 404 static final int MSG_PERMISSIONS_CHANGED = 2; 405 static final int MSG_APPOPS_CHANGED = 3; 406 407 private @NonNull AppPermissionTracker mTracker; 408 MyHandler(@onNull AppPermissionTracker tracker)409 MyHandler(@NonNull AppPermissionTracker tracker) { 410 super(tracker.mBgHandler.getLooper()); 411 mTracker = tracker; 412 } 413 414 @Override handleMessage(Message msg)415 public void handleMessage(Message msg) { 416 switch (msg.what) { 417 case MSG_PERMISSIONS_INIT: 418 mTracker.handleAppOpsInit(); 419 mTracker.handlePermissionsInit(); 420 break; 421 case MSG_PERMISSIONS_DESTROY: 422 mTracker.handlePermissionsDestroy(); 423 mTracker.handleAppOpsDestroy(); 424 break; 425 case MSG_PERMISSIONS_CHANGED: 426 mTracker.handlePermissionsChanged(msg.arg1); 427 break; 428 case MSG_APPOPS_CHANGED: 429 mTracker.handleOpChanged(msg.arg1, msg.arg2, (String) msg.obj); 430 break; 431 } 432 } 433 } 434 onPermissionTrackerEnabled(boolean enabled)435 private void onPermissionTrackerEnabled(boolean enabled) { 436 if (!mLockedBootCompleted) { 437 // Not ready, bail out. 438 return; 439 } 440 final PermissionManager pm = mInjector.getPermissionManager(); 441 if (enabled) { 442 pm.addOnPermissionsChangeListener(this); 443 mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_INIT).sendToTarget(); 444 } else { 445 pm.removeOnPermissionsChangeListener(this); 446 mHandler.obtainMessage(MyHandler.MSG_PERMISSIONS_DESTROY).sendToTarget(); 447 } 448 } 449 450 @Override onLockedBootCompleted()451 void onLockedBootCompleted() { 452 mLockedBootCompleted = true; 453 onPermissionTrackerEnabled(mInjector.getPolicy().isEnabled()); 454 } 455 456 @Override dump(PrintWriter pw, String prefix)457 void dump(PrintWriter pw, String prefix) { 458 pw.print(prefix); 459 pw.println("APP PERMISSIONS TRACKER:"); 460 final Pair[] permissions = mInjector.getPolicy().getBgPermissionsInMonitor(); 461 final String prefixMore = " " + prefix; 462 final String prefixMoreMore = " " + prefixMore; 463 for (Pair<String, Integer> permission : permissions) { 464 pw.print(prefixMore); 465 final boolean emptyPermissionName = TextUtils.isEmpty(permission.first); 466 if (!emptyPermissionName) { 467 pw.print(permission.first); 468 } 469 if (permission.second != OP_NONE) { 470 if (!emptyPermissionName) { 471 pw.print('+'); 472 } 473 pw.print(opToPublicName(permission.second)); 474 } 475 pw.println(':'); 476 synchronized (mLock) { 477 final SparseArray<ArraySet<UidGrantedPermissionState>> uidPerms = 478 mUidGrantedPermissionsInMonitor; 479 pw.print(prefixMoreMore); 480 pw.print('['); 481 boolean needDelimiter = false; 482 for (int i = 0, size = uidPerms.size(); i < size; i++) { 483 final ArraySet<UidGrantedPermissionState> uidPerm = uidPerms.valueAt(i); 484 for (int j = uidPerm.size() - 1; j >= 0; j--) { 485 final UidGrantedPermissionState state = uidPerm.valueAt(j); 486 if (state.mAppOp == permission.second 487 && TextUtils.equals(state.mPermission, permission.first)) { 488 if (needDelimiter) { 489 pw.print(','); 490 } 491 needDelimiter = true; 492 pw.print(UserHandle.formatUid(state.mUid)); 493 break; 494 } 495 } 496 } 497 pw.println(']'); 498 } 499 } 500 super.dump(pw, prefix); 501 } 502 503 static final class AppPermissionPolicy extends BaseAppStatePolicy<AppPermissionTracker> { 504 /** 505 * Whether or not we should enable the monitoring on app permissions. 506 */ 507 static final String KEY_BG_PERMISSION_MONITOR_ENABLED = 508 DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_monitor_enabled"; 509 510 /** 511 * The names of the permissions we're monitoring its changes. 512 */ 513 static final String KEY_BG_PERMISSIONS_IN_MONITOR = 514 DEVICE_CONFIG_SUBNAMESPACE_PREFIX + "permission_in_monitor"; 515 516 /** 517 * Default value to {@link #mTrackerEnabled}. 518 */ 519 static final boolean DEFAULT_BG_PERMISSION_MONITOR_ENABLED = true; 520 521 /** 522 * Default value to {@link #mBgPermissionsInMonitor}, it comes in pair; 523 * the first string strings in the pair is the permission name, and the second string 524 * is the appops name, if they are associated. 525 */ 526 static final String[] DEFAULT_BG_PERMISSIONS_IN_MONITOR = new String[] { 527 ACCESS_FINE_LOCATION, OPSTR_FINE_LOCATION, 528 CAMERA, OPSTR_CAMERA, 529 RECORD_AUDIO, OPSTR_RECORD_AUDIO, 530 }; 531 532 /** 533 * @see #KEY_BG_PERMISSIONS_IN_MONITOR. 534 */ 535 volatile @NonNull Pair[] mBgPermissionsInMonitor; 536 AppPermissionPolicy(@onNull Injector injector, @NonNull AppPermissionTracker tracker)537 AppPermissionPolicy(@NonNull Injector injector, @NonNull AppPermissionTracker tracker) { 538 super(injector, tracker, KEY_BG_PERMISSION_MONITOR_ENABLED, 539 DEFAULT_BG_PERMISSION_MONITOR_ENABLED); 540 mBgPermissionsInMonitor = parsePermissionConfig(DEFAULT_BG_PERMISSIONS_IN_MONITOR); 541 } 542 543 @Override onSystemReady()544 public void onSystemReady() { 545 super.onSystemReady(); 546 updateBgPermissionsInMonitor(); 547 } 548 549 @Override onPropertiesChanged(String name)550 public void onPropertiesChanged(String name) { 551 switch (name) { 552 case KEY_BG_PERMISSIONS_IN_MONITOR: 553 updateBgPermissionsInMonitor(); 554 break; 555 default: 556 super.onPropertiesChanged(name); 557 break; 558 } 559 } 560 getBgPermissionsInMonitor()561 Pair[] getBgPermissionsInMonitor() { 562 return mBgPermissionsInMonitor; 563 } 564 parsePermissionConfig(@onNull String[] perms)565 private @NonNull Pair[] parsePermissionConfig(@NonNull String[] perms) { 566 final Pair[] result = new Pair[perms.length / 2]; 567 for (int i = 0, j = 0; i < perms.length; i += 2, j++) { 568 try { 569 result[j] = Pair.create(TextUtils.isEmpty(perms[i]) ? null : perms[i], 570 TextUtils.isEmpty(perms[i + 1]) ? OP_NONE : strOpToOp(perms[i + 1])); 571 } catch (Exception e) { 572 // Ignore. 573 } 574 } 575 return result; 576 } 577 updateBgPermissionsInMonitor()578 private void updateBgPermissionsInMonitor() { 579 final String config = DeviceConfig.getString( 580 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, 581 KEY_BG_PERMISSIONS_IN_MONITOR, 582 null); 583 final Pair[] newPermsInMonitor = parsePermissionConfig( 584 config != null ? config.split(",") : DEFAULT_BG_PERMISSIONS_IN_MONITOR); 585 if (!Arrays.equals(mBgPermissionsInMonitor, newPermsInMonitor)) { 586 mBgPermissionsInMonitor = newPermsInMonitor; 587 if (isEnabled()) { 588 // Trigger a reload. 589 onTrackerEnabled(false); 590 onTrackerEnabled(true); 591 } 592 } 593 } 594 595 @Override onTrackerEnabled(boolean enabled)596 public void onTrackerEnabled(boolean enabled) { 597 mTracker.onPermissionTrackerEnabled(enabled); 598 } 599 600 @Override dump(PrintWriter pw, String prefix)601 void dump(PrintWriter pw, String prefix) { 602 pw.print(prefix); 603 pw.println("APP PERMISSION TRACKER POLICY SETTINGS:"); 604 prefix = " " + prefix; 605 super.dump(pw, prefix); 606 pw.print(prefix); 607 pw.print(KEY_BG_PERMISSIONS_IN_MONITOR); 608 pw.print('='); 609 pw.print('['); 610 for (int i = 0; i < mBgPermissionsInMonitor.length; i++) { 611 if (i > 0) { 612 pw.print(','); 613 } 614 final Pair<String, Integer> pair = mBgPermissionsInMonitor[i]; 615 if (pair.first != null) { 616 pw.print(pair.first); 617 } 618 pw.print(','); 619 if (pair.second != OP_NONE) { 620 pw.print(opToPublicName(pair.second)); 621 } 622 } 623 pw.println(']'); 624 } 625 } 626 } 627