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.appop;
18 
19 import static android.app.AppOpsManager.MODE_ALLOWED;
20 import static android.app.AppOpsManager.OP_NONE;
21 import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
22 import static android.app.AppOpsManager.OP_USE_FULL_SCREEN_INTENT;
23 import static android.app.AppOpsManager.WATCH_FOREGROUND_CHANGES;
24 import static android.app.AppOpsManager.opRestrictsRead;
25 import static android.app.AppOpsManager.opToDefaultMode;
26 
27 import static com.android.server.appop.AppOpsService.ModeCallback.ALL_OPS;
28 
29 import android.Manifest;
30 import android.annotation.NonNull;
31 import android.annotation.Nullable;
32 import android.annotation.UserIdInt;
33 import android.app.AppGlobals;
34 import android.app.AppOpsManager;
35 import android.app.AppOpsManager.Mode;
36 import android.content.Context;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageManagerInternal;
39 import android.content.pm.UserPackage;
40 import android.os.AsyncTask;
41 import android.os.Binder;
42 import android.os.Handler;
43 import android.os.RemoteException;
44 import android.os.UserHandle;
45 import android.permission.PermissionManager;
46 import android.util.ArrayMap;
47 import android.util.ArraySet;
48 import android.util.AtomicFile;
49 import android.util.Slog;
50 import android.util.SparseArray;
51 import android.util.SparseBooleanArray;
52 import android.util.SparseIntArray;
53 import android.util.Xml;
54 
55 import com.android.internal.annotations.GuardedBy;
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.internal.util.XmlUtils;
58 import com.android.internal.util.function.pooled.PooledLambda;
59 import com.android.modules.utils.TypedXmlPullParser;
60 import com.android.modules.utils.TypedXmlSerializer;
61 import com.android.server.LocalServices;
62 import com.android.server.pm.UserManagerInternal;
63 import com.android.server.pm.permission.PermissionManagerServiceInternal;
64 
65 import libcore.util.EmptyArray;
66 
67 import org.xmlpull.v1.XmlPullParser;
68 import org.xmlpull.v1.XmlPullParserException;
69 
70 import java.io.File;
71 import java.io.FileInputStream;
72 import java.io.FileNotFoundException;
73 import java.io.FileOutputStream;
74 import java.io.IOException;
75 import java.io.PrintWriter;
76 import java.util.ArrayList;
77 import java.util.Collections;
78 import java.util.List;
79 import java.util.Objects;
80 
81 
82 /**
83  * Legacy implementation for App-ops service's app-op mode (uid and package) storage and access.
84  * In the future this class will also include mode callbacks and op restrictions.
85  */
86 public class AppOpsCheckingServiceImpl implements AppOpsCheckingServiceInterface {
87 
88     static final String TAG = "LegacyAppOpsServiceInterfaceImpl";
89 
90     private static final boolean DEBUG = false;
91 
92     // Write at most every 30 minutes.
93     private static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
94 
95     /**
96      * Sentinel integer version to denote that there was no appops.xml found on boot.
97      * This will happen when a device boots with no existing userdata.
98      */
99     private static final int NO_FILE_VERSION = -2;
100 
101     /**
102      * Sentinel integer version to denote that there was no version in the appops.xml found on boot.
103      * This means the file is coming from a build before versioning was added.
104      */
105     private static final int NO_VERSION = -1;
106 
107     /**
108      * Increment by one every time and add the corresponding upgrade logic in
109      * {@link #upgradeLocked(int)} below. The first version was 1.
110      */
111     @VisibleForTesting
112     static final int CURRENT_VERSION = 4;
113 
114     /**
115      * This stores the version of appops.xml seen at boot. If this is smaller than
116      * {@link #CURRENT_VERSION}, then we will run {@link #upgradeLocked(int)} on startup.
117      */
118     private int mVersionAtBoot = NO_FILE_VERSION;
119 
120     // Must be the same object that the AppOpsService is using for locking.
121     final Object mLock;
122     final Handler mHandler;
123     final Context mContext;
124     final SparseArray<int[]> mSwitchedOps;
125 
126     @GuardedBy("mLock")
127     @VisibleForTesting
128     final SparseArray<SparseIntArray> mUidModes = new SparseArray<>();
129 
130     @GuardedBy("mLock")
131     final SparseArray<ArrayMap<String, SparseIntArray>> mUserPackageModes = new SparseArray<>();
132 
133     final SparseArray<ArraySet<OnOpModeChangedListener>> mOpModeWatchers = new SparseArray<>();
134     final ArrayMap<String, ArraySet<OnOpModeChangedListener>> mPackageModeWatchers =
135             new ArrayMap<>();
136 
137     final AtomicFile mFile;
138     final Runnable mWriteRunner = new Runnable() {
139         public void run() {
140             synchronized (mLock) {
141                 mWriteScheduled = false;
142                 mFastWriteScheduled = false;
143                 AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
144                     @Override
145                     protected Void doInBackground(Void... params) {
146                         writeState();
147                         return null;
148                     }
149                 };
150                 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
151             }
152         }
153     };
154 
155     boolean mWriteScheduled;
156     boolean mFastWriteScheduled;
157 
158 
159     // Constant meaning that any UID should be matched when dispatching callbacks
160     private static final int UID_ANY = -2;
161 
AppOpsCheckingServiceImpl(File storageFile, @NonNull Object lock, Handler handler, Context context, SparseArray<int[]> switchedOps)162     AppOpsCheckingServiceImpl(File storageFile,
163             @NonNull Object lock, Handler handler, Context context,
164             SparseArray<int[]> switchedOps) {
165         this.mFile = new AtomicFile(storageFile);
166         this.mLock = lock;
167         this.mHandler = handler;
168         this.mContext = context;
169         this.mSwitchedOps = switchedOps;
170     }
171 
172     @Override
systemReady()173     public void systemReady() {
174         synchronized (mLock) {
175             // TODO: This file version upgrade code may still need to happen after we switch to
176             //  another implementation of AppOpsCheckingServiceInterface.
177             upgradeLocked(mVersionAtBoot);
178         }
179     }
180 
181     @Override
getNonDefaultUidModes(int uid)182     public SparseIntArray getNonDefaultUidModes(int uid) {
183         synchronized (mLock) {
184             SparseIntArray opModes = mUidModes.get(uid, null);
185             if (opModes == null) {
186                 return new SparseIntArray();
187             }
188             return opModes.clone();
189         }
190     }
191 
192     @Override
getNonDefaultPackageModes(String packageName, int userId)193     public SparseIntArray getNonDefaultPackageModes(String packageName, int userId) {
194         synchronized (mLock) {
195             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
196             if (packageModes == null) {
197                 return new SparseIntArray();
198             }
199             SparseIntArray opModes = packageModes.get(packageName);
200             if (opModes == null) {
201                 return new SparseIntArray();
202             }
203             return opModes.clone();
204         }
205     }
206 
207     @Override
getUidMode(int uid, int op)208     public int getUidMode(int uid, int op) {
209         synchronized (mLock) {
210             SparseIntArray opModes = mUidModes.get(uid, null);
211             if (opModes == null) {
212                 return AppOpsManager.opToDefaultMode(op);
213             }
214             return opModes.get(op, AppOpsManager.opToDefaultMode(op));
215         }
216     }
217 
218     @Override
setUidMode(int uid, int op, int mode)219     public boolean setUidMode(int uid, int op, int mode) {
220         final int defaultMode = AppOpsManager.opToDefaultMode(op);
221         synchronized (mLock) {
222             SparseIntArray opModes = mUidModes.get(uid, null);
223             if (opModes == null) {
224                 if (mode != defaultMode) {
225                     opModes = new SparseIntArray();
226                     mUidModes.put(uid, opModes);
227                     opModes.put(op, mode);
228                     scheduleWriteLocked();
229                 }
230             } else {
231                 if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
232                     return false;
233                 }
234                 if (mode == defaultMode) {
235                     opModes.delete(op);
236                     if (opModes.size() <= 0) {
237                         opModes = null;
238                         mUidModes.delete(uid);
239                     }
240                 } else {
241                     opModes.put(op, mode);
242                 }
243                 scheduleWriteLocked();
244             }
245         }
246         return true;
247     }
248 
249     @Override
getPackageMode(String packageName, int op, @UserIdInt int userId)250     public int getPackageMode(String packageName, int op, @UserIdInt int userId) {
251         synchronized (mLock) {
252             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
253             if (packageModes == null) {
254                 return AppOpsManager.opToDefaultMode(op);
255             }
256             SparseIntArray opModes = packageModes.getOrDefault(packageName, null);
257             if (opModes == null) {
258                 return AppOpsManager.opToDefaultMode(op);
259             }
260             return opModes.get(op, AppOpsManager.opToDefaultMode(op));
261         }
262     }
263 
264     @Override
setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId)265     public void setPackageMode(String packageName, int op, @Mode int mode, @UserIdInt int userId) {
266         final int defaultMode = AppOpsManager.opToDefaultMode(op);
267         synchronized (mLock) {
268             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
269             if (packageModes == null) {
270                 packageModes = new ArrayMap<>();
271                 mUserPackageModes.put(userId, packageModes);
272             }
273             SparseIntArray opModes = packageModes.get(packageName);
274             if (opModes == null) {
275                 if (mode != defaultMode) {
276                     opModes = new SparseIntArray();
277                     packageModes.put(packageName, opModes);
278                     opModes.put(op, mode);
279                     scheduleWriteLocked();
280                 }
281             } else {
282                 if (opModes.indexOfKey(op) >= 0 && opModes.get(op) == mode) {
283                     return;
284                 }
285                 if (mode == defaultMode) {
286                     opModes.delete(op);
287                     if (opModes.size() <= 0) {
288                         opModes = null;
289                         packageModes.remove(packageName);
290                     }
291                 } else {
292                     opModes.put(op, mode);
293                 }
294                 scheduleWriteLocked();
295             }
296         }
297     }
298 
299     @Override
removeUid(int uid)300     public void removeUid(int uid) {
301         synchronized (mLock) {
302             SparseIntArray opModes = mUidModes.get(uid);
303             if (opModes == null) {
304                 return;
305             }
306             mUidModes.remove(uid);
307             scheduleFastWriteLocked();
308         }
309     }
310 
311     @Override
areUidModesDefault(int uid)312     public boolean areUidModesDefault(int uid) {
313         synchronized (mLock) {
314             SparseIntArray opModes = mUidModes.get(uid);
315             return (opModes == null || opModes.size() <= 0);
316         }
317     }
318 
319     @Override
arePackageModesDefault(@onNull String packageName, @UserIdInt int userId)320     public boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId) {
321         synchronized (mLock) {
322             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
323             if (packageModes == null) {
324                 return true;
325             }
326             SparseIntArray opModes = packageModes.get(packageName);
327             return (opModes == null || opModes.size() <= 0);
328         }
329     }
330 
331     @Override
removePackage(String packageName, @UserIdInt int userId)332     public boolean removePackage(String packageName, @UserIdInt int userId) {
333         synchronized (mLock) {
334             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
335             if (packageModes == null) {
336                 return false;
337             }
338             SparseIntArray ops = packageModes.remove(packageName);
339             if (ops != null) {
340                 scheduleFastWriteLocked();
341                 return true;
342             }
343             return false;
344         }
345     }
346 
347     @Override
clearAllModes()348     public void clearAllModes() {
349         synchronized (mLock) {
350             mUidModes.clear();
351             mUserPackageModes.clear();
352         }
353     }
354 
355     @Override
startWatchingOpModeChanged(@onNull OnOpModeChangedListener changedListener, int op)356     public void startWatchingOpModeChanged(@NonNull OnOpModeChangedListener changedListener,
357             int op) {
358         Objects.requireNonNull(changedListener);
359         synchronized (mLock) {
360             ArraySet<OnOpModeChangedListener> modeWatcherSet = mOpModeWatchers.get(op);
361             if (modeWatcherSet == null) {
362                 modeWatcherSet = new ArraySet<>();
363                 mOpModeWatchers.put(op, modeWatcherSet);
364             }
365             modeWatcherSet.add(changedListener);
366         }
367     }
368 
369     @Override
startWatchingPackageModeChanged(@onNull OnOpModeChangedListener changedListener, @NonNull String packageName)370     public void startWatchingPackageModeChanged(@NonNull OnOpModeChangedListener changedListener,
371             @NonNull String packageName) {
372         Objects.requireNonNull(changedListener);
373         Objects.requireNonNull(packageName);
374         synchronized (mLock) {
375             ArraySet<OnOpModeChangedListener> modeWatcherSet =
376                     mPackageModeWatchers.get(packageName);
377             if (modeWatcherSet == null) {
378                 modeWatcherSet = new ArraySet<>();
379                 mPackageModeWatchers.put(packageName, modeWatcherSet);
380             }
381             modeWatcherSet.add(changedListener);
382         }
383     }
384 
385     @Override
removeListener(@onNull OnOpModeChangedListener changedListener)386     public void removeListener(@NonNull OnOpModeChangedListener changedListener) {
387         Objects.requireNonNull(changedListener);
388 
389         synchronized (mLock) {
390             for (int i = mOpModeWatchers.size() - 1; i >= 0; i--) {
391                 ArraySet<OnOpModeChangedListener> cbs = mOpModeWatchers.valueAt(i);
392                 cbs.remove(changedListener);
393                 if (cbs.size() <= 0) {
394                     mOpModeWatchers.removeAt(i);
395                 }
396             }
397 
398             for (int i = mPackageModeWatchers.size() - 1; i >= 0; i--) {
399                 ArraySet<OnOpModeChangedListener> cbs = mPackageModeWatchers.valueAt(i);
400                 cbs.remove(changedListener);
401                 if (cbs.size() <= 0) {
402                     mPackageModeWatchers.removeAt(i);
403                 }
404             }
405         }
406     }
407 
408     @Override
getOpModeChangedListeners(int op)409     public ArraySet<OnOpModeChangedListener> getOpModeChangedListeners(int op) {
410         synchronized (mLock) {
411             ArraySet<OnOpModeChangedListener> modeChangedListenersSet = mOpModeWatchers.get(op);
412             if (modeChangedListenersSet == null) {
413                 return new ArraySet<>();
414             }
415             return new ArraySet<>(modeChangedListenersSet);
416         }
417     }
418 
419     @Override
getPackageModeChangedListeners( @onNull String packageName)420     public ArraySet<OnOpModeChangedListener> getPackageModeChangedListeners(
421             @NonNull String packageName) {
422         Objects.requireNonNull(packageName);
423 
424         synchronized (mLock) {
425             ArraySet<OnOpModeChangedListener> modeChangedListenersSet =
426                     mPackageModeWatchers.get(packageName);
427             if (modeChangedListenersSet == null) {
428                 return new ArraySet<>();
429             }
430             return new ArraySet<>(modeChangedListenersSet);
431         }
432     }
433 
434     @Override
notifyWatchersOfChange(int code, int uid)435     public void notifyWatchersOfChange(int code, int uid) {
436         ArraySet<OnOpModeChangedListener> listenerSet = getOpModeChangedListeners(code);
437         if (listenerSet == null) {
438             return;
439         }
440         for (int i = 0; i < listenerSet.size(); i++) {
441             final OnOpModeChangedListener listener = listenerSet.valueAt(i);
442             notifyOpChanged(listener, code, uid, null);
443         }
444     }
445 
446     @Override
notifyOpChanged(@onNull OnOpModeChangedListener onModeChangedListener, int code, int uid, @Nullable String packageName)447     public void notifyOpChanged(@NonNull OnOpModeChangedListener onModeChangedListener, int code,
448             int uid, @Nullable String packageName) {
449         Objects.requireNonNull(onModeChangedListener);
450 
451         if (uid != UID_ANY && onModeChangedListener.getWatchingUid() >= 0
452                 && onModeChangedListener.getWatchingUid() != uid) {
453             return;
454         }
455 
456         // See CALL_BACK_ON_CHANGED_LISTENER_WITH_SWITCHED_OP_CHANGE
457         int[] switchedCodes;
458         if (onModeChangedListener.getWatchedOpCode() == ALL_OPS) {
459             switchedCodes = mSwitchedOps.get(code);
460         } else if (onModeChangedListener.getWatchedOpCode() == OP_NONE) {
461             switchedCodes = new int[]{code};
462         } else {
463             switchedCodes = new int[]{onModeChangedListener.getWatchedOpCode()};
464         }
465 
466         for (int switchedCode : switchedCodes) {
467             // There are features watching for mode changes such as window manager
468             // and location manager which are in our process. The callbacks in these
469             // features may require permissions our remote caller does not have.
470             final long identity = Binder.clearCallingIdentity();
471             try {
472                 if (shouldIgnoreCallback(switchedCode, onModeChangedListener.getCallingPid(),
473                         onModeChangedListener.getCallingUid())) {
474                     continue;
475                 }
476                 onModeChangedListener.onOpModeChanged(switchedCode, uid, packageName);
477             } catch (RemoteException e) {
478                 /* ignore */
479             } finally {
480                 Binder.restoreCallingIdentity(identity);
481             }
482         }
483     }
484 
shouldIgnoreCallback(int op, int watcherPid, int watcherUid)485     private boolean shouldIgnoreCallback(int op, int watcherPid, int watcherUid) {
486         // If it's a restricted read op, ignore it if watcher doesn't have manage ops permission,
487         // as watcher should not use this to signal if the value is changed.
488         return opRestrictsRead(op) && mContext.checkPermission(Manifest.permission.MANAGE_APPOPS,
489                 watcherPid, watcherUid) != PackageManager.PERMISSION_GRANTED;
490     }
491 
492     @Override
notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground, @Nullable OnOpModeChangedListener callbackToIgnore)493     public void notifyOpChangedForAllPkgsInUid(int code, int uid, boolean onlyForeground,
494             @Nullable OnOpModeChangedListener callbackToIgnore) {
495         String[] uidPackageNames = getPackagesForUid(uid);
496         ArrayMap<OnOpModeChangedListener, ArraySet<String>> callbackSpecs = null;
497 
498         synchronized (mLock) {
499             ArraySet<OnOpModeChangedListener> callbacks = mOpModeWatchers.get(code);
500             if (callbacks != null) {
501                 final int callbackCount = callbacks.size();
502                 for (int i = 0; i < callbackCount; i++) {
503                     OnOpModeChangedListener callback = callbacks.valueAt(i);
504 
505                     if (onlyForeground && (callback.getFlags()
506                             & WATCH_FOREGROUND_CHANGES) == 0) {
507                         continue;
508                     }
509 
510                     ArraySet<String> changedPackages = new ArraySet<>();
511                     Collections.addAll(changedPackages, uidPackageNames);
512                     if (callbackSpecs == null) {
513                         callbackSpecs = new ArrayMap<>();
514                     }
515                     callbackSpecs.put(callback, changedPackages);
516                 }
517             }
518 
519             for (String uidPackageName : uidPackageNames) {
520                 callbacks = mPackageModeWatchers.get(uidPackageName);
521                 if (callbacks != null) {
522                     if (callbackSpecs == null) {
523                         callbackSpecs = new ArrayMap<>();
524                     }
525                     final int callbackCount = callbacks.size();
526                     for (int i = 0; i < callbackCount; i++) {
527                         OnOpModeChangedListener callback = callbacks.valueAt(i);
528 
529                         if (onlyForeground && (callback.getFlags()
530                                 & WATCH_FOREGROUND_CHANGES) == 0) {
531                             continue;
532                         }
533 
534                         ArraySet<String> changedPackages = callbackSpecs.get(callback);
535                         if (changedPackages == null) {
536                             changedPackages = new ArraySet<>();
537                             callbackSpecs.put(callback, changedPackages);
538                         }
539                         changedPackages.add(uidPackageName);
540                     }
541                 }
542             }
543 
544             if (callbackSpecs != null && callbackToIgnore != null) {
545                 callbackSpecs.remove(callbackToIgnore);
546             }
547         }
548 
549         if (callbackSpecs == null) {
550             return;
551         }
552 
553         for (int i = 0; i < callbackSpecs.size(); i++) {
554             final OnOpModeChangedListener callback = callbackSpecs.keyAt(i);
555             final ArraySet<String> reportedPackageNames = callbackSpecs.valueAt(i);
556             if (reportedPackageNames == null) {
557                 mHandler.sendMessage(PooledLambda.obtainMessage(
558                         AppOpsCheckingServiceImpl::notifyOpChanged,
559                         this, callback, code, uid, (String) null));
560 
561             } else {
562                 final int reportedPackageCount = reportedPackageNames.size();
563                 for (int j = 0; j < reportedPackageCount; j++) {
564                     final String reportedPackageName = reportedPackageNames.valueAt(j);
565                     mHandler.sendMessage(PooledLambda.obtainMessage(
566                             AppOpsCheckingServiceImpl::notifyOpChanged,
567                             this, callback, code, uid, reportedPackageName));
568                 }
569             }
570         }
571     }
572 
getPackagesForUid(int uid)573     private static String[] getPackagesForUid(int uid) {
574         String[] packageNames = null;
575 
576         // Very early during boot the package manager is not yet or not yet fully started. At this
577         // time there are no packages yet.
578         if (AppGlobals.getPackageManager() != null) {
579             try {
580                 packageNames = AppGlobals.getPackageManager().getPackagesForUid(uid);
581             } catch (RemoteException e) {
582                 /* ignore - local call */
583             }
584         }
585         if (packageNames == null) {
586             return EmptyArray.STRING;
587         }
588         return packageNames;
589     }
590 
591     @Override
evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps)592     public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
593         synchronized (mLock) {
594             return evalForegroundOps(mUidModes.get(uid), foregroundOps);
595         }
596     }
597 
598     @Override
evalForegroundPackageOps(String packageName, SparseBooleanArray foregroundOps, @UserIdInt int userId)599     public SparseBooleanArray evalForegroundPackageOps(String packageName,
600             SparseBooleanArray foregroundOps, @UserIdInt int userId) {
601         synchronized (mLock) {
602             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
603             return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
604                     foregroundOps);
605         }
606     }
607 
evalForegroundOps(SparseIntArray opModes, SparseBooleanArray foregroundOps)608     private SparseBooleanArray evalForegroundOps(SparseIntArray opModes,
609             SparseBooleanArray foregroundOps) {
610         SparseBooleanArray tempForegroundOps = foregroundOps;
611         if (opModes != null) {
612             for (int i = opModes.size() - 1; i >= 0; i--) {
613                 if (opModes.valueAt(i) == AppOpsManager.MODE_FOREGROUND) {
614                     if (tempForegroundOps == null) {
615                         tempForegroundOps = new SparseBooleanArray();
616                     }
617                     evalForegroundWatchers(opModes.keyAt(i), tempForegroundOps);
618                 }
619             }
620         }
621         return tempForegroundOps;
622     }
623 
evalForegroundWatchers(int op, SparseBooleanArray foregroundOps)624     private void evalForegroundWatchers(int op, SparseBooleanArray foregroundOps) {
625         boolean curValue = foregroundOps.get(op, false);
626         ArraySet<OnOpModeChangedListener> listenerSet = mOpModeWatchers.get(op);
627         if (listenerSet != null) {
628             for (int cbi = listenerSet.size() - 1; !curValue && cbi >= 0; cbi--) {
629                 if ((listenerSet.valueAt(cbi).getFlags()
630                         & AppOpsManager.WATCH_FOREGROUND_CHANGES) != 0) {
631                     curValue = true;
632                 }
633             }
634         }
635         foregroundOps.put(op, curValue);
636     }
637 
638     @Override
dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter)639     public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
640             PrintWriter printWriter) {
641         boolean needSep = false;
642         if (mOpModeWatchers.size() > 0) {
643             boolean printedHeader = false;
644             for (int i = 0; i < mOpModeWatchers.size(); i++) {
645                 if (dumpOp >= 0 && dumpOp != mOpModeWatchers.keyAt(i)) {
646                     continue;
647                 }
648                 boolean printedOpHeader = false;
649                 ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
650                         mOpModeWatchers.valueAt(i);
651                 for (int j = 0; j < modeChangedListenerSet.size(); j++) {
652                     final OnOpModeChangedListener listener = modeChangedListenerSet.valueAt(j);
653                     if (dumpPackage != null
654                             && dumpUid != UserHandle.getAppId(listener.getWatchingUid())) {
655                         continue;
656                     }
657                     needSep = true;
658                     if (!printedHeader) {
659                         printWriter.println("  Op mode watchers:");
660                         printedHeader = true;
661                     }
662                     if (!printedOpHeader) {
663                         printWriter.print("    Op ");
664                         printWriter.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i)));
665                         printWriter.println(":");
666                         printedOpHeader = true;
667                     }
668                     printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
669                     printWriter.println(listener.toString());
670                 }
671             }
672         }
673 
674         if (mPackageModeWatchers.size() > 0 && dumpOp < 0) {
675             boolean printedHeader = false;
676             for (int i = 0; i < mPackageModeWatchers.size(); i++) {
677                 if (dumpPackage != null
678                         && !dumpPackage.equals(mPackageModeWatchers.keyAt(i))) {
679                     continue;
680                 }
681                 needSep = true;
682                 if (!printedHeader) {
683                     printWriter.println("  Package mode watchers:");
684                     printedHeader = true;
685                 }
686                 printWriter.print("    Pkg "); printWriter.print(mPackageModeWatchers.keyAt(i));
687                 printWriter.println(":");
688                 ArraySet<OnOpModeChangedListener> modeChangedListenerSet =
689                         mPackageModeWatchers.valueAt(i);
690 
691                 for (int j = 0; j < modeChangedListenerSet.size(); j++) {
692                     printWriter.print("      #"); printWriter.print(j); printWriter.print(": ");
693                     printWriter.println(modeChangedListenerSet.valueAt(j).toString());
694                 }
695             }
696         }
697         return needSep;
698     }
699 
scheduleWriteLocked()700     private void scheduleWriteLocked() {
701         if (!mWriteScheduled) {
702             mWriteScheduled = true;
703             mHandler.postDelayed(mWriteRunner, WRITE_DELAY);
704         }
705     }
706 
scheduleFastWriteLocked()707     private void scheduleFastWriteLocked() {
708         if (!mFastWriteScheduled) {
709             mWriteScheduled = true;
710             mFastWriteScheduled = true;
711             mHandler.removeCallbacks(mWriteRunner);
712             mHandler.postDelayed(mWriteRunner, 10 * 1000);
713         }
714     }
715 
716     @Override
writeState()717     public void writeState() {
718         synchronized (mFile) {
719             FileOutputStream stream;
720             try {
721                 stream = mFile.startWrite();
722             } catch (IOException e) {
723                 Slog.w(TAG, "Failed to write state: " + e);
724                 return;
725             }
726 
727             try {
728                 TypedXmlSerializer out = Xml.resolveSerializer(stream);
729                 out.startDocument(null, true);
730                 out.startTag(null, "app-ops");
731                 out.attributeInt(null, "v", CURRENT_VERSION);
732 
733                 SparseArray<SparseIntArray> uidModesCopy = new SparseArray<>();
734                 SparseArray<ArrayMap<String, SparseIntArray>> userPackageModesCopy =
735                         new SparseArray<>();
736                 int uidModesSize;
737                 int usersSize;
738                 synchronized (mLock) {
739                     uidModesSize = mUidModes.size();
740                     for (int uidIdx = 0; uidIdx < uidModesSize; uidIdx++) {
741                         int uid = mUidModes.keyAt(uidIdx);
742                         SparseIntArray modes = mUidModes.valueAt(uidIdx);
743                         uidModesCopy.put(uid, modes.clone());
744                     }
745                     usersSize = mUserPackageModes.size();
746                     for (int userIdx = 0; userIdx < usersSize; userIdx++) {
747                         int user = mUserPackageModes.keyAt(userIdx);
748                         ArrayMap<String, SparseIntArray> packageModes =
749                                 mUserPackageModes.valueAt(userIdx);
750                         ArrayMap<String, SparseIntArray> packageModesCopy = new ArrayMap<>();
751                         userPackageModesCopy.put(user, packageModesCopy);
752                         for (int pkgIdx = 0, packageModesSize = packageModes.size();
753                                 pkgIdx < packageModesSize; pkgIdx++) {
754                             String pkg = packageModes.keyAt(pkgIdx);
755                             SparseIntArray modes = packageModes.valueAt(pkgIdx);
756                             packageModesCopy.put(pkg, modes.clone());
757                         }
758                     }
759                 }
760 
761                 for (int uidStateNum = 0; uidStateNum < uidModesSize; uidStateNum++) {
762                     int uid = uidModesCopy.keyAt(uidStateNum);
763                     SparseIntArray modes = uidModesCopy.valueAt(uidStateNum);
764 
765                     out.startTag(null, "uid");
766                     out.attributeInt(null, "n", uid);
767 
768                     final int modesSize = modes.size();
769                     for (int modeIdx = 0; modeIdx < modesSize; modeIdx++) {
770                         final int op = modes.keyAt(modeIdx);
771                         final int mode = modes.valueAt(modeIdx);
772                         out.startTag(null, "op");
773                         out.attributeInt(null, "n", op);
774                         out.attributeInt(null, "m", mode);
775                         out.endTag(null, "op");
776                     }
777                     out.endTag(null, "uid");
778                 }
779 
780                 for (int userIdx = 0; userIdx < usersSize; userIdx++) {
781                     int userId = userPackageModesCopy.keyAt(userIdx);
782                     ArrayMap<String, SparseIntArray> packageModes =
783                             userPackageModesCopy.valueAt(userIdx);
784 
785                     out.startTag(null, "user");
786                     out.attributeInt(null, "n", userId);
787 
788                     int packageModesSize = packageModes.size();
789                     for (int pkgIdx = 0; pkgIdx < packageModesSize; pkgIdx++) {
790                         String pkg = packageModes.keyAt(pkgIdx);
791                         SparseIntArray modes = packageModes.valueAt(pkgIdx);
792 
793                         out.startTag(null, "pkg");
794                         out.attribute(null, "n", pkg);
795 
796                         final int modesSize = modes.size();
797                         for (int modeIdx = 0; modeIdx < modesSize; modeIdx++) {
798                             final int op = modes.keyAt(modeIdx);
799                             final int mode = modes.valueAt(modeIdx);
800 
801                             out.startTag(null, "op");
802                             out.attributeInt(null, "n", op);
803                             out.attributeInt(null, "m", mode);
804                             out.endTag(null, "op");
805                         }
806                         out.endTag(null, "pkg");
807                     }
808                     out.endTag(null, "user");
809                 }
810 
811                 out.endTag(null, "app-ops");
812                 out.endDocument();
813                 mFile.finishWrite(stream);
814             } catch (IOException e) {
815                 Slog.w(TAG, "Failed to write state, restoring backup.", e);
816                 mFile.failWrite(stream);
817             }
818         }
819     }
820 
821     /* Current format
822         <uid>
823           <op>
824         </uid>
825 
826         <user>
827           <pkg>
828             <op>
829           </pkg>
830         </user>
831      */
832 
833     @Override
readState()834     public void readState() {
835         synchronized (mFile) {
836             synchronized (mLock) {
837                 FileInputStream stream;
838                 try {
839                     stream = mFile.openRead();
840                 } catch (FileNotFoundException e) {
841                     Slog.i(TAG, "No existing app ops " + mFile.getBaseFile() + "; starting empty");
842                     mVersionAtBoot = NO_FILE_VERSION;
843                     return;
844                 }
845 
846                 try {
847                     TypedXmlPullParser parser = Xml.resolvePullParser(stream);
848                     int type;
849                     while ((type = parser.next()) != XmlPullParser.START_TAG
850                             && type != XmlPullParser.END_DOCUMENT) {
851                         // Parse next until we reach the start or end
852                     }
853 
854                     if (type != XmlPullParser.START_TAG) {
855                         throw new IllegalStateException("no start tag found");
856                     }
857 
858                     mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
859 
860                     int outerDepth = parser.getDepth();
861                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
862                             && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
863                         if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
864                             continue;
865                         }
866 
867                         String tagName = parser.getName();
868                         if (tagName.equals("pkg")) {
869                             // version 2 has the structure pkg -> uid -> op ->
870                             // in version 3, since pkg and uid states are kept completely
871                             // independent we switch to user -> pkg -> op
872                             readPackage(parser);
873                         } else if (tagName.equals("uid")) {
874                             readUidOps(parser);
875                         } else if (tagName.equals("user")) {
876                             readUser(parser);
877                         } else {
878                             Slog.w(TAG, "Unknown element under <app-ops>: "
879                                     + parser.getName());
880                             XmlUtils.skipCurrentTag(parser);
881                         }
882                     }
883                     return;
884                 } catch (XmlPullParserException e) {
885                     throw new RuntimeException(e);
886                 } catch (IOException e) {
887                     throw new RuntimeException(e);
888                 }
889             }
890         }
891     }
892 
893     @Override
shutdown()894     public void shutdown() {
895         boolean doWrite = false;
896         synchronized (this) {
897             if (mWriteScheduled) {
898                 mWriteScheduled = false;
899                 mFastWriteScheduled = false;
900                 mHandler.removeCallbacks(mWriteRunner);
901                 doWrite = true;
902             }
903         }
904         if (doWrite) {
905             writeState();
906         }
907     }
908 
909     @GuardedBy("mLock")
readUidOps(TypedXmlPullParser parser)910     private void readUidOps(TypedXmlPullParser parser) throws NumberFormatException,
911             XmlPullParserException, IOException {
912         final int uid = parser.getAttributeInt(null, "n");
913         SparseIntArray modes = mUidModes.get(uid);
914         if (modes == null) {
915             modes = new SparseIntArray();
916             mUidModes.put(uid, modes);
917         }
918 
919         int outerDepth = parser.getDepth();
920         int type;
921         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
922                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
923             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
924                 continue;
925             }
926 
927             String tagName = parser.getName();
928             if (tagName.equals("op")) {
929                 final int code = parser.getAttributeInt(null, "n");
930                 final int mode = parser.getAttributeInt(null, "m");
931 
932                 if (mode != opToDefaultMode(code)) {
933                     modes.put(code, mode);
934                 }
935             } else {
936                 Slog.w(TAG, "Unknown element under <uid>: "
937                         + parser.getName());
938                 XmlUtils.skipCurrentTag(parser);
939             }
940         }
941     }
942 
943     /*
944      * Used for migration when pkg is the depth=1 tag
945      */
946     @GuardedBy("mLock")
readPackage(TypedXmlPullParser parser)947     private void readPackage(TypedXmlPullParser parser)
948             throws NumberFormatException, XmlPullParserException, IOException {
949         String pkgName = parser.getAttributeValue(null, "n");
950         int outerDepth = parser.getDepth();
951         int type;
952         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
953                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
954             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
955                 continue;
956             }
957 
958             String tagName = parser.getName();
959             if (tagName.equals("uid")) {
960                 readUid(parser, pkgName);
961             } else {
962                 Slog.w(TAG, "Unknown element under <pkg>: "
963                         + parser.getName());
964                 XmlUtils.skipCurrentTag(parser);
965             }
966         }
967     }
968 
969     /*
970      * Used for migration when uid is the depth=2 tag
971      */
972     @GuardedBy("mLock")
readUid(TypedXmlPullParser parser, String pkgName)973     private void readUid(TypedXmlPullParser parser, String pkgName)
974             throws NumberFormatException, XmlPullParserException, IOException {
975         int userId = UserHandle.getUserId(parser.getAttributeInt(null, "n"));
976         int outerDepth = parser.getDepth();
977         int type;
978         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
979                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
980             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
981                 continue;
982             }
983 
984             String tagName = parser.getName();
985             if (tagName.equals("op")) {
986                 readOp(parser, userId, pkgName);
987             } else {
988                 Slog.w(TAG, "Unknown element under <pkg>: "
989                         + parser.getName());
990                 XmlUtils.skipCurrentTag(parser);
991             }
992         }
993     }
994 
995     @GuardedBy("mLock")
readUser(TypedXmlPullParser parser)996     private void readUser(TypedXmlPullParser parser)
997             throws NumberFormatException, XmlPullParserException, IOException {
998         int userId = parser.getAttributeInt(null, "n");
999         int outerDepth = parser.getDepth();
1000         int type;
1001         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1002                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1003             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1004                 continue;
1005             }
1006 
1007             String tagName = parser.getName();
1008             if (tagName.equals("pkg")) {
1009                 readPackage(parser, userId);
1010             } else {
1011                 Slog.w(TAG, "Unknown element under <user>: "
1012                         + parser.getName());
1013                 XmlUtils.skipCurrentTag(parser);
1014             }
1015         }
1016     }
1017 
1018     @GuardedBy("mLock")
readPackage(TypedXmlPullParser parser, int userId)1019     private void readPackage(TypedXmlPullParser parser, int userId)
1020             throws NumberFormatException, XmlPullParserException, IOException {
1021         String pkgName = parser.getAttributeValue(null, "n");
1022         int outerDepth = parser.getDepth();
1023         int type;
1024         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
1025                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
1026             if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
1027                 continue;
1028             }
1029 
1030             String tagName = parser.getName();
1031             if (tagName.equals("op")) {
1032                 readOp(parser, userId, pkgName);
1033             } else {
1034                 Slog.w(TAG, "Unknown element under <pkg>: "
1035                         + parser.getName());
1036                 XmlUtils.skipCurrentTag(parser);
1037             }
1038         }
1039     }
1040 
1041     @GuardedBy("mLock")
readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)1042     private void readOp(TypedXmlPullParser parser, int userId, @NonNull String pkgName)
1043             throws NumberFormatException, XmlPullParserException {
1044         final int opCode = parser.getAttributeInt(null, "n");
1045         final int defaultMode = AppOpsManager.opToDefaultMode(opCode);
1046         final int mode = parser.getAttributeInt(null, "m", defaultMode);
1047 
1048         if (mode != defaultMode) {
1049             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId);
1050             if (packageModes == null) {
1051                 packageModes = new ArrayMap<>();
1052                 mUserPackageModes.put(userId, packageModes);
1053             }
1054 
1055             SparseIntArray modes = packageModes.get(pkgName);
1056             if (modes == null) {
1057                 modes = new SparseIntArray();
1058                 packageModes.put(pkgName, modes);
1059             }
1060 
1061             modes.put(opCode, mode);
1062         }
1063     }
1064 
1065     @GuardedBy("mLock")
upgradeLocked(int oldVersion)1066     private void upgradeLocked(int oldVersion) {
1067         if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
1068             return;
1069         }
1070         Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
1071         switch (oldVersion) {
1072             case NO_VERSION:
1073                 upgradeRunAnyInBackgroundLocked();
1074                 // fall through
1075             case 1:
1076                 upgradeScheduleExactAlarmLocked();
1077                 // fall through
1078             case 2:
1079                 // split the appops.xml into appops.xml to store appop state and apppops_access.xml
1080                 // to store app-op access.
1081                 // fall through
1082             case 3:
1083                 resetUseFullScreenIntentLocked();
1084                 // fall through
1085         }
1086         scheduleFastWriteLocked();
1087     }
1088 
1089     /**
1090      * For all installed apps at time of upgrade, OP_RUN_ANY_IN_BACKGROUND will inherit the mode
1091      *  from RUN_IN_BACKGROUND.
1092      */
1093     @VisibleForTesting
1094     @GuardedBy("mLock")
upgradeRunAnyInBackgroundLocked()1095     void upgradeRunAnyInBackgroundLocked() {
1096         final int uidModesSize = mUidModes.size();
1097         for (int uidIdx = 0; uidIdx < uidModesSize; uidIdx++) {
1098             SparseIntArray modesForUid = mUidModes.valueAt(uidIdx);
1099 
1100             final int idx = modesForUid.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
1101             if (idx >= 0) {
1102                 // Only non-default should exist in the map
1103                 modesForUid.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, modesForUid.valueAt(idx));
1104             }
1105         }
1106 
1107         final int usersSize = mUserPackageModes.size();
1108         for (int userIdx = 0; userIdx < usersSize; userIdx++) {
1109             ArrayMap<String, SparseIntArray> packageModes =
1110                     mUserPackageModes.valueAt(userIdx);
1111 
1112             for (int pkgIdx = 0, packageModesSize = packageModes.size();
1113                     pkgIdx < packageModesSize; pkgIdx++) {
1114                 SparseIntArray modes = packageModes.valueAt(pkgIdx);
1115 
1116                 final int idx = modes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND);
1117                 if (idx >= 0) {
1118                     // Only non-default should exist in the map
1119                     modes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, modes.valueAt(idx));
1120                 }
1121             }
1122         }
1123     }
1124 
1125     /**
1126      * The interpretation of the default mode - MODE_DEFAULT - for OP_SCHEDULE_EXACT_ALARM is
1127      * changing. Simultaneously, we want to change this op's mode from MODE_DEFAULT to MODE_ALLOWED
1128      * for already installed apps. For newer apps, it will stay as MODE_DEFAULT.
1129      */
1130     @VisibleForTesting
1131     @GuardedBy("mLock")
upgradeScheduleExactAlarmLocked()1132     void upgradeScheduleExactAlarmLocked() {
1133         final PermissionManagerServiceInternal pmsi = LocalServices.getService(
1134                 PermissionManagerServiceInternal.class);
1135         final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
1136         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
1137 
1138         final String[] packagesDeclaringPermission = pmsi.getAppOpPermissionPackages(
1139                 AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
1140         final int[] userIds = umi.getUserIds();
1141 
1142         for (final String pkg : packagesDeclaringPermission) {
1143             for (int userId : userIds) {
1144                 final int uid = pmi.getPackageUid(pkg, 0, userId);
1145                 final int oldMode = getUidMode(uid, OP_SCHEDULE_EXACT_ALARM);
1146                 if (oldMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
1147                     setUidMode(uid, OP_SCHEDULE_EXACT_ALARM, MODE_ALLOWED);
1148                 }
1149             }
1150             // This appop is meant to be controlled at a uid level. So we leave package modes as
1151             // they are.
1152         }
1153     }
1154 
1155     /**
1156      * A cleanup step for U Beta 2 that reverts the OP_USE_FULL_SCREEN_INTENT's mode to MODE_DEFAULT
1157      * if the permission flags for the USE_FULL_SCREEN_INTENT permission does not have USER_SET.
1158      */
1159     @VisibleForTesting
1160     @GuardedBy("mLock")
resetUseFullScreenIntentLocked()1161     void resetUseFullScreenIntentLocked() {
1162         final PermissionManagerServiceInternal pmsi = LocalServices.getService(
1163                 PermissionManagerServiceInternal.class);
1164         final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
1165         final PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
1166         final PermissionManager permissionManager =
1167                 mContext.getSystemService(PermissionManager.class);
1168 
1169         final String permissionName = AppOpsManager.opToPermission(OP_USE_FULL_SCREEN_INTENT);
1170         final String[] packagesDeclaringPermission =
1171             pmsi.getAppOpPermissionPackages(permissionName);
1172         final int[] userIds = umi.getUserIds();
1173 
1174         for (final String pkg : packagesDeclaringPermission) {
1175             for (int userId : userIds) {
1176                 final int uid = pmi.getPackageUid(pkg, 0, userId);
1177                 final int flags = permissionManager.getPermissionFlags(pkg, permissionName,
1178                         UserHandle.of(userId));
1179                 if ((flags & PackageManager.FLAG_PERMISSION_USER_SET) == 0) {
1180                     setUidMode(uid, OP_USE_FULL_SCREEN_INTENT,
1181                             AppOpsManager.opToDefaultMode(OP_USE_FULL_SCREEN_INTENT));
1182                 }
1183             }
1184         }
1185     }
1186 
1187     @VisibleForTesting
getUidsWithNonDefaultModes()1188     List<Integer> getUidsWithNonDefaultModes() {
1189         List<Integer> result = new ArrayList<>();
1190         synchronized (mLock) {
1191             for (int i = 0; i < mUidModes.size(); i++) {
1192                 SparseIntArray modes = mUidModes.valueAt(i);
1193                 if (modes.size() > 0) {
1194                     result.add(mUidModes.keyAt(i));
1195                 }
1196             }
1197         }
1198 
1199         return result;
1200     }
1201 
1202     @VisibleForTesting
getPackagesWithNonDefaultModes()1203     List<UserPackage> getPackagesWithNonDefaultModes() {
1204         List<UserPackage> result = new ArrayList<>();
1205         synchronized (mLock) {
1206             for (int i = 0; i < mUserPackageModes.size(); i++) {
1207                 ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.valueAt(i);
1208                 for (int j = 0; j < packageModes.size(); j++) {
1209                     SparseIntArray modes = packageModes.valueAt(j);
1210                     if (modes.size() > 0) {
1211                         result.add(
1212                                 UserPackage.of(mUserPackageModes.keyAt(i), packageModes.keyAt(j)));
1213                     }
1214                 }
1215             }
1216         }
1217 
1218         return result;
1219     }
1220 }