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