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