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