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