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