• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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.pinner;
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 static com.android.server.flags.Flags.pinGlobalQuota;
24 import static com.android.server.flags.Flags.pinWebview;
25 
26 import android.annotation.EnforcePermission;
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.app.ActivityManager;
31 import android.app.ActivityManagerInternal;
32 import android.app.IActivityManager;
33 import android.app.UidObserver;
34 import android.app.pinner.IPinnerService;
35 import android.app.pinner.PinnedFileStat;
36 import android.content.BroadcastReceiver;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.IntentFilter;
40 import android.content.pm.ActivityInfo;
41 import android.content.pm.ApplicationInfo;
42 import android.content.pm.PackageManager;
43 import android.content.pm.ResolveInfo;
44 import android.database.ContentObserver;
45 import android.net.Uri;
46 import android.os.Binder;
47 import android.os.Build;
48 import android.os.Handler;
49 import android.os.HandlerExecutor;
50 import android.os.Looper;
51 import android.os.Message;
52 import android.os.Process;
53 import android.os.RemoteException;
54 import android.os.ResultReceiver;
55 import android.os.ShellCallback;
56 import android.os.SystemProperties;
57 import android.os.UserHandle;
58 import android.os.UserManager;
59 import android.provider.DeviceConfig;
60 import android.provider.DeviceConfigInterface;
61 import android.provider.MediaStore;
62 import android.provider.Settings;
63 import android.system.ErrnoException;
64 import android.system.Os;
65 import android.system.OsConstants;
66 import android.util.ArrayMap;
67 import android.util.ArraySet;
68 import android.util.Slog;
69 
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.annotations.VisibleForTesting;
72 import com.android.internal.app.ResolverActivity;
73 import com.android.internal.os.BackgroundThread;
74 import com.android.internal.util.DumpUtils;
75 import com.android.internal.util.function.pooled.PooledLambda;
76 import com.android.server.LocalServices;
77 import com.android.server.SystemService;
78 import com.android.server.wm.ActivityTaskManagerInternal;
79 
80 import dalvik.system.DexFile;
81 import dalvik.system.VMRuntime;
82 
83 import java.io.FileDescriptor;
84 import java.io.FileOutputStream;
85 import java.io.IOException;
86 import java.io.InputStream;
87 import java.io.PrintWriter;
88 import java.lang.annotation.Retention;
89 import java.lang.annotation.RetentionPolicy;
90 import java.lang.reflect.Method;
91 import java.util.ArrayList;
92 import java.util.Collection;
93 import java.util.HashSet;
94 import java.util.List;
95 import java.util.zip.ZipEntry;
96 import java.util.zip.ZipFile;
97 
98 import sun.misc.Unsafe;
99 
100 /**
101  * <p>PinnerService pins important files for key processes in memory.</p>
102  * <p>Files to pin are specified in the config_defaultPinnerServiceFiles
103  * overlay.</p>
104  * <p>Pin the default camera application if specified in config_pinnerCameraApp.</p>
105  * <p>(Optional) Pin experimental carveout regions based on DeviceConfig flags.</p>
106  */
107 public final class PinnerService extends SystemService {
108     private static final boolean DEBUG = false;
109     private static final String TAG = "PinnerService";
110 
111     private static final String PIN_META_FILENAME = "pinlist.meta";
112     private static final int PAGE_SIZE = (int) Os.sysconf(OsConstants._SC_PAGESIZE);
113     private static final int MATCH_FLAGS = PackageManager.MATCH_DEFAULT_ONLY
114             | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
115 
116     private static final int KEY_CAMERA = 0;
117     private static final int KEY_HOME = 1;
118     private static final int KEY_ASSISTANT = 2;
119 
120     // Pin using pinlist.meta when pinning apps.
121     private static boolean PROP_PIN_PINLIST =
122             SystemProperties.getBoolean("pinner.use_pinlist", true);
123 
124     public static final String ANON_REGION_STAT_NAME = "[anon]";
125 
126     private static final String SYSTEM_GROUP_NAME = "system";
127 
128     @IntDef({KEY_CAMERA, KEY_HOME, KEY_ASSISTANT})
129     @Retention(RetentionPolicy.SOURCE)
130     public @interface AppKey {}
131 
132     private final Context mContext;
133     private final Injector mInjector;
134     private final DeviceConfigInterface mDeviceConfigInterface;
135     private final ActivityTaskManagerInternal mAtmInternal;
136     private final ActivityManagerInternal mAmInternal;
137     private final IActivityManager mAm;
138     private final UserManager mUserManager;
139 
140     /** The list of the statically pinned files. */
141     @GuardedBy("this")
142     private final ArrayMap<String, PinnedFile> mPinnedFiles = new ArrayMap<>();
143 
144     /** The list of the pinned apps. This is a map from {@link AppKey} to a pinned app. */
145     @GuardedBy("this")
146     private final ArrayMap<Integer, PinnedApp> mPinnedApps = new ArrayMap<>();
147 
148     /**
149      * The list of the pinned apps that need to be repinned as soon as the all processes of a given
150      * uid are no longer active. Note that with background dex opt, the new dex/vdex files are only
151      * loaded into the processes once it restarts. So in case background dex opt recompiled these
152      * files, we still need to keep the old ones pinned until the processes restart.
153      * <p>
154      * This is a map from uid to {@link AppKey}
155      */
156     @GuardedBy("this")
157     private final ArrayMap<Integer, Integer> mPendingRepin = new ArrayMap<>();
158 
159     /**
160      * A set of {@link AppKey} that are configured to be pinned.
161      */
162     @GuardedBy("this") private
163     ArraySet<Integer> mPinKeys;
164 
165     // Note that we don't use the `_BOOT` namespace for anonymous pinnings, as we want
166     // them to be responsive to dynamic flag changes for experimentation.
167     private static final String DEVICE_CONFIG_NAMESPACE_ANON_SIZE =
168             DeviceConfig.NAMESPACE_RUNTIME_NATIVE;
169     private static final String DEVICE_CONFIG_KEY_ANON_SIZE = "pin_shared_anon_size";
170     private static final long DEFAULT_ANON_SIZE =
171             SystemProperties.getLong("pinner.pin_shared_anon_size", 0);
172     private static final long MAX_ANON_SIZE = 2L * (1L << 30); // 2GB
173     private long mPinAnonSize;
174     private long mPinAnonAddress;
175     private long mCurrentlyPinnedAnonSize;
176 
177     // Resource-configured pinner flags;
178     private final boolean mConfiguredToPinCamera;
179     private final int mConfiguredCameraPinBytes;
180     private final int mConfiguredHomePinBytes;
181     private final boolean mConfiguredToPinAssistant;
182     private final int mConfiguredAssistantPinBytes;
183     private final int mConfiguredWebviewPinBytes;
184 
185     // This is the percentage of total device memory that will be used to set the global quota.
186     private final int mConfiguredMaxPinnedMemoryPercentage;
187 
188     // This is the global pinner quota that can be pinned.
189     private long mConfiguredMaxPinnedMemory;
190 
191     // This is the currently pinned memory.
192     private long mCurrentPinnedMemory = 0;
193 
194     private BinderService mBinderService;
195     private PinnerHandler mPinnerHandler = null;
196 
197     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
198         @Override
199         public void onReceive(Context context, Intent intent) {
200             // If an app has updated, update pinned files accordingly.
201             if (Intent.ACTION_PACKAGE_REPLACED.equals(intent.getAction())) {
202                 Uri packageUri = intent.getData();
203                 String packageName = packageUri.getSchemeSpecificPart();
204                 ArraySet<String> updatedPackages = new ArraySet<>();
205                 updatedPackages.add(packageName);
206                 update(updatedPackages, true /* force */);
207             }
208         }
209     };
210 
211     private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigAnonSizeListener =
212             new DeviceConfig.OnPropertiesChangedListener() {
213                 @Override
214                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
215                     if (DEVICE_CONFIG_NAMESPACE_ANON_SIZE.equals(properties.getNamespace())
216                             && properties.getKeyset().contains(DEVICE_CONFIG_KEY_ANON_SIZE)) {
217                         refreshPinAnonConfig();
218                     }
219                 }
220             };
221 
222     /** Utility class for testing. */
223     @VisibleForTesting
224     public static class Injector {
getDeviceConfigInterface()225         protected DeviceConfigInterface getDeviceConfigInterface() {
226             return DeviceConfigInterface.REAL;
227         }
228 
publishBinderService(PinnerService service, Binder binderService)229         protected void publishBinderService(PinnerService service, Binder binderService) {
230             service.publishBinderService("pinner", binderService);
231         }
232 
pinFileInternal(PinnerService service, String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection)233         protected PinnedFile pinFileInternal(PinnerService service, String fileToPin,
234                 long maxBytesToPin, boolean attemptPinIntrospection) {
235             return service.pinFileInternal(fileToPin, maxBytesToPin, attemptPinIntrospection);
236         }
237     }
238 
PinnerService(Context context)239     public PinnerService(Context context) {
240         this(context, new Injector());
241     }
242 
243     @VisibleForTesting
PinnerService(Context context, Injector injector)244     public PinnerService(Context context, Injector injector) {
245         super(context);
246 
247         mContext = context;
248         mInjector = injector;
249         mDeviceConfigInterface = mInjector.getDeviceConfigInterface();
250         mConfiguredToPinCamera = context.getResources().getBoolean(
251                 com.android.internal.R.bool.config_pinnerCameraApp);
252         mConfiguredCameraPinBytes = context.getResources().getInteger(
253                 com.android.internal.R.integer.config_pinnerCameraPinBytes);
254         mConfiguredAssistantPinBytes = context.getResources().getInteger(
255                 com.android.internal.R.integer.config_pinnerAssistantPinBytes);
256         mConfiguredHomePinBytes = context.getResources().getInteger(
257                 com.android.internal.R.integer.config_pinnerHomePinBytes);
258         mConfiguredToPinAssistant = context.getResources().getBoolean(
259                 com.android.internal.R.bool.config_pinnerAssistantApp);
260         mConfiguredWebviewPinBytes = context.getResources().getInteger(
261                 com.android.internal.R.integer.config_pinnerWebviewPinBytes);
262         mConfiguredMaxPinnedMemoryPercentage = context.getResources().getInteger(
263                 com.android.internal.R.integer.config_pinnerMaxPinnedMemoryPercentage);
264 
265         mPinKeys = createPinKeys();
266         mPinnerHandler = new PinnerHandler(BackgroundThread.get().getLooper());
267 
268         mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
269         mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
270         mAm = ActivityManager.getService();
271 
272         mUserManager = mContext.getSystemService(UserManager.class);
273 
274         IntentFilter filter = new IntentFilter();
275         filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
276         filter.addDataScheme("package");
277         mContext.registerReceiver(mBroadcastReceiver, filter);
278 
279         registerUidListener();
280         registerUserSetupCompleteListener();
281 
282         mDeviceConfigInterface.addOnPropertiesChangedListener(DEVICE_CONFIG_NAMESPACE_ANON_SIZE,
283                 new HandlerExecutor(mPinnerHandler), mDeviceConfigAnonSizeListener);
284     }
285 
286     @Override
onStart()287     public void onStart() {
288         if (DEBUG) {
289             Slog.i(TAG, "Starting PinnerService");
290         }
291         mConfiguredMaxPinnedMemory =
292                 (Process.getTotalMemory()
293                         * Math.clamp(mConfiguredMaxPinnedMemoryPercentage, 0, 100))
294                 / 100;
295         mBinderService = new BinderService();
296         mInjector.publishBinderService(this, mBinderService);
297         publishLocalService(PinnerService.class, this);
298 
299         mPinnerHandler.obtainMessage(PinnerHandler.PIN_ONSTART_MSG).sendToTarget();
300         sendPinAppsMessage(UserHandle.USER_SYSTEM);
301     }
302 
303     /**
304      * Repin apps on user switch.
305      * <p>
306      * If more than one user is using the device each user may set a different preference for the
307      * individual apps. Make sure that user's preference is pinned into memory.
308      */
309     @Override
onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)310     public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
311         int userId = to.getUserIdentifier();
312         if (!mUserManager.isManagedProfile(userId)) {
313             sendPinAppsMessage(userId);
314         }
315     }
316 
317     @Override
onUserUnlocking(@onNull TargetUser user)318     public void onUserUnlocking(@NonNull TargetUser user) {
319         final int userId = user.getUserIdentifier();
320         if (userId != UserHandle.USER_SYSTEM && !mUserManager.isManagedProfile(userId)) {
321             // App pinning for the system should have already been triggered from onStart().
322             sendPinAppsMessage(userId);
323         }
324     }
325 
326     /**
327      * Update the currently pinned files.
328      * Specifically, this only updates pinning for the apps that need to be pinned.
329      * The other files pinned in onStart will not need to be updated.
330      */
update(ArraySet<String> updatedPackages, boolean force)331     public void update(ArraySet<String> updatedPackages, boolean force) {
332         ArraySet<Integer> pinKeys = getPinKeys();
333         int currentUser = ActivityManager.getCurrentUser();
334         for (int i = pinKeys.size() - 1; i >= 0; i--) {
335             int key = pinKeys.valueAt(i);
336             ApplicationInfo info = getInfoForKey(key, currentUser);
337             if (info != null && updatedPackages.contains(info.packageName)) {
338                 Slog.i(TAG, "Updating pinned files for " + info.packageName + " force=" + force);
339                 sendPinAppMessage(key, currentUser, force);
340             }
341         }
342     }
343 
344     /** Returns information about pinned files and sizes for StatsPullAtomService. */
dumpDataForStatsd()345     public List<PinnedFileStats> dumpDataForStatsd() {
346         List<PinnedFileStats> pinnedFileStats = new ArrayList<>();
347         synchronized (PinnerService.this) {
348             for (PinnedFile pinnedFile : mPinnedFiles.values()) {
349                 pinnedFileStats.add(new PinnedFileStats(SYSTEM_UID, pinnedFile));
350             }
351 
352             for (int key : mPinnedApps.keySet()) {
353                 PinnedApp app = mPinnedApps.get(key);
354                 for (PinnedFile pinnedFile : mPinnedApps.get(key).mFiles) {
355                     pinnedFileStats.add(new PinnedFileStats(app.uid, pinnedFile));
356                 }
357             }
358         }
359         return pinnedFileStats;
360     }
361 
362     /** Wrapper class for statistics for a pinned file. */
363     public static class PinnedFileStats {
364         public final int uid;
365         public final String filename;
366         public final int sizeKb;
367 
PinnedFileStats(int uid, PinnedFile file)368         protected PinnedFileStats(int uid, PinnedFile file) {
369             this.uid = uid;
370             this.filename = file.fileName.substring(file.fileName.lastIndexOf('/') + 1);
371             this.sizeKb = (int) file.bytesPinned / 1024;
372         }
373     }
374 
375     /**
376      * Handler for on start pinning message
377      */
handlePinOnStart()378     private void handlePinOnStart() {
379         // Files to pin come from the overlay and can be specified per-device config
380         String[] filesToPin = mContext.getResources().getStringArray(
381                 com.android.internal.R.array.config_defaultPinnerServiceFiles);
382         // Continue trying to pin each file even if we fail to pin some of them
383         for (String fileToPin : filesToPin) {
384             pinFile(fileToPin, Integer.MAX_VALUE, /*appInfo=*/null, /*groupName=*/SYSTEM_GROUP_NAME,
385                     true);
386         }
387 
388         refreshPinAnonConfig();
389     }
390 
391     /**
392      * Registers a listener to repin the home app when user setup is complete, as the home intent
393      * initially resolves to setup wizard, but once setup is complete, it will resolve to the
394      * regular home app.
395      */
registerUserSetupCompleteListener()396     private void registerUserSetupCompleteListener() {
397         Uri userSetupCompleteUri = Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE);
398         mContext.getContentResolver().registerContentObserver(
399                 userSetupCompleteUri, false, new ContentObserver(null) {
400                     @Override
401                     public void onChange(boolean selfChange, Uri uri) {
402                         if (userSetupCompleteUri.equals(uri)) {
403                             if (mConfiguredHomePinBytes > 0) {
404                                 sendPinAppMessage(KEY_HOME, ActivityManager.getCurrentUser(),
405                                         true /* force */);
406                             }
407                         }
408                     }
409                 }, UserHandle.USER_ALL);
410     }
411 
registerUidListener()412     private void registerUidListener() {
413         try {
414             mAm.registerUidObserver(new UidObserver() {
415                 @Override
416                 public void onUidGone(int uid, boolean disabled) {
417                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
418                             PinnerService::handleUidGone, PinnerService.this, uid));
419                 }
420 
421                 @Override
422                 public void onUidActive(int uid) {
423                     mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
424                             PinnerService::handleUidActive, PinnerService.this, uid));
425                 }
426             }, UID_OBSERVER_GONE | UID_OBSERVER_ACTIVE, 0, null);
427         } catch (RemoteException e) {
428             Slog.e(TAG, "Failed to register uid observer", e);
429         }
430     }
431 
handleUidGone(int uid)432     private void handleUidGone(int uid) {
433         updateActiveState(uid, false /* active */);
434         int key;
435         synchronized (this) {
436             // In case we have a pending repin, repin now. See mPendingRepin for more information.
437             key = mPendingRepin.getOrDefault(uid, -1);
438             if (key == -1) {
439                 return;
440             }
441             mPendingRepin.remove(uid);
442         }
443         pinApp(key, ActivityManager.getCurrentUser(), false /* force */);
444     }
445 
handleUidActive(int uid)446     private void handleUidActive(int uid) {
447         updateActiveState(uid, true /* active */);
448     }
449 
updateActiveState(int uid, boolean active)450     private void updateActiveState(int uid, boolean active) {
451         synchronized (this) {
452             for (int i = mPinnedApps.size() - 1; i >= 0; i--) {
453                 PinnedApp app = mPinnedApps.valueAt(i);
454                 if (app.uid == uid) {
455                     app.active = active;
456                 }
457             }
458         }
459     }
460 
unpinApps()461     private void unpinApps() {
462         ArraySet<Integer> pinKeys = getPinKeys();
463         for (int i = pinKeys.size() - 1; i >= 0; i--) {
464             int key = pinKeys.valueAt(i);
465             unpinApp(key);
466         }
467     }
468 
unpinApp(@ppKey int key)469     private void unpinApp(@AppKey int key) {
470         ArrayList<PinnedFile> pinnedAppFiles;
471         synchronized (this) {
472             PinnedApp app = mPinnedApps.get(key);
473             if (app == null) {
474                 return;
475             }
476             mPinnedApps.remove(key);
477             pinnedAppFiles = new ArrayList<>(app.mFiles);
478         }
479         for (PinnedFile pinnedFile : pinnedAppFiles) {
480             unpinFile(pinnedFile.fileName);
481         }
482     }
483 
isResolverActivity(ActivityInfo info)484     private boolean isResolverActivity(ActivityInfo info) {
485         return ResolverActivity.class.getName().equals(info.name);
486     }
487 
getWebviewPinQuota()488     public int getWebviewPinQuota() {
489         if (!pinWebview()) {
490             return 0;
491         }
492         int quota = mConfiguredWebviewPinBytes;
493         int overrideQuota = SystemProperties.getInt("pinner.pin_webview_size", -1);
494         if (overrideQuota != -1) {
495             // Quota was overridden
496             quota = overrideQuota;
497         }
498         return quota;
499     }
500 
getCameraInfo(int userHandle)501     private ApplicationInfo getCameraInfo(int userHandle) {
502         Intent cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
503         ApplicationInfo info = getApplicationInfoForIntent(
504                 cameraIntent, userHandle, false /* defaultToSystemApp */);
505 
506         // If the STILL_IMAGE_CAMERA intent doesn't resolve, try the _SECURE intent.
507         // We don't use _SECURE first because it will never get set on a device
508         // without File-based Encryption. But if the user has only set the intent
509         // before unlocking their device, we may still be able to identify their
510         // preference using this intent.
511         if (info == null) {
512             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE);
513             info = getApplicationInfoForIntent(
514                     cameraIntent, userHandle, false /* defaultToSystemApp */);
515         }
516 
517         // If the _SECURE intent doesn't resolve, try the original intent but request
518         // the system app for camera if there was more than one result.
519         if (info == null) {
520             cameraIntent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
521             info = getApplicationInfoForIntent(
522                     cameraIntent, userHandle, true /* defaultToSystemApp */);
523         }
524         return info;
525     }
526 
getHomeInfo(int userHandle)527     private ApplicationInfo getHomeInfo(int userHandle) {
528         Intent intent = mAtmInternal.getHomeIntent();
529         return getApplicationInfoForIntent(intent, userHandle, false);
530     }
531 
getAssistantInfo(int userHandle)532     private ApplicationInfo getAssistantInfo(int userHandle) {
533         Intent intent = new Intent(Intent.ACTION_ASSIST);
534         return getApplicationInfoForIntent(intent, userHandle, true);
535     }
536 
getApplicationInfoForIntent( Intent intent, int userHandle, boolean defaultToSystemApp)537     private ApplicationInfo getApplicationInfoForIntent(
538             Intent intent, int userHandle, boolean defaultToSystemApp) {
539         if (intent == null) {
540             return null;
541         }
542 
543         ResolveInfo resolveInfo =
544                 mContext.getPackageManager().resolveActivityAsUser(intent, MATCH_FLAGS, userHandle);
545 
546         // If this intent can resolve to only one app, choose that one.
547         // Otherwise, if we've requested to default to the system app, return it;
548         // if we have not requested that default, return null if there's more than one option.
549         // If there's more than one system app, return null since we don't know which to pick.
550         if (resolveInfo == null) {
551             return null;
552         }
553 
554         if (!isResolverActivity(resolveInfo.activityInfo)) {
555             return resolveInfo.activityInfo.applicationInfo;
556         }
557 
558         if (defaultToSystemApp) {
559             List<ResolveInfo> infoList = mContext.getPackageManager().queryIntentActivitiesAsUser(
560                     intent, MATCH_FLAGS, userHandle);
561             ApplicationInfo systemAppInfo = null;
562             for (ResolveInfo info : infoList) {
563                 if ((info.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
564                     if (systemAppInfo == null) {
565                         systemAppInfo = info.activityInfo.applicationInfo;
566                     } else {
567                         // If there's more than one system app, return null due to ambiguity.
568                         return null;
569                     }
570                 }
571             }
572             return systemAppInfo;
573         }
574 
575         return null;
576     }
577 
sendPinAppsMessage(int userHandle)578     private void sendPinAppsMessage(int userHandle) {
579         mPinnerHandler.sendMessage(
580                 PooledLambda.obtainMessage(PinnerService::pinApps, this, userHandle));
581     }
582 
sendPinAppsWithUpdatedKeysMessage(int userHandle)583     private void sendPinAppsWithUpdatedKeysMessage(int userHandle) {
584         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(
585                 PinnerService::pinAppsWithUpdatedKeys, this, userHandle));
586     }
sendUnpinAppsMessage()587     private void sendUnpinAppsMessage() {
588         mPinnerHandler.sendMessage(PooledLambda.obtainMessage(PinnerService::unpinApps, this));
589     }
590 
createPinKeys()591     private ArraySet<Integer> createPinKeys() {
592         ArraySet<Integer> pinKeys = new ArraySet<>();
593         // Pin the camera application. Default to the system property only if the experiment
594         // phenotype property is not set.
595         boolean shouldPinCamera = mConfiguredToPinCamera
596                 && mDeviceConfigInterface.getBoolean(DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT,
597                         "pin_camera", SystemProperties.getBoolean("pinner.pin_camera", true));
598         if (shouldPinCamera) {
599             pinKeys.add(KEY_CAMERA);
600         } else if (DEBUG) {
601             Slog.i(TAG, "Pinner - skip pinning camera app");
602         }
603 
604         if (mConfiguredHomePinBytes > 0) {
605             pinKeys.add(KEY_HOME);
606         }
607         if (mConfiguredToPinAssistant) {
608             pinKeys.add(KEY_ASSISTANT);
609         }
610 
611         return pinKeys;
612     }
613 
getPinKeys()614     private synchronized ArraySet<Integer> getPinKeys() {
615         return mPinKeys;
616     }
617 
pinApps(int userHandle)618     private void pinApps(int userHandle) {
619         pinAppsInternal(userHandle, false);
620     }
621 
pinAppsWithUpdatedKeys(int userHandle)622     private void pinAppsWithUpdatedKeys(int userHandle) {
623         pinAppsInternal(userHandle, true);
624     }
625 
626     /**
627      * @param updateKeys True if the pinned app list has to be updated. This is true only when
628      *                   "pinner repin" shell command is requested.
629      */
pinAppsInternal(int userHandle, boolean updateKeys)630     private void pinAppsInternal(int userHandle, boolean updateKeys) {
631         if (updateKeys) {
632             ArraySet<Integer> newKeys = createPinKeys();
633             synchronized (this) {
634                 // This code path demands preceding unpinApps() call.
635                 if (!mPinnedApps.isEmpty()) {
636                     Slog.e(TAG,
637                             "Attempted to update a list of apps, "
638                                     + "but apps were already pinned. Skipping.");
639                     return;
640                 }
641 
642                 mPinKeys = newKeys;
643             }
644         }
645 
646         ArraySet<Integer> currentPinKeys = getPinKeys();
647         for (int i = currentPinKeys.size() - 1; i >= 0; i--) {
648             int key = currentPinKeys.valueAt(i);
649             pinApp(key, userHandle, true /* force */);
650         }
651     }
652 
653     /**
654      * @see #pinApp(int, int, boolean)
655      */
sendPinAppMessage(int key, int userHandle, boolean force)656     private void sendPinAppMessage(int key, int userHandle, boolean force) {
657         mPinnerHandler.sendMessage(
658                 PooledLambda.obtainMessage(PinnerService::pinApp, this, key, userHandle, force));
659     }
660 
661     /**
662      * Pins an app of a specific type {@code key}.
663      *
664      * @param force If false, this will not repin the app if it's currently active. See
665      *              {@link #mPendingRepin}.
666      */
pinApp(int key, int userHandle, boolean force)667     private void pinApp(int key, int userHandle, boolean force) {
668         int uid = getUidForKey(key);
669 
670         // In case the app is currently active, don't repin until next process restart. See
671         // mPendingRepin for more information.
672         if (!force && uid != -1) {
673             synchronized (this) {
674                 mPendingRepin.put(uid, key);
675             }
676             return;
677         }
678         ApplicationInfo info = getInfoForKey(key, userHandle);
679         unpinApp(key);
680         if (info != null) {
681             pinAppInternal(key, info);
682         }
683     }
684 
685     /**
686      * Checks whether the pinned package with {@code key} is active or not.
687 
688      * @return The uid of the pinned app, or {@code -1} otherwise.
689      */
getUidForKey(@ppKey int key)690     private int getUidForKey(@AppKey int key) {
691         synchronized (this) {
692             PinnedApp existing = mPinnedApps.get(key);
693             return existing != null && existing.active ? existing.uid : -1;
694         }
695     }
696 
697     /**
698      * Retrieves the current application info for the given app type.
699      *
700      * @param key The app type to retrieve the info for.
701      * @param userHandle The user id of the current user.
702      */
getInfoForKey(@ppKey int key, int userHandle)703     private @Nullable ApplicationInfo getInfoForKey(@AppKey int key, int userHandle) {
704         switch (key) {
705             case KEY_CAMERA:
706                 return getCameraInfo(userHandle);
707             case KEY_HOME:
708                 return getHomeInfo(userHandle);
709             case KEY_ASSISTANT:
710                 return getAssistantInfo(userHandle);
711             default:
712                 return null;
713         }
714     }
715 
716     /**
717      * @return The app type name for {@code key}.
718      */
getNameForKey(@ppKey int key)719     private String getNameForKey(@AppKey int key) {
720         switch (key) {
721             case KEY_CAMERA:
722                 return "Camera";
723             case KEY_HOME:
724                 return "Home";
725             case KEY_ASSISTANT:
726                 return "Assistant";
727             default:
728                 return "";
729         }
730     }
731 
732     /**
733      * Handle any changes in the anon region pinner config.
734      */
refreshPinAnonConfig()735     private void refreshPinAnonConfig() {
736         long newPinAnonSize = mDeviceConfigInterface.getLong(
737                 DEVICE_CONFIG_NAMESPACE_ANON_SIZE, DEVICE_CONFIG_KEY_ANON_SIZE, DEFAULT_ANON_SIZE);
738         newPinAnonSize = Math.max(0, Math.min(newPinAnonSize, MAX_ANON_SIZE));
739         if (newPinAnonSize != mPinAnonSize) {
740             mPinAnonSize = newPinAnonSize;
741             pinAnonRegion();
742         }
743     }
744 
745     /**
746      * Pin an empty anonymous region. This should only be used for ablation experiments.
747      */
pinAnonRegion()748     private void pinAnonRegion() {
749         if (mPinAnonSize == 0) {
750             Slog.d(TAG, "pinAnonRegion: releasing pinned region");
751             unpinAnonRegion();
752             return;
753         }
754         long alignedPinSize = mPinAnonSize;
755         if (alignedPinSize % PAGE_SIZE != 0) {
756             alignedPinSize -= alignedPinSize % PAGE_SIZE;
757             Slog.e(TAG, "pinAnonRegion: aligning size to " + alignedPinSize);
758         }
759         if (mPinAnonAddress != 0) {
760             if (mCurrentlyPinnedAnonSize == alignedPinSize) {
761                 Slog.d(TAG, "pinAnonRegion: already pinned region of size " + alignedPinSize);
762                 return;
763             }
764             Slog.d(TAG, "pinAnonRegion: resetting pinned region for new size " + alignedPinSize);
765             unpinAnonRegion();
766         }
767         long address = 0;
768         try {
769             // Map as SHARED to avoid changing rss.anon for system_server (per /proc/*/status).
770             // The mapping is visible in other rss metrics, and as private dirty in smaps/meminfo.
771             address = Os.mmap(0, alignedPinSize, OsConstants.PROT_READ | OsConstants.PROT_WRITE,
772                     OsConstants.MAP_SHARED | OsConstants.MAP_ANONYMOUS, new FileDescriptor(),
773                     /*offset=*/0);
774 
775             Unsafe tempUnsafe = null;
776             Class<sun.misc.Unsafe> clazz = sun.misc.Unsafe.class;
777             for (java.lang.reflect.Field f : clazz.getDeclaredFields()) {
778                 f.setAccessible(true);
779                 Object obj = f.get(null);
780                 if (clazz.isInstance(obj)) {
781                     tempUnsafe = clazz.cast(obj);
782                 }
783             }
784             if (tempUnsafe == null) {
785                 throw new Exception("Couldn't get Unsafe");
786             }
787             Method setMemory = clazz.getMethod("setMemory", long.class, long.class, byte.class);
788             setMemory.invoke(tempUnsafe, address, alignedPinSize, (byte) 1);
789             Os.mlock(address, alignedPinSize);
790             mCurrentlyPinnedAnonSize = alignedPinSize;
791             mPinAnonAddress = address;
792             address = -1;
793             Slog.w(TAG, "pinAnonRegion success, size=" + mCurrentlyPinnedAnonSize);
794         } catch (Exception ex) {
795             Slog.e(TAG, "Could not pin anon region of size " + alignedPinSize, ex);
796             return;
797         } finally {
798             if (address >= 0) {
799                 PinnerUtils.safeMunmap(address, alignedPinSize);
800             }
801         }
802     }
803 
unpinAnonRegion()804     private void unpinAnonRegion() {
805         if (mPinAnonAddress != 0) {
806             PinnerUtils.safeMunmap(mPinAnonAddress, mCurrentlyPinnedAnonSize);
807         }
808         mPinAnonAddress = 0;
809         mCurrentlyPinnedAnonSize = 0;
810     }
811 
812     /**
813      * @return The maximum amount of bytes to be pinned for an app of type {@code key}.
814      */
getSizeLimitForKey(@ppKey int key)815     private int getSizeLimitForKey(@AppKey int key) {
816         switch (key) {
817             case KEY_CAMERA:
818                 return mConfiguredCameraPinBytes;
819             case KEY_HOME:
820                 return mConfiguredHomePinBytes;
821             case KEY_ASSISTANT:
822                 return mConfiguredAssistantPinBytes;
823             default:
824                 return 0;
825         }
826     }
827 
828     /**
829      * Retrieves remaining quota for pinner service, once it reaches 0 it will no longer
830      * pin any file.
831      */
getAvailableGlobalQuota()832     private long getAvailableGlobalQuota() {
833         return mConfiguredMaxPinnedMemory - mCurrentPinnedMemory;
834     }
835 
836     /**
837      * Pins an application.
838      *
839      * @param key The key of the app to pin.
840      * @param appInfo The corresponding app info.
841      */
pinAppInternal(@ppKey int key, @Nullable ApplicationInfo appInfo)842     private void pinAppInternal(@AppKey int key, @Nullable ApplicationInfo appInfo) {
843         if (appInfo == null) {
844             return;
845         }
846 
847         PinnedApp pinnedApp = new PinnedApp(appInfo);
848         synchronized (this) {
849             mPinnedApps.put(key, pinnedApp);
850         }
851 
852         // pin APK
853         final int pinSizeLimit = getSizeLimitForKey(key);
854         List<String> apks = new ArrayList<>();
855         apks.add(appInfo.sourceDir);
856 
857         if (appInfo.splitSourceDirs != null) {
858             for (String splitApk : appInfo.splitSourceDirs) {
859                 apks.add(splitApk);
860             }
861         }
862 
863         long apkPinSizeLimit = pinSizeLimit;
864 
865         for (String apk : apks) {
866             if (apkPinSizeLimit <= 0) {
867                 Slog.w(TAG, "Reached to the pin size limit. Skipping: " + apk);
868                 // Continue instead of break to print all skipped APK names.
869                 continue;
870             }
871 
872             String pinGroup = getNameForKey(key);
873             boolean shouldPinDeps = apk.equals(appInfo.sourceDir);
874             PinnedFile pf = pinFile(apk, apkPinSizeLimit, appInfo, pinGroup, shouldPinDeps);
875             if (pf == null) {
876                 Slog.e(TAG, "Failed to pin " + apk);
877                 continue;
878             }
879 
880             if (DEBUG) {
881                 Slog.i(TAG, "Pinned " + pf.fileName);
882             }
883             synchronized (this) {
884                 pinnedApp.mFiles.add(pf);
885             }
886 
887             apkPinSizeLimit -= pf.bytesPinned;
888         }
889     }
890 
891     /**
892      * Pin file or apk to memory.
893      *
894      * Prefer to use this method instead of {@link #pinFileInternal(String, int, boolean)} as it
895      * takes care of accounting and if pinning an apk, it also pins any extra optimized art files
896      * that related to the file but not within itself.
897      *
898      * @param fileToPin File to pin
899      * @param bytesRequestedToPin maximum bytes requested to pin for {@code fileToPin}.
900      * @param pinOptimizedDeps whether optimized dependencies such as odex,vdex, etc be pinned.
901      *                         Note: {@code bytesRequestedToPin} limit will not apply to optimized
902      *                         dependencies pinned, only global quotas will apply instead.
903      * @return pinned file
904      */
pinFile(String fileToPin, long bytesRequestedToPin, @Nullable ApplicationInfo appInfo, @Nullable String groupName, boolean pinOptimizedDeps)905     public PinnedFile pinFile(String fileToPin, long bytesRequestedToPin,
906             @Nullable ApplicationInfo appInfo, @Nullable String groupName,
907             boolean pinOptimizedDeps) {
908         PinnedFile existingPin;
909         synchronized (this) {
910             existingPin = mPinnedFiles.get(fileToPin);
911         }
912         if (existingPin != null) {
913             if (existingPin.bytesPinned == bytesRequestedToPin) {
914                 // Duplicate pin requesting same amount of bytes, lets just bail out.
915                 return null;
916             } else {
917                 // User decided to pin a different amount of bytes than currently pinned
918                 // so this is a valid pin request. Unpin the previous version before repining.
919                 if (DEBUG) {
920                     Slog.d(TAG, "Unpinning file prior to repin: " + fileToPin);
921                 }
922                 unpinFile(fileToPin);
923             }
924         }
925 
926         long remainingQuota = getAvailableGlobalQuota();
927 
928         if (pinGlobalQuota()) {
929             if (remainingQuota <= 0) {
930                 Slog.w(TAG, "Reached pin quota, skipping file: " + fileToPin);
931                 return null;
932             }
933             bytesRequestedToPin = Math.min(bytesRequestedToPin, remainingQuota);
934         }
935 
936         boolean isApk = fileToPin.endsWith(".apk");
937 
938         PinnedFile pf = mInjector.pinFileInternal(this, fileToPin, bytesRequestedToPin,
939                 /*attemptPinIntrospection=*/isApk);
940         if (pf == null) {
941             Slog.e(TAG, "Failed to pin file = " + fileToPin);
942             return null;
943         }
944         pf.groupName = groupName != null ? groupName : "";
945 
946         mCurrentPinnedMemory += pf.bytesPinned;
947 
948         synchronized (this) {
949             mPinnedFiles.put(pf.fileName, pf);
950         }
951 
952         if (pinOptimizedDeps) {
953             mCurrentPinnedMemory +=
954                     pinOptimizedDexDependencies(pf, getAvailableGlobalQuota(), appInfo);
955         }
956 
957         return pf;
958     }
959 
960     /**
961      * Pin any dependency optimized files generated by ART.
962      * @param pinnedFile An already pinned file whose dependencies we want pinned.
963      * @param maxBytesToPin Maximum amount of bytes to pin.
964      * @param appInfo Used to determine the ABI in case the application has one custom set, when set
965      *                to null it will use the default supported ABI by the device.
966      * @return total bytes pinned.
967      */
pinOptimizedDexDependencies( PinnedFile pinnedFile, long maxBytesToPin, @Nullable ApplicationInfo appInfo)968     private long pinOptimizedDexDependencies(
969             PinnedFile pinnedFile, long maxBytesToPin, @Nullable ApplicationInfo appInfo) {
970         if (pinnedFile == null) {
971             return 0;
972         }
973 
974         long bytesPinned = 0;
975         if (pinnedFile.fileName.endsWith(".jar") | pinnedFile.fileName.endsWith(".apk")) {
976             String abi = null;
977             if (appInfo != null) {
978                 abi = appInfo.primaryCpuAbi;
979             }
980             if (abi == null) {
981                 abi = Build.SUPPORTED_ABIS[0];
982             }
983             // Check whether the runtime has compilation artifacts to pin.
984             String arch = VMRuntime.getInstructionSet(abi);
985             String[] files = null;
986             try {
987                 files = DexFile.getDexFileOutputPaths(pinnedFile.fileName, arch);
988             } catch (IOException ioe) {
989             }
990             if (files == null) {
991                 return bytesPinned;
992             }
993             for (String file : files) {
994                 // Unpin if it was already pinned prior to re-pinning.
995                 unpinFile(file);
996 
997                 PinnedFile df = mInjector.pinFileInternal(this, file, maxBytesToPin,
998                         /*attemptPinIntrospection=*/false);
999                 if (df == null) {
1000                     Slog.i(TAG, "Failed to pin ART file = " + file);
1001                     return bytesPinned;
1002                 }
1003                 df.groupName = pinnedFile.groupName;
1004                 pinnedFile.pinnedDeps.add(df);
1005                 maxBytesToPin -= df.bytesPinned;
1006                 bytesPinned += df.bytesPinned;
1007                 synchronized (this) {
1008                     mPinnedFiles.put(df.fileName, df);
1009                 }
1010             }
1011         }
1012         return bytesPinned;
1013     }
1014 
1015     /**
1016      * mlock length bytes of fileToPin in memory
1017      *
1018      * If attemptPinIntrospection is true, then treat the file to pin as a zip file and
1019      * look for a "pinlist.meta" file in the archive root directory. The structure of this
1020      * file is a PINLIST_META as described below:
1021      *
1022      * <pre>
1023      *   PINLIST_META: PIN_RANGE*
1024      *   PIN_RANGE: PIN_START PIN_LENGTH
1025      *   PIN_START: big endian i32: offset in bytes of pin region from file start
1026      *   PIN_LENGTH: big endian i32: length of pin region in bytes
1027      * </pre>
1028      *
1029      * (We use big endian because that's what DataInputStream is hardcoded to use.)
1030      *
1031      * If attemptPinIntrospection is false, then we use a single implicit PIN_RANGE of (0,
1032      * maxBytesToPin); that is, we attempt to pin the first maxBytesToPin bytes of the file.
1033      *
1034      * After we open a file, we march through the list of pin ranges and attempt to pin
1035      * each one, stopping after we've pinned maxBytesToPin bytes. (We may truncate the last
1036      * pinned range to fit.)  In this way, by choosing to emit certain PIN_RANGE pairs
1037      * before others, file generators can express pins in priority order, making most
1038      * effective use of the pinned-page quota.
1039      *
1040      * N.B. Each PIN_RANGE is clamped to the actual bounds of the file; all inputs have a
1041      * meaningful interpretation. Also, a range locking a single byte of a page locks the
1042      * whole page. Any truncated PIN_RANGE at EOF is ignored. Overlapping pinned entries
1043      * are legal, but each pin of a byte counts toward the pin quota regardless of whether
1044      * that byte has already been pinned, so the generator of PINLIST_META ought to ensure
1045      * that ranges are non-overlapping.
1046      *
1047      * @param fileToPin Path to file to pin
1048      * @param maxBytesToPin Maximum number of bytes to pin
1049      * @param attemptPinIntrospection If true, try to open file as a
1050      *   zip in order to extract the
1051      * @return Pinned memory resource owner thing or null on error
1052      */
pinFileInternal( String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection)1053     private PinnedFile pinFileInternal(
1054             String fileToPin, long maxBytesToPin, boolean attemptPinIntrospection) {
1055         if (DEBUG) {
1056             Slog.d(TAG, "pin file: " + fileToPin + " use-pinlist: " + attemptPinIntrospection);
1057         }
1058         ZipFile fileAsZip = null;
1059         InputStream pinRangeStream = null;
1060         try {
1061             if (attemptPinIntrospection) {
1062                 fileAsZip = maybeOpenZip(fileToPin);
1063             }
1064 
1065             if (fileAsZip != null) {
1066                 pinRangeStream = maybeOpenPinMetaInZip(fileAsZip, fileToPin);
1067             }
1068             boolean use_pinlist = (pinRangeStream != null);
1069             PinRangeSource pinRangeSource = use_pinlist
1070                     ? new PinRangeSourceStream(pinRangeStream)
1071                     : new PinRangeSourceStatic(0, Integer.MAX_VALUE /* will be clipped */);
1072             PinnedFile pinnedFile = pinFileRanges(fileToPin, maxBytesToPin, pinRangeSource);
1073             if (pinnedFile != null) {
1074                 pinnedFile.used_pinlist = use_pinlist;
1075             }
1076             return pinnedFile;
1077         } finally {
1078             PinnerUtils.safeClose(pinRangeStream);
1079             PinnerUtils.safeClose(fileAsZip); // Also closes any streams we've opened
1080         }
1081     }
1082 
1083     /**
1084      * Attempt to open a file as a zip file. On any sort of corruption, log, swallow the
1085      * error, and return null.
1086      */
maybeOpenZip(String fileName)1087     private static ZipFile maybeOpenZip(String fileName) {
1088         ZipFile zip = null;
1089         try {
1090             zip = new ZipFile(fileName);
1091         } catch (IOException ex) {
1092             Slog.w(TAG, String.format("could not open \"%s\" as zip: pinning as blob", fileName),
1093                     ex);
1094         }
1095         return zip;
1096     }
1097 
1098     /**
1099      * Open a pin metadata file in the zip if one is present.
1100      *
1101      * @param zipFile Zip file to search
1102      * @return Open input stream or null on any error
1103      */
maybeOpenPinMetaInZip(ZipFile zipFile, String fileName)1104     private static InputStream maybeOpenPinMetaInZip(ZipFile zipFile, String fileName) {
1105         if (!PROP_PIN_PINLIST) {
1106             if (DEBUG) {
1107                 Slog.i(TAG, "Pin - skip pinlist.meta in " + fileName);
1108             }
1109             return null;
1110         }
1111 
1112         // Looking at root directory is the old behavior but still some apps rely on it so keeping
1113         // for backward compatibility. As doing a single item lookup is cheap in the root.
1114         ZipEntry pinMetaEntry = zipFile.getEntry(PIN_META_FILENAME);
1115 
1116         if (pinMetaEntry == null) {
1117             // It is usually within an apk's control to include files in assets/ directory
1118             // so this would be the expected point to have the pinlist.meta coming from.
1119             // we explicitly avoid doing an exhaustive search because it may be expensive so
1120             // prefer to have a good known location to retrieve the file.
1121             pinMetaEntry = zipFile.getEntry("assets/" + PIN_META_FILENAME);
1122         }
1123 
1124         InputStream pinMetaStream = null;
1125         if (pinMetaEntry != null) {
1126             if (DEBUG) {
1127                 Slog.d(TAG, "Found pinlist.meta for " + fileName);
1128             }
1129             try {
1130                 pinMetaStream = zipFile.getInputStream(pinMetaEntry);
1131             } catch (IOException ex) {
1132                 Slog.w(TAG,
1133                         String.format(
1134                                 "error reading pin metadata \"%s\": pinning as blob", fileName),
1135                         ex);
1136             }
1137         } else {
1138             Slog.w(TAG,
1139                     String.format(
1140                             "Could not find pinlist.meta for \"%s\": pinning as blob", fileName));
1141         }
1142         return pinMetaStream;
1143     }
1144 
1145     /**
1146      * Helper for pinFile.
1147      *
1148      * @param fileToPin Name of file to pin
1149      * @param maxBytesToPin Maximum number of bytes to pin
1150      * @param pinRangeSource Read PIN_RANGE entries from this stream to tell us what bytes
1151      *   to pin.
1152      * @return PinnedFile or null on error
1153      */
pinFileRanges( String fileToPin, long maxBytesToPin, PinRangeSource pinRangeSource)1154     private static PinnedFile pinFileRanges(
1155             String fileToPin, long maxBytesToPin, PinRangeSource pinRangeSource) {
1156         FileDescriptor fd = new FileDescriptor();
1157         long address = -1;
1158         long mapSize = 0;
1159 
1160         try {
1161             int openFlags = (OsConstants.O_RDONLY | OsConstants.O_CLOEXEC);
1162             fd = Os.open(fileToPin, openFlags, 0);
1163             mapSize = (int) Math.min(Os.fstat(fd).st_size, Integer.MAX_VALUE);
1164             address = Os.mmap(
1165                     0, mapSize, OsConstants.PROT_READ, OsConstants.MAP_SHARED, fd, /*offset=*/0);
1166 
1167             PinRange pinRange = new PinRange();
1168             long bytesPinned = 0;
1169 
1170             // We pin at page granularity, so make sure the limit is page-aligned
1171             if (maxBytesToPin % PAGE_SIZE != 0) {
1172                 maxBytesToPin -= maxBytesToPin % PAGE_SIZE;
1173             }
1174 
1175             while (bytesPinned < maxBytesToPin && pinRangeSource.read(pinRange)) {
1176                 long pinStart = pinRange.start;
1177                 long pinLength = pinRange.length;
1178                 pinStart = PinnerUtils.clamp(0, pinStart, mapSize);
1179                 pinLength = PinnerUtils.clamp(0, pinLength, mapSize - pinStart);
1180                 pinLength = Math.min(maxBytesToPin - bytesPinned, pinLength);
1181 
1182                 // mlock doesn't require the region to be page-aligned, but we snap the
1183                 // lock region to page boundaries anyway so that we don't under-count
1184                 // locking a single byte of a page as a charge of one byte even though the
1185                 // OS will retain the whole page. Thanks to this adjustment, we slightly
1186                 // over-count the pin charge of back-to-back pins touching the same page,
1187                 // but better that than undercounting. Besides: nothing stops pin metafile
1188                 // creators from making the actual regions page-aligned.
1189                 pinLength += pinStart % PAGE_SIZE;
1190                 pinStart -= pinStart % PAGE_SIZE;
1191                 if (pinLength % PAGE_SIZE != 0) {
1192                     pinLength += PAGE_SIZE - pinLength % PAGE_SIZE;
1193                 }
1194                 pinLength = PinnerUtils.clamp(0, pinLength, maxBytesToPin - bytesPinned);
1195 
1196                 if (pinLength > 0) {
1197                     if (DEBUG) {
1198                         Slog.d(TAG,
1199                                 String.format("pinning at %s %s bytes of %s", pinStart, pinLength,
1200                                         fileToPin));
1201                     }
1202                     Os.mlock(address + pinStart, pinLength);
1203                 }
1204                 bytesPinned += pinLength;
1205             }
1206 
1207             PinnedFile pinnedFile = new PinnedFile(address, mapSize, fileToPin, bytesPinned);
1208             address = -1; // Ownership transferred
1209             return pinnedFile;
1210         } catch (ErrnoException ex) {
1211             Slog.e(TAG, "Could not pin file " + fileToPin, ex);
1212             return null;
1213         } finally {
1214             PinnerUtils.safeClose(fd);
1215             if (address >= 0) {
1216                 PinnerUtils.safeMunmap(address, mapSize);
1217             }
1218         }
1219     }
getAllPinsForGroup(String group)1220     private List<PinnedFile> getAllPinsForGroup(String group) {
1221         List<PinnedFile> filesInGroup;
1222         synchronized (this) {
1223             filesInGroup = mPinnedFiles.values()
1224                                    .stream()
1225                                    .filter(pf -> pf.groupName.equals(group))
1226                                    .toList();
1227         }
1228         return filesInGroup;
1229     }
unpinGroup(String group)1230     public void unpinGroup(String group) {
1231         List<PinnedFile> pinnedFiles = getAllPinsForGroup(group);
1232         for (PinnedFile pf : pinnedFiles) {
1233             unpinFile(pf.fileName);
1234         }
1235     }
1236 
1237     /**
1238      * Unpin a file and its optimized dependencies.
1239      *
1240      * @param filename file to unpin.
1241      * @return number of bytes unpinned, 0 in case of failure or nothing to unpin.
1242      */
unpinFile(String filename)1243     public long unpinFile(String filename) {
1244         PinnedFile pinnedFile;
1245         synchronized (this) {
1246             pinnedFile = mPinnedFiles.get(filename);
1247         }
1248         if (pinnedFile == null) {
1249             // File not pinned, nothing to do.
1250             return 0;
1251         }
1252         long unpinnedBytes = pinnedFile.bytesPinned;
1253         pinnedFile.close();
1254         synchronized (this) {
1255             if (DEBUG) {
1256                 Slog.d(TAG, "Unpinned file: " + filename);
1257             }
1258             mCurrentPinnedMemory -= pinnedFile.bytesPinned;
1259 
1260             mPinnedFiles.remove(pinnedFile.fileName);
1261             for (PinnedFile dep : pinnedFile.pinnedDeps) {
1262                 if (dep == null) {
1263                     continue;
1264                 }
1265                 unpinnedBytes -= dep.bytesPinned;
1266                 mCurrentPinnedMemory -= dep.bytesPinned;
1267                 mPinnedFiles.remove(dep.fileName);
1268                 if (DEBUG) {
1269                     Slog.d(TAG, "Unpinned dependency: " + dep.fileName);
1270                 }
1271             }
1272         }
1273 
1274         return unpinnedBytes;
1275     }
1276 
getPinnerStats()1277     public List<PinnedFileStat> getPinnerStats() {
1278         ArrayList<PinnedFileStat> stats = new ArrayList<>();
1279         Collection<PinnedFile> pinnedFiles;
1280         synchronized (this) {
1281             pinnedFiles = mPinnedFiles.values();
1282         }
1283         for (PinnedFile pf : pinnedFiles) {
1284             PinnedFileStat stat = new PinnedFileStat(pf.fileName, pf.bytesPinned, pf.groupName);
1285             stats.add(stat);
1286         }
1287         if (mCurrentlyPinnedAnonSize > 0) {
1288             stats.add(new PinnedFileStat(
1289                     ANON_REGION_STAT_NAME, mCurrentlyPinnedAnonSize, ANON_REGION_STAT_NAME));
1290         }
1291         return stats;
1292     }
1293 
1294     public final class BinderService extends IPinnerService.Stub {
1295         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)1296         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1297             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw))
1298                 return;
1299             HashSet<PinnedFile> shownPins = new HashSet<>();
1300             HashSet<String> shownGroups = new HashSet<>();
1301             HashSet<String> groupsToPrint = new HashSet<>();
1302             final double bytesPerMB = 1024 * 1024;
1303             pw.format("Pinner Configs:\n");
1304             pw.format("   Total Pinner quota: %d%% of total device memory\n",
1305                     mConfiguredMaxPinnedMemoryPercentage);
1306             pw.format("   Maximum Pinner quota: %d bytes (%.2f MB)\n", mConfiguredMaxPinnedMemory,
1307                     mConfiguredMaxPinnedMemory / bytesPerMB);
1308             pw.format("   Max Home App Pin Bytes (without deps): %d (%.2f MB)\n",
1309                     mConfiguredHomePinBytes, mConfiguredHomePinBytes / bytesPerMB);
1310             pw.format("   Max Assistant App Pin Bytes (without deps): %d (%.2f MB)\n",
1311                     mConfiguredAssistantPinBytes, mConfiguredAssistantPinBytes / bytesPerMB);
1312             pw.format(
1313                     "   Max Camera App Pin Bytes (without deps): %d (%.2f MB)\n",
1314                     mConfiguredCameraPinBytes, mConfiguredCameraPinBytes / bytesPerMB);
1315             pw.format("\nPinned Files:\n");
1316             synchronized (PinnerService.this) {
1317                 long totalSize = 0;
1318 
1319                 // We print apps separately from regular pins as they contain extra information that
1320                 // other pins do not.
1321                 for (int key : mPinnedApps.keySet()) {
1322                     PinnedApp app = mPinnedApps.get(key);
1323                     pw.print(getNameForKey(key));
1324                     pw.print(" uid=");
1325                     pw.print(app.uid);
1326                     pw.print(" active=");
1327                     pw.print(app.active);
1328 
1329                     if (!app.mFiles.isEmpty()) {
1330                         shownGroups.add(app.mFiles.getFirst().groupName);
1331                     }
1332                     pw.println();
1333                     long bytesPinnedForApp = 0;
1334                     long bytesPinnedForAppDeps = 0;
1335                     for (PinnedFile pf : mPinnedApps.get(key).mFiles) {
1336                         pw.print("  ");
1337                         pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b\n", pf.fileName,
1338                                 pf.bytesPinned, pf.bytesPinned / bytesPerMB, pf.used_pinlist);
1339                         totalSize += pf.bytesPinned;
1340                         bytesPinnedForApp += pf.bytesPinned;
1341                         shownPins.add(pf);
1342                         for (PinnedFile dep : pf.pinnedDeps) {
1343                             pw.print("  ");
1344                             pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
1345                                     dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
1346                                     dep.used_pinlist);
1347                             totalSize += dep.bytesPinned;
1348                             bytesPinnedForAppDeps += dep.bytesPinned;
1349                             shownPins.add(dep);
1350                         }
1351                     }
1352                     long bytesPinnedForAppAndDeps = bytesPinnedForApp + bytesPinnedForAppDeps;
1353                     pw.format("Total Pinned = %d (%.2f MB) [App=%d (%.2f MB), "
1354                                     + "Dependencies=%d (%.2f MB)]\n\n",
1355                             bytesPinnedForAppAndDeps, bytesPinnedForAppAndDeps / bytesPerMB,
1356                             bytesPinnedForApp, bytesPinnedForApp / bytesPerMB,
1357                             bytesPinnedForAppDeps, bytesPinnedForAppDeps / bytesPerMB);
1358                 }
1359                 pw.println();
1360                 for (PinnedFile pinnedFile : mPinnedFiles.values()) {
1361                     if (!groupsToPrint.contains(pinnedFile.groupName)
1362                             && !shownGroups.contains(pinnedFile.groupName)) {
1363                         groupsToPrint.add(pinnedFile.groupName);
1364                     }
1365                 }
1366 
1367                 // Print all the non app groups.
1368                 for (String group : groupsToPrint) {
1369                     List<PinnedFile> groupPins = getAllPinsForGroup(group);
1370                     pw.print("\nGroup:" + group);
1371                     long bytesPinnedForGroupNoDeps = 0;
1372                     long bytesPinnedForGroupDeps = 0;
1373                     pw.println();
1374                     for (PinnedFile pinnedFile : groupPins) {
1375                         if (shownPins.contains(pinnedFile)) {
1376                             // Already displayed and accounted for, skip.
1377                             continue;
1378                         }
1379                         pw.format("  %s pinned: %d bytes (%.2f MB) pinlist:%b\n",
1380                                 pinnedFile.fileName, pinnedFile.bytesPinned,
1381                                 pinnedFile.bytesPinned / bytesPerMB, pinnedFile.used_pinlist);
1382                         totalSize += pinnedFile.bytesPinned;
1383                         bytesPinnedForGroupNoDeps += pinnedFile.bytesPinned;
1384                         shownPins.add(pinnedFile);
1385                         for (PinnedFile dep : pinnedFile.pinnedDeps) {
1386                             if (shownPins.contains(dep)) {
1387                                 // Already displayed and accounted for, skip.
1388                                 continue;
1389                             }
1390                             pw.print("  ");
1391                             pw.format("%s pinned:%d bytes (%.2f MB) pinlist:%b (Dependency)\n",
1392                                     dep.fileName, dep.bytesPinned, dep.bytesPinned / bytesPerMB,
1393                                     dep.used_pinlist);
1394                             totalSize += dep.bytesPinned;
1395                             bytesPinnedForGroupDeps += dep.bytesPinned;
1396                             shownPins.add(dep);
1397                         }
1398                     }
1399                     long bytesPinnedForGroup = bytesPinnedForGroupNoDeps + bytesPinnedForGroupDeps;
1400                     pw.format("Total Pinned = %d (%.2f MB) [Main=%d (%.2f MB), "
1401                                     + "Dependencies=%d (%.2f MB)]\n\n",
1402                             bytesPinnedForGroup, bytesPinnedForGroup / bytesPerMB,
1403                             bytesPinnedForGroupNoDeps, bytesPinnedForGroupNoDeps / bytesPerMB,
1404                             bytesPinnedForGroupDeps, bytesPinnedForGroupDeps / bytesPerMB);
1405                 }
1406                 pw.println();
1407                 if (mPinAnonAddress != 0) {
1408                     pw.format("Pinned anon region: %d (%.2f MB)\n", mCurrentlyPinnedAnonSize,
1409                             mCurrentlyPinnedAnonSize / bytesPerMB);
1410                     totalSize += mCurrentlyPinnedAnonSize;
1411                 }
1412                 pw.format("Total pinned: %d bytes (%.2f MB)\n", totalSize, totalSize / bytesPerMB);
1413                 pw.format("Available Pinner quota: %d bytes (%.2f MB)\n", getAvailableGlobalQuota(),
1414                         getAvailableGlobalQuota() / bytesPerMB);
1415                 pw.println();
1416                 if (!mPendingRepin.isEmpty()) {
1417                     pw.print("Pending repin: ");
1418                     for (int key : mPendingRepin.values()) {
1419                         pw.print(getNameForKey(key));
1420                         pw.print(' ');
1421                     }
1422                     pw.println();
1423                 }
1424             }
1425         }
1426 
repin()1427         private void repin() {
1428             sendUnpinAppsMessage();
1429             // TODO(morrita): Consider supporting non-system user.
1430             sendPinAppsWithUpdatedKeysMessage(UserHandle.USER_SYSTEM);
1431         }
1432 
printError(FileDescriptor out, String message)1433         private void printError(FileDescriptor out, String message) {
1434             PrintWriter writer = new PrintWriter(new FileOutputStream(out));
1435             writer.println(message);
1436             writer.flush();
1437         }
1438 
1439         @Override
onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver)1440         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
1441                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
1442             if (args.length < 1) {
1443                 printError(out, "Command is not given.");
1444                 resultReceiver.send(-1, null);
1445                 return;
1446             }
1447 
1448             String command = args[0];
1449             switch (command) {
1450                 case "repin":
1451                     repin();
1452                     break;
1453                 default:
1454                     printError(out,
1455                             String.format("Unknown pinner command: %s. Supported commands: repin",
1456                                     command));
1457                     resultReceiver.send(-1, null);
1458                     return;
1459             }
1460 
1461             resultReceiver.send(0, null);
1462         }
1463 
1464         @EnforcePermission(android.Manifest.permission.DUMP)
1465         @Override
getPinnerStats()1466         public List<PinnedFileStat> getPinnerStats() {
1467             getPinnerStats_enforcePermission();
1468             return PinnerService.this.getPinnerStats();
1469         }
1470     }
1471 
1472     final static class PinRange {
1473         int start;
1474         int length;
1475     }
1476 
1477     /**
1478      * Represents an app that was pinned.
1479      */
1480     private final class PinnedApp {
1481         /**
1482          * The uid of the package being pinned. This stays constant while the package stays
1483          * installed.
1484          */
1485         final int uid;
1486 
1487         /** Whether it is currently active, i.e. there is a running process from that package. */
1488         boolean active;
1489 
1490         /** List of pinned files. */
1491         final ArrayList<PinnedFile> mFiles = new ArrayList<>();
1492 
PinnedApp(ApplicationInfo appInfo)1493         private PinnedApp(ApplicationInfo appInfo) {
1494             uid = appInfo.uid;
1495             active = mAmInternal.isUidActive(uid);
1496         }
1497     }
1498 
1499     final class PinnerHandler extends Handler {
1500         static final int PIN_ONSTART_MSG = 4001;
1501 
PinnerHandler(Looper looper)1502         public PinnerHandler(Looper looper) {
1503             super(looper, null, true);
1504         }
1505 
1506         @Override
handleMessage(Message msg)1507         public void handleMessage(Message msg) {
1508             switch (msg.what) {
1509                 case PIN_ONSTART_MSG: {
1510                     handlePinOnStart();
1511                 } break;
1512 
1513                 default:
1514                     super.handleMessage(msg);
1515             }
1516         }
1517     }
1518 }
1519