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