1 /*
2  * Copyright (C) 2016 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;
18 
19 import static android.app.ActivityManager.UID_OBSERVER_ACTIVE;
20 import static android.app.ActivityManager.UID_OBSERVER_GONE;
21 import static android.os.Process.SYSTEM_UID;
22 
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.app.ActivityManager;
27 import android.app.ActivityManagerInternal;
28 import android.app.IActivityManager;
29 import android.app.SearchManager;
30 import android.app.UidObserver;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.content.pm.ActivityInfo;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.ResolveInfo;
39 import android.database.ContentObserver;
40 import android.net.Uri;
41 import android.os.Binder;
42 import android.os.Build;
43 import android.os.Handler;
44 import android.os.Looper;
45 import android.os.Message;
46 import android.os.RemoteException;
47 import android.os.ResultReceiver;
48 import android.os.ShellCallback;
49 import android.os.SystemProperties;
50 import android.os.UserHandle;
51 import android.os.UserManager;
52 import android.provider.DeviceConfig;
53 import android.provider.MediaStore;
54 import android.provider.Settings;
55 import android.system.ErrnoException;
56 import android.system.Os;
57 import android.system.OsConstants;
58 import android.util.ArrayMap;
59 import android.util.ArraySet;
60 import android.util.Slog;
61 
62 import com.android.internal.annotations.GuardedBy;
63 import com.android.internal.app.ResolverActivity;
64 import com.android.internal.os.BackgroundThread;
65 import com.android.internal.util.DumpUtils;
66 import com.android.internal.util.function.pooled.PooledLambda;
67 import com.android.server.wm.ActivityTaskManagerInternal;
68 
69 import dalvik.system.DexFile;
70 import dalvik.system.VMRuntime;
71 
72 import java.io.Closeable;
73 import java.io.DataInputStream;
74 import java.io.FileDescriptor;
75 import java.io.FileOutputStream;
76 import java.io.IOException;
77 import java.io.InputStream;
78 import java.io.PrintWriter;
79 import java.lang.annotation.Retention;
80 import java.lang.annotation.RetentionPolicy;
81 import java.lang.reflect.Method;
82 import java.util.ArrayList;
83 import java.util.List;
84 import java.util.zip.ZipEntry;
85 import java.util.zip.ZipFile;
86 
87 import sun.misc.Unsafe;
88 
89 /**
90  * <p>PinnerService pins important files for key processes in memory.</p>
91  * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
92  * overlay.</p>
93  * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
94  */
95 public final class PinnerService extends SystemService {
96     private static final boolean DEBUG = false;
97     private static final String TAG = "PinnerService";
98 
99     private static final String PIN_META_FILENAME = "pinlist.meta";
100     private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
101     private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
102             | PackageManager.MATCH_DIRECT_BOOT_AWARE
103             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
104 
105     private static final int KEY_CAMERA = 0;
106     private static final int KEY_HOME = 1;
107     private static final int KEY_ASSISTANT = 2;
108 
109     // Pin using pinlist.meta when pinning apps.
110     private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean(
111             "pinner.use_pinlist", true);
112     // Pin the whole odex/vdex/etc file when pinning apps.
113     private static boolean PROP_PIN_ODEX = SystemProperties.getBoolean(
114             "pinner.whole_odex", true);
115 
116     private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app.
117     private static final int MAX_HOME_PIN_SIZE = 6 * (1 << 20); // 6MB max for home app.
118     private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app.
119 
120     @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
121     @Retention(RetentionPolicy.SOURCE)
122     public @interface AppKey {}
123 
124     private final Context mContext;
125     private final ActivityTaskManagerInternal mAtmInternal;
126     private final ActivityManagerInternal mAmInternal;
127     private final IActivityManager mAm;
128     private final UserManager mUserManager;
129     private SearchManager mSearchManager;
130 
131     /** The list of the statically pinned files. */
132     @GuardedBy("this")
133     private final ArrayList<PinnedFile> mPinnedFiles = new ArrayList<>();
134 
135     /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
136     @GuardedBy("this")
137     private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
138 
139     /**
140      * The list of the pinned apps that need to be repinned as soon as the all processes of a given
141      * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
142      * loaded into the processes once it restarts. So in case background dex opt recompiled these
143      * files, we still need to keep the old ones pinned until the processes restart.
144      * <p>
145      * This is a map from uid to {@link AppKey}
146      */
147     @GuardedBy("this")
148     private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
149 
150     /**
151      * A set of {@link AppKey} that are configured to be pinned.
152      */
153     @GuardedBy("this")
154     private ArraySet<Integer> mPinKeys;
155 
156     private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
157     private long mPinAnonSize;
158     private long mPinAnonAddress;
159     private long mCurrentlyPinnedAnonSize;
160 
161     // Resource-configured pinner flags;
162     private final boolean mConfiguredToPinCamera;
163     private final boolean mConfiguredToPinHome;
164     private final boolean mConfiguredToPinAssistant;
165 
166     private BinderService mBinderService;
167     private PinnerHandler mPinnerHandler = null;
168 
169     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
170         @Override
171         public void onReceive(Context context, Intent intent) {
172           // If an app has updated, update pinned files accordingly.
173           if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
174                 Uri packageUri = intent.getData();
175                 String packageName = packageUri.getSchemeSpecificPart();
176                 ArraySet<String> updatedPackages = new ArraySet<>();
177                 updatedPackages.add(packageName);
178                 update(updatedPackages, true /* force */);
179             }
180         }
181     };
182 
PinnerService(Context context)183     public PinnerService(Context context) {
184         super(context);
185 
186         mContext = context;
187         mConfiguredToPinCamera = context.getResources().getBoolean(
188                 com.android.internal.R.bool.config_pinnerCameraApp);
189         mConfiguredToPinHome = context.getResources().getBoolean(
190                 com.android.internal.R.bool.config_pinnerHomeApp);
191         mConfiguredToPinAssistant = context.getResources().getBoolean(
192                 com.android.internal.R.bool.config_pinnerAssistantApp);
193         mPinKeys = createPinKeys();
194         mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
195 
196         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
197         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
198         mAm = ActivityManager.getService();
199 
200         mUserManager = mContext.getSystemService(UserManager.class);
201 
202         IntentFilter filter = new IntentFilter();
203         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
204         filter.addDataScheme("package");
205         mContext.registerReceiver(mBroadcastReceiver, filter);
206 
207         registerUidListener();
208         registerUserSetupCompleteListener();
209     }
210 
211     @Override
onStart()212     public void onStart() {
213         if (DEBUG) {
214             Slog.i(TAG, "Starting PinnerService");
215         }
216         mBinderService = new BinderService();
217         publishBinderService("pinner", mBinderService);
218         publishLocalService(PinnerService.class, this);
219 
220         mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
221         sendPinAppsMessage(UserHandle.USER_SYSTEM);
222     }
223 
224     @Override
onBootPhase(int phase)225     public void onBootPhase(int phase) {
226         // SearchManagerService is started after PinnerService, wait for PHASE_SYSTEM_SERVICES_READY
227         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
228             mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
229             sendPinAppsMessage(UserHandle.USER_SYSTEM);
230         }
231     }
232 
233     /**
234      * Repin apps on user switch.
235      * <p>
236      * If more than one user is using the device each user may set a different preference for the
237      * individual apps. Make sure that user's preference is pinned into memory.
238      */
239     @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)240     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
241         int userId = to.getUserIdentifier();
242         if (!mUserManager.isManagedProfile(userId)) {
243             sendPinAppsMessage(userId);
244         }
245     }
246 
247     @Override
onUserUnlocking(@onNull TargetUser user)248     public void onUserUnlocking(@NonNull TargetUser user) {
249         int userId = user.getUserIdentifier();
250         if (!mUserManager.isManagedProfile(userId)) {
251             sendPinAppsMessage(userId);
252         }
253     }
254 
255     /**
256      * Update the currently pinned files.
257      * Specifically, this only updates pinning for the apps that need to be pinned.
258      * The other files pinned in onStart will not need to be updated.
259      */
update(ArraySet<String> updatedPackages, boolean force)260     public void update(ArraySet<String> updatedPackages, boolean force) {
261         ArraySet<Integer> pinKeys = getPinKeys();
262         int currentUser = ActivityManager.getCurrentUser();
263         for (int i = pinKeys.size() - 1; i >= 0; i--) {
264             int key = pinKeys.valueAt(i);
265             ApplicationInfo info = getInfoForKey(key, currentUser);
266             if (info != null && updatedPackages.contains(info.packageName)) {
267                 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
268                 sendPinAppMessage(key, currentUser, force);
269             }
270         }
271     }
272 
273     /** Returns information about pinned files and sizes for StatsPullAtomService. */
dumpDataForStatsd()274     public List<PinnedFileStats> dumpDataForStatsd() {
275         List<PinnedFileStats> pinnedFileStats = new ArrayList<>();
276         synchronized (PinnerService.this) {
277             for (PinnedFile pinnedFile : mPinnedFiles) {
278                 pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile));
279             }
280 
281             for (int key : mPinnedApps.keySet()) {
282                 PinnedApp app = mPinnedApps.get(key);
283                 for (PinnedFile pinnedFile : mPinnedApps.get(key).mFiles) {
284                     pinnedFileStats.add(new PinnedFileStats(app.uid, pinnedFile));
285                 }
286             }
287         }
288         return pinnedFileStats;
289     }
290 
291     /** Wrapper class for statistics for a pinned file. */
292     public static class PinnedFileStats {
293         public final int uid;
294         public final String filename;
295         public final int sizeKb;
296 
PinnedFileStats(int uid, PinnedFile file)297         protected PinnedFileStats(int uid, PinnedFile file) {
298             this.uid = uid;
299             this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
300             this.sizeKb = file.bytesPinned / 1024;
301         }
302     }
303 
304     /**
305      * Handler for on start pinning message
306      */
handlePinOnStart()307     private void handlePinOnStart() {
308         // Files to pin come from the overlay and can be specified per-device config
309         String[] filesToPin = mContext.getResources().getStringArray(
310             com.android.internal.R.array.config_defaultPinnerServiceFiles);
311         // Continue trying to pin each file even if we fail to pin some of them
312         for (String fileToPin : filesToPin) {
313             PinnedFile pf = pinFile(fileToPin,
314                                     Integer.MAX_VALUE,
315                                     /*attemptPinIntrospection=*/false);
316             if (pf == null) {
317                 Slog.e(TAG, "Failed to pin file = " + fileToPin);
318                 continue;
319             }
320             synchronized (this) {
321                 mPinnedFiles.add(pf);
322             }
323             if (fileToPin.endsWith(".jar") | fileToPin.endsWith(".apk")) {
324                 // Check whether the runtime has compilation artifacts to pin.
325                 String arch = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);
326                 String[] files = null;
327                 try {
328                     files = DexFile.getDexFileOutputPaths(fileToPin, arch);
329                 } catch (IOException ioe) { }
330                 if (files == null) {
331                     continue;
332                 }
333                 for (String file : files) {
334                     PinnedFile df = pinFile(file,
335                                             Integer.MAX_VALUE,
336                                             /*attemptPinIntrospection=*/false);
337                     if (df == null) {
338                         Slog.i(TAG, "Failed to pin ART file = " + file);
339                         continue;
340                     }
341                     synchronized (this) {
342                         mPinnedFiles.add(df);
343                     }
344                 }
345             }
346         }
347     }
348 
349     /**
350      * Registers a listener to repin the home app when user setup is complete, as the home intent
351      * initially resolves to setup wizard, but once setup is complete, it will resolve to the
352      * regular home app.
353      */
registerUserSetupCompleteListener()354     private void registerUserSetupCompleteListener() {
355         Uri userSetupCompleteUri = Settings.Secure.getUriFor(
356                 Settings.Secure.USER_SETUP_COMPLETE);
357         mContext.getContentResolver().registerContentObserver(userSetupCompleteUri,
358                 false, new ContentObserver(null) {
359                     @Override
360                     public void onChange(boolean selfChange, Uri uri) {
361                         if (userSetupCompleteUri.equals(uri)) {
362                             sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
363                                     true /* force */);
364                         }
365                     }
366                 }, UserHandle.USER_ALL);
367     }
368 
registerUidListener()369     private void registerUidListener() {
370         try {
371             mAm.registerUidObserver(new UidObserver() {
372                 @Override
373                 public void onUidGone(int uid, boolean disabled) {
374                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
375                             PinnerService::handleUidGone, PinnerService.this, uid));
376                 }
377 
378                 @Override
379                 public void onUidActive(int uid)  {
380                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
381                             PinnerService::handleUidActive, PinnerService.this, uid));
382                 }
383             }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null);
384         } catch (RemoteException e) {
385             Slog.e(TAG, "Failed to register uid observer", e);
386         }
387     }
388 
handleUidGone(int uid)389     private void handleUidGone(int uid) {
390         updateActiveState(uid, false /* active */);
391         int key;
392         synchronized (this) {
393 
394             // In case we have a pending repin, repin now. See mPendingRepin for more information.
395             key = mPendingRepin.getOrDefault(uid, -1);
396             if (key == -1) {
397                 return;
398             }
399             mPendingRepin.remove(uid);
400         }
401         pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
402     }
403 
handleUidActive(int uid)404     private void handleUidActive(int uid) {
405         updateActiveState(uid, true /* active */);
406     }
407 
updateActiveState(int uid, boolean active)408     private void updateActiveState(int uid, boolean active) {
409         synchronized (this) {
410             for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
411                 PinnedApp app = mPinnedApps.valueAt(i);
412                 if (app.uid == uid) {
413                     app.active = active;
414                 }
415             }
416         }
417     }
418 
unpinApps()419     private void unpinApps() {
420         ArraySet<Integer> pinKeys = getPinKeys();
421         for (int i = pinKeys.size() - 1; i >= 0; i--) {
422             int key = pinKeys.valueAt(i);
423             unpinApp(key);
424         }
425     }
426 
unpinApp(@ppKey int key)427     private void unpinApp(@AppKey int key) {
428         ArrayList<PinnedFile> pinnedAppFiles;
429         synchronized (this) {
430             PinnedApp app = mPinnedApps.get(key);
431             if (app == null) {
432                 return;
433             }
434             mPinnedApps.remove(key);
435             pinnedAppFiles = new ArrayList<>(app.mFiles);
436         }
437         for (PinnedFile pinnedFile : pinnedAppFiles) {
438             pinnedFile.close();
439         }
440     }
441 
isResolverActivity(ActivityInfo info)442     private boolean isResolverActivity(ActivityInfo info) {
443         return ResolverActivity.class.getName().equals(info.name);
444     }
445 
getCameraInfo(int userHandle)446     private ApplicationInfo getCameraInfo(int userHandle) {
447         Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
448         ApplicationInfo info = getApplicationInfoForIntent(cameraIntent, userHandle,
449             false /* defaultToSystemApp */);
450 
451         // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
452         // We don't use _SECURE first because it will never get set on a device
453         // without File-based Encryption. But if the user has only set the intent
454         // before unlocking their device, we may still be able to identify their
455         // preference using this intent.
456         if (info == null) {
457             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
458             info = getApplicationInfoForIntent(cameraIntent, userHandle,
459                 false /* defaultToSystemApp */);
460         }
461 
462         // If the _SECURE intent doesn't resolve, try the original intent but request
463         // the system app for camera if there was more than one result.
464         if (info == null) {
465             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
466             info = getApplicationInfoForIntent(cameraIntent, userHandle,
467                 true /* defaultToSystemApp */);
468         }
469         return info;
470     }
471 
getHomeInfo(int userHandle)472     private ApplicationInfo getHomeInfo(int userHandle) {
473         Intent intent = mAtmInternal.getHomeIntent();
474         return getApplicationInfoForIntent(intent, userHandle, false);
475     }
476 
getAssistantInfo(int userHandle)477     private ApplicationInfo getAssistantInfo(int userHandle) {
478         if (mSearchManager != null) {
479             Intent intent = mSearchManager.getAssistIntent(false);
480             return getApplicationInfoForIntent(intent, userHandle, true);
481         }
482         return null;
483     }
484 
getApplicationInfoForIntent(Intent intent, int userHandle, boolean defaultToSystemApp)485     private ApplicationInfo getApplicationInfoForIntent(Intent intent, int userHandle,
486             boolean defaultToSystemApp) {
487         if (intent == null) {
488             return null;
489         }
490 
491         ResolveInfo resolveInfo = mContext.getPackageManager().resolveActivityAsUser(intent,
492                 MATCH_FLAGS, userHandle);
493 
494         // If this intent can resolve to only one app, choose that one.
495         // Otherwise, if we've requested to default to the system app, return it;
496         // if we have not requested that default, return null if there's more than one option.
497         // If there's more than one system app, return null since we don't know which to pick.
498         if (resolveInfo == null) {
499             return null;
500         }
501 
502         if (!isResolverActivity(resolveInfo.activityInfo)) {
503             return resolveInfo.activityInfo.applicationInfo;
504         }
505 
506         if (defaultToSystemApp) {
507             List<ResolveInfo> infoList = mContext.getPackageManager()
508                 .queryIntentActivitiesAsUser(intent, MATCH_FLAGS, userHandle);
509             ApplicationInfo systemAppInfo = null;
510             for (ResolveInfo info : infoList) {
511                 if ((info.activityInfo.applicationInfo.flags
512                       & ApplicationInfo.FLAG_SYSTEM) != 0) {
513                     if (systemAppInfo == null) {
514                         systemAppInfo = info.activityInfo.applicationInfo;
515                     } else {
516                         // If there's more than one system app, return null due to ambiguity.
517                         return null;
518                     }
519                 }
520             }
521             return systemAppInfo;
522         }
523 
524         return null;
525     }
526 
sendPinAppsMessage(int userHandle)527     private void sendPinAppsMessage(int userHandle) {
528         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApps, this,
529                 userHandle));
530     }
531 
sendPinAppsWithUpdatedKeysMessage(int userHandle)532     private void sendPinAppsWithUpdatedKeysMessage(int userHandle) {
533         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinAppsWithUpdatedKeys,
534                 this, userHandle));
535     }
sendUnpinAppsMessage()536     private void sendUnpinAppsMessage() {
537         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this));
538     }
539 
createPinKeys()540     private ArraySet<Integer> createPinKeys() {
541         ArraySet<Integer> pinKeys = new ArraySet<>();
542         // Pin the camera application. Default to the system property only if the experiment
543         // phenotype property is not set.
544         boolean shouldPinCamera = mConfiguredToPinCamera
545                 && DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
546                         "pin_camera",
547                         SystemProperties.getBoolean("pinner.pin_camera", true));
548         if (shouldPinCamera) {
549             pinKeys.add(KEY_CAMERA);
550         } else if (DEBUG) {
551             Slog.i(TAG, "Pinner - skip pinning camera app");
552         }
553 
554         if (mConfiguredToPinHome) {
555             pinKeys.add(KEY_HOME);
556         }
557         if (mConfiguredToPinAssistant) {
558             pinKeys.add(KEY_ASSISTANT);
559         }
560 
561         mPinAnonSize = DeviceConfig.getLong(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
562                 "pin_anon_size",
563                 SystemProperties.getLong("pinner.pin_anon_size", 0));
564         mPinAnonSize = Math.max(0, Math.min(mPinAnonSize, MAX_ANON_SIZE));
565 
566         return pinKeys;
567     }
568 
getPinKeys()569     private synchronized ArraySet<Integer> getPinKeys() {
570         return mPinKeys;
571     }
572 
pinApps(int userHandle)573     private void pinApps(int userHandle) {
574         pinAppsInternal(userHandle, false);
575     }
576 
pinAppsWithUpdatedKeys(int userHandle)577     private void pinAppsWithUpdatedKeys(int userHandle) {
578         pinAppsInternal(userHandle, true);
579     }
580 
581     /**
582      * @param updateKeys True if the pinned app list has to be updated. This is true only when
583      *                   "pinner repin" shell command is requested.
584      */
pinAppsInternal(int userHandle, boolean updateKeys)585     private void pinAppsInternal(int userHandle, boolean updateKeys) {
586         if (updateKeys) {
587             ArraySet<Integer> newKeys = createPinKeys();
588             synchronized (this) {
589                 // This code path demands preceding unpinApps() call.
590                 if (!mPinnedApps.isEmpty()) {
591                     Slog.e(TAG, "Attempted to update a list of apps, "
592                             + "but apps were already pinned. Skipping.");
593                     return;
594                 }
595 
596                 mPinKeys = newKeys;
597             }
598         }
599 
600         ArraySet<Integer> currentPinKeys = getPinKeys();
601         for (int i = currentPinKeys.size() - 1; i >= 0; i--) {
602             int key = currentPinKeys.valueAt(i);
603             pinApp(key, userHandle, true /* force */);
604         }
605         pinAnonRegion();
606     }
607 
608     /**
609      * @see #pinApp(int, int, boolean)
610      */
sendPinAppMessage(int key, int userHandle, boolean force)611     private void sendPinAppMessage(int key, int userHandle, boolean force) {
612         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::pinApp, this,
613                 key, userHandle, force));
614     }
615 
616     /**
617      * Pins an app of a specific type {@code key}.
618      *
619      * @param force If false, this will not repin the app if it's currently active. See
620      *              {@link #mPendingRepin}.
621      */
pinApp(int key, int userHandle, boolean force)622     private void pinApp(int key, int userHandle, boolean force) {
623         int uid = getUidForKey(key);
624 
625         // In case the app is currently active, don't repin until next process restart. See
626         // mPendingRepin for more information.
627         if (!force && uid != -1) {
628             synchronized (this) {
629                 mPendingRepin.put(uid, key);
630             }
631             return;
632         }
633         unpinApp(key);
634         ApplicationInfo info = getInfoForKey(key, userHandle);
635         if (info != null) {
636             pinApp(key, info);
637         }
638     }
639 
640     /**
641      * Checks whether the pinned package with {@code key} is active or not.
642 
643      * @return The uid of the pinned app, or {@code -1} otherwise.
644      */
getUidForKey(@ppKey int key)645     private int getUidForKey(@AppKey int key) {
646         synchronized (this) {
647             PinnedApp existing = mPinnedApps.get(key);
648             return existing != null && existing.active
649                     ? existing.uid
650                     : -1;
651         }
652     }
653 
654     /**
655      * Retrieves the current application info for the given app type.
656      *
657      * @param key The app type to retrieve the info for.
658      * @param userHandle The user id of the current user.
659      */
getInfoForKey(@ppKey int key, int userHandle)660     private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
661         switch (key) {
662             case KEY_CAMERA:
663                 return getCameraInfo(userHandle);
664             case KEY_HOME:
665                 return getHomeInfo(userHandle);
666             case KEY_ASSISTANT:
667                 return getAssistantInfo(userHandle);
668             default:
669                 return null;
670         }
671     }
672 
673     /**
674      * @return The app type name for {@code key}.
675      */
getNameForKey(@ppKey int key)676     private String getNameForKey(@AppKey int key) {
677         switch (key) {
678             case KEY_CAMERA:
679                 return "Camera";
680             case KEY_HOME:
681                 return "Home";
682             case KEY_ASSISTANT:
683                 return "Assistant";
684             default:
685                 return null;
686         }
687     }
688 
689     /**
690      * Pin an empty anonymous region. This should only be used for ablation experiments.
691      */
pinAnonRegion()692     private void pinAnonRegion() {
693         if (mPinAnonSize == 0) {
694             return;
695         }
696         long alignedPinSize = mPinAnonSize;
697         if (alignedPinSize % PAGE_SIZE != 0) {
698             alignedPinSize -= alignedPinSize % PAGE_SIZE;
699             Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
700         }
701         if (mPinAnonAddress != 0
702                 && mCurrentlyPinnedAnonSize != alignedPinSize) {
703             unpinAnonRegion();
704         }
705         long address = 0;
706         try {
707             address = Os.mmap(0, alignedPinSize,
708                     OsConstants.PROT_READ | OsConstants.PROT_WRITE,
709                     OsConstants.MAP_PRIVATE | OsConstants.MAP_ANONYMOUS,
710                     new FileDescriptor(), /*offset=*/0);
711 
712             Unsafe tempUnsafe = null;
713             Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
714             for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
715                 f.setAccessible(true);
716                 Object obj = f.get(null);
717                 if (clazz.isInstance(obj)) {
718                     tempUnsafe = clazz.cast(obj);
719                 }
720             }
721             if (tempUnsafe == null) {
722                 throw new Exception("Couldn't get Unsafe");
723             }
724             Method setMemory = clazz.getMethod("setMemory", long.class, long.class, byte.class);
725             setMemory.invoke(tempUnsafe, address, alignedPinSize, (byte) 1);
726             Os.mlock(address, alignedPinSize);
727             mCurrentlyPinnedAnonSize = alignedPinSize;
728             mPinAnonAddress = address;
729             address = -1;
730             Slog.e(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
731         } catch (Exception ex) {
732             Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
733             return;
734         } finally {
735             if (address >= 0) {
736                 safeMunmap(address, alignedPinSize);
737             }
738         }
739     }
740 
unpinAnonRegion()741     private void unpinAnonRegion() {
742         if (mPinAnonAddress != 0) {
743             safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
744         }
745     }
746 
747     /**
748      * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
749      */
getSizeLimitForKey(@ppKey int key)750     private int getSizeLimitForKey(@AppKey int key) {
751         switch (key) {
752             case KEY_CAMERA:
753                 return MAX_CAMERA_PIN_SIZE;
754             case KEY_HOME:
755                 return MAX_HOME_PIN_SIZE;
756             case KEY_ASSISTANT:
757                 return MAX_ASSISTANT_PIN_SIZE;
758             default:
759                 return 0;
760         }
761     }
762 
763     /**
764      * Pins an application.
765      *
766      * @param key The key of the app to pin.
767      * @param appInfo The corresponding app info.
768      */
pinApp(@ppKey int key, @Nullable ApplicationInfo appInfo)769     private void pinApp(@AppKey int key, @Nullable ApplicationInfo appInfo) {
770         if (appInfo == null) {
771             return;
772         }
773 
774         PinnedApp pinnedApp = new PinnedApp(appInfo);
775         synchronized (this) {
776             mPinnedApps.put(key, pinnedApp);
777         }
778 
779 
780         // pin APK
781         final int pinSizeLimit = getSizeLimitForKey(key);
782         List<String> apks = new ArrayList<>();
783         apks.add(appInfo.sourceDir);
784 
785         if (appInfo.splitSourceDirs != null) {
786             for (String splitApk : appInfo.splitSourceDirs) {
787                 apks.add(splitApk);
788             }
789         }
790 
791         int apkPinSizeLimit = pinSizeLimit;
792         for (String apk: apks) {
793             if (apkPinSizeLimit <= 0) {
794                 Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
795                 // Continue instead of break to print all skipped APK names.
796                 continue;
797             }
798 
799             PinnedFile pf = pinFile(apk, apkPinSizeLimit, /*attemptPinIntrospection=*/true);
800             if (pf == null) {
801                 Slog.e(TAG, "Failed to pin " + apk);
802                 continue;
803             }
804 
805             if (DEBUG) {
806                 Slog.i(TAG, "Pinned " + pf.fileName);
807             }
808             synchronized (this) {
809                 pinnedApp.mFiles.add(pf);
810             }
811 
812             apkPinSizeLimit -= pf.bytesPinned;
813         }
814 
815         // determine the ABI from either ApplicationInfo or Build
816         String abi = appInfo.primaryCpuAbi != null ? appInfo.primaryCpuAbi :
817                 Build.SUPPORTED_ABIS[0];
818         String arch = VMRuntime.getInstructionSet(abi);
819         // get the path to the odex or oat file
820         String baseCodePath = appInfo.getBaseCodePath();
821         String[] files = null;
822         try {
823             files = DexFile.getDexFileOutputPaths(baseCodePath, arch);
824         } catch (IOException ioe) {}
825         if (files == null) {
826             return;
827         }
828 
829         //not pinning the oat/odex is not a fatal error
830         for (String file : files) {
831             PinnedFile pf = pinFile(file, pinSizeLimit, /*attemptPinIntrospection=*/false);
832             if (pf != null) {
833                 synchronized (this) {
834                     if (PROP_PIN_ODEX) {
835                       pinnedApp.mFiles.add(pf);
836                     }
837                 }
838                 if (DEBUG) {
839                     if (PROP_PIN_ODEX) {
840                         Slog.i(TAG, "Pinned " + pf.fileName);
841                     } else {
842                         Slog.i(TAG, "Pinned [skip] " + pf.fileName);
843                     }
844                 }
845             }
846         }
847     }
848 
849     /** mlock length bytes of fileToPin in memory
850      *
851      * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
852      * look for a "pinlist.meta" file in the archive root directory. The structure of this
853      * file is a PINLIST_META as described below:
854      *
855      * <pre>
856      *   PINLIST_META: PIN_RANGE*
857      *   PIN_RANGE: PIN_START PIN_LENGTH
858      *   PIN_START: big endian i32: offset in bytes of pin region from file start
859      *   PIN_LENGTH: big endian i32: length of pin region in bytes
860      * </pre>
861      *
862      * (We use big endian because that's what DataInputStream is hardcoded to use.)
863      *
864      * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
865      * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
866      *
867      * After we open a file, we march through the list of pin ranges and attempt to pin
868      * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
869      * pinned range to fit.)  In this way, by choosing to emit certain PIN_RANGE pairs
870      * before others, file generators can express pins in priority order, making most
871      * effective use of the pinned-page quota.
872      *
873      * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
874      * meaningful interpretation. Also, a range locking a single byte of a page locks the
875      * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
876      * are legal, but each pin of a byte counts toward the pin quota regardless of whether
877      * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
878      * that ranges are non-overlapping.
879      *
880      * @param fileToPin Path to file to pin
881      * @param maxBytesToPin Maximum number of bytes to pin
882      * @param attemptPinIntrospection If true, try to open file as a
883      *   zip in order to extract the
884      * @return Pinned memory resource owner thing or null on error
885      */
pinFile(String fileToPin, int maxBytesToPin, boolean attemptPinIntrospection)886     private static PinnedFile pinFile(String fileToPin,
887                                       int maxBytesToPin,
888                                       boolean attemptPinIntrospection) {
889         ZipFile fileAsZip = null;
890         InputStream pinRangeStream = null;
891         try {
892             if (attemptPinIntrospection) {
893                 fileAsZip = maybeOpenZip(fileToPin);
894             }
895 
896             if (fileAsZip != null) {
897                 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
898             }
899 
900             Slog.d(TAG, "pinRangeStream: " + pinRangeStream);
901 
902             PinRangeSource pinRangeSource = (pinRangeStream != null)
903                 ? new PinRangeSourceStream(pinRangeStream)
904                 : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
905             return pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
906         } finally {
907             safeClose(pinRangeStream);
908             safeClose(fileAsZip);  // Also closes any streams we've opened
909         }
910     }
911 
912     /**
913      * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
914      * error, and return null.
915      */
maybeOpenZip(String fileName)916     private static ZipFile maybeOpenZip(String fileName) {
917         ZipFile zip = null;
918         try {
919             zip = new ZipFile(fileName);
920         } catch (IOException ex) {
921             Slog.w(TAG,
922                    String.format(
923                        "could not open \"%s\" as zip: pinning as blob",
924                                  fileName),
925                    ex);
926         }
927         return zip;
928     }
929 
930     /**
931      * Open a pin metadata file in the zip if one is present.
932      *
933      * @param zipFile Zip file to search
934      * @return Open input stream or null on any error
935      */
maybeOpenPinMetaInZip(ZipFile zipFile, String fileName)936     private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
937         if (!PROP_PIN_PINLIST) {
938             if (DEBUG) {
939                 Slog.i(TAG, "Pin - skip pinlist.meta in " + fileName);
940             }
941             return null;
942         }
943 
944         ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
945         InputStream pinMetaStream = null;
946         if (pinMetaEntry != null) {
947             try {
948                 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
949             } catch (IOException ex) {
950                 Slog.w(TAG,
951                        String.format("error reading pin metadata \"%s\": pinning as blob",
952                                      fileName),
953                        ex);
954             }
955         }
956         return pinMetaStream;
957     }
958 
959     private static abstract class PinRangeSource {
960         /** Retrive a range to pin.
961          *
962          * @param outPinRange Receives the pin region
963          * @return True if we filled in outPinRange or false if we're out of pin entries
964          */
read(PinRange outPinRange)965         abstract boolean read(PinRange outPinRange);
966     }
967 
968     private static final class PinRangeSourceStatic extends PinRangeSource {
969         private final int mPinStart;
970         private final int mPinLength;
971         private boolean mDone = false;
972 
PinRangeSourceStatic(int pinStart, int pinLength)973         PinRangeSourceStatic(int pinStart, int pinLength) {
974             mPinStart = pinStart;
975             mPinLength = pinLength;
976         }
977 
978         @Override
read(PinRange outPinRange)979         boolean read(PinRange outPinRange) {
980             outPinRange.start = mPinStart;
981             outPinRange.length = mPinLength;
982             boolean done = mDone;
983             mDone = true;
984             return !done;
985         }
986     }
987 
988     private static final class PinRangeSourceStream extends PinRangeSource {
989         private final DataInputStream mStream;
990         private boolean mDone = false;
991 
PinRangeSourceStream(InputStream stream)992         PinRangeSourceStream(InputStream stream) {
993             mStream = new DataInputStream(stream);
994         }
995 
996         @Override
read(PinRange outPinRange)997         boolean read(PinRange outPinRange) {
998             if (!mDone) {
999                 try {
1000                     outPinRange.start = mStream.readInt();
1001                     outPinRange.length = mStream.readInt();
1002                 } catch (IOException ex) {
1003                     mDone = true;
1004                 }
1005             }
1006             return !mDone;
1007         }
1008     }
1009 
1010     /**
1011      * Helper for pinFile.
1012      *
1013      * @param fileToPin Name of file to pin
1014      * @param maxBytesToPin Maximum number of bytes to pin
1015      * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
1016      *   to pin.
1017      * @return PinnedFile or null on error
1018      */
pinFileRanges( String fileToPin, int maxBytesToPin, PinRangeSource pinRangeSource)1019     private static PinnedFile pinFileRanges(
1020         String fileToPin,
1021         int maxBytesToPin,
1022         PinRangeSource pinRangeSource)
1023     {
1024         FileDescriptor fd = new FileDescriptor();
1025         long address = -1;
1026         int mapSize = 0;
1027 
1028         try {
1029             int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC);
1030             fd = Os.open(fileToPin, openFlags, 0);
1031             mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
1032             address = Os.mmap(0, mapSize,
1033                               OsConstants.PROT_READ,
1034                               OsConstants.MAP_SHARED,
1035                               fd, /*offset=*/0);
1036 
1037             PinRange pinRange = new PinRange();
1038             int bytesPinned = 0;
1039 
1040             // We pin at page granularity, so make sure the limit is page-aligned
1041             if (maxBytesToPin % PAGE_SIZE != 0) {
1042                 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
1043             }
1044 
1045             while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
1046                 int pinStart = pinRange.start;
1047                 int pinLength = pinRange.length;
1048                 pinStart = clamp(0, pinStart, mapSize);
1049                 pinLength = clamp(0, pinLength, mapSize - pinStart);
1050                 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
1051 
1052                 // mlock doesn't require the region to be page-aligned, but we snap the
1053                 // lock region to page boundaries anyway so that we don't under-count
1054                 // locking a single byte of a page as a charge of one byte even though the
1055                 // OS will retain the whole page. Thanks to this adjustment, we slightly
1056                 // over-count the pin charge of back-to-back pins touching the same page,
1057                 // but better that than undercounting. Besides: nothing stops pin metafile
1058                 // creators from making the actual regions page-aligned.
1059                 pinLength += pinStart % PAGE_SIZE;
1060                 pinStart -= pinStart % PAGE_SIZE;
1061                 if (pinLength % PAGE_SIZE != 0) {
1062                     pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
1063                 }
1064                 pinLength = clamp(0, pinLength, maxBytesToPin - bytesPinned);
1065 
1066                 if (pinLength > 0) {
1067                     if (DEBUG) {
1068                         Slog.d(TAG,
1069                                String.format(
1070                                    "pinning at %s %s bytes of %s",
1071                                    pinStart, pinLength, fileToPin));
1072                     }
1073                     Os.mlock(address + pinStart, pinLength);
1074                 }
1075                 bytesPinned += pinLength;
1076             }
1077 
1078             PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
1079             address = -1;  // Ownership transferred
1080             return pinnedFile;
1081         } catch (ErrnoException ex) {
1082             Slog.e(TAG, "Could not pin file " + fileToPin, ex);
1083             return null;
1084         } finally {
1085             safeClose(fd);
1086             if (address >= 0) {
1087                 safeMunmap(address, mapSize);
1088             }
1089         }
1090     }
1091 
clamp(int min, int value, int max)1092     private static int clamp(int min, int value, int max) {
1093         return Math.max(min, Math.min(value, max));
1094     }
1095 
safeMunmap(long address, long mapSize)1096     private static void safeMunmap(long address, long mapSize) {
1097         try {
1098             Os.munmap(address, mapSize);
1099         } catch (ErrnoException ex) {
1100             Slog.w(TAG, "ignoring error in unmap", ex);
1101         }
1102     }
1103 
1104     /**
1105      * Close FD, swallowing irrelevant errors.
1106      */
safeClose(@ullable FileDescriptor fd)1107     private static void safeClose(@Nullable FileDescriptor fd) {
1108         if (fd != null && fd.valid()) {
1109             try {
1110                 Os.close(fd);
1111             } catch (ErrnoException ex) {
1112                 // Swallow the exception: non-EBADF errors in close(2)
1113                 // indicate deferred paging write errors, which we
1114                 // don't care about here. The underlying file
1115                 // descriptor is always closed.
1116                 if (ex.errno == OsConstants.EBADF) {
1117                     throw new AssertionError(ex);
1118                 }
1119             }
1120         }
1121     }
1122 
1123     /**
1124      * Close closeable thing, swallowing errors.
1125      */
safeClose(@ullable Closeable thing)1126     private static void safeClose(@Nullable Closeable thing) {
1127         if (thing != null) {
1128             try {
1129                 thing.close();
1130             } catch (IOException ex) {
1131                 Slog.w(TAG, "ignoring error closing resource: " + thing, ex);
1132             }
1133         }
1134     }
1135 
1136     private final class BinderService extends Binder {
1137         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1138         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1139             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
1140             synchronized (PinnerService.this) {
1141                 long totalSize = 0;
1142                 for (PinnedFile pinnedFile : mPinnedFiles) {
1143                     pw.format("%s %s\n", pinnedFile.fileName, pinnedFile.bytesPinned);
1144                     totalSize += pinnedFile.bytesPinned;
1145                 }
1146                 pw.println();
1147                 for (int key : mPinnedApps.keySet()) {
1148                     PinnedApp app = mPinnedApps.get(key);
1149                     pw.print(getNameForKey(key));
1150                     pw.print(" uid="); pw.print(app.uid);
1151                     pw.print(" active="); pw.print(app.active);
1152                     pw.println();
1153                     for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
1154                         pw.print("  "); pw.format("%s %s\n", pf.fileName, pf.bytesPinned);
1155                         totalSize += pf.bytesPinned;
1156                     }
1157                 }
1158                 if (mPinAnonAddress != 0) {
1159                     pw.format("Pinned anon region: %s\n", mCurrentlyPinnedAnonSize);
1160                 }
1161                 pw.format("Total size: %s\n", totalSize);
1162                 pw.println();
1163                 if (!mPendingRepin.isEmpty()) {
1164                     pw.print("Pending repin: ");
1165                     for (int key : mPendingRepin.values()) {
1166                         pw.print(getNameForKey(key)); pw.print(' ');
1167                     }
1168                     pw.println();
1169                 }
1170             }
1171         }
1172 
repin()1173         private void repin() {
1174             sendUnpinAppsMessage();
1175             // TODO(morrita): Consider supporting non-system user.
1176             sendPinAppsWithUpdatedKeysMessage(UserHandle.USER_SYSTEM);
1177         }
1178 
printError(FileDescriptor out, String message)1179         private void printError(FileDescriptor out, String message) {
1180             PrintWriter writer = new PrintWriter(new FileOutputStream(out));
1181             writer.println(message);
1182             writer.flush();
1183         }
1184 
1185         @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)1186         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1187                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
1188             if (args.length < 1) {
1189                 printError(out, "Command is not given.");
1190                 resultReceiver.send(-1, null);
1191                 return;
1192             }
1193 
1194             String command = args[0];
1195             switch (command) {
1196                 case "repin":
1197                     repin();
1198                     break;
1199                 default:
1200                     printError(out, String.format(
1201                             "Unknown pinner command: %s. Supported commands: repin", command));
1202                     resultReceiver.send(-1, null);
1203                     return;
1204             }
1205 
1206             resultReceiver.send(0, null);
1207         }
1208     }
1209 
1210     private static final class PinnedFile implements AutoCloseable {
1211         private long mAddress;
1212         final int mapSize;
1213         final String fileName;
1214         final int bytesPinned;
1215 
PinnedFile(long address, int mapSize, String fileName, int bytesPinned)1216         PinnedFile(long address, int mapSize, String fileName, int bytesPinned) {
1217              mAddress = address;
1218              this.mapSize = mapSize;
1219              this.fileName = fileName;
1220              this.bytesPinned = bytesPinned;
1221         }
1222 
1223         @Override
close()1224         public void close() {
1225             if (mAddress >= 0) {
1226                 safeMunmap(mAddress, mapSize);
1227                 mAddress = -1;
1228             }
1229         }
1230 
1231         @Override
finalize()1232         public void finalize() {
1233             close();
1234         }
1235     }
1236 
1237     final static class PinRange {
1238         int start;
1239         int length;
1240     }
1241 
1242     /**
1243      * Represents an app that was pinned.
1244      */
1245     private final class PinnedApp {
1246 
1247         /**
1248          * The uid of the package being pinned. This stays constant while the package stays
1249          * installed.
1250          */
1251         final int uid;
1252 
1253         /** Whether it is currently active, i.e. there is a running process from that package. */
1254         boolean active;
1255 
1256         /** List of pinned files. */
1257         final ArrayList<PinnedFile> mFiles = new ArrayList<>();
1258 
PinnedApp(ApplicationInfo appInfo)1259         private PinnedApp(ApplicationInfo appInfo) {
1260             uid = appInfo.uid;
1261             active = mAmInternal.isUidActive(uid);
1262         }
1263     }
1264 
1265     final class PinnerHandler extends Handler {
1266         static final int PIN_ONSTART_MSG = 4001;
1267 
PinnerHandler(Looper looper)1268         public PinnerHandler(Looper looper) {
1269             super(looper, null, true);
1270         }
1271 
1272         @Override
handleMessage(Message msg)1273         public void handleMessage(Message msg) {
1274             switch (msg.what) {
1275                 case PIN_ONSTART_MSG:
1276                 {
1277                     handlePinOnStart();
1278                 }
1279                 break;
1280 
1281                 default:
1282                     super.handleMessage(msg);
1283             }
1284         }
1285     }
1286 
1287 }
1288