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