PackageInstallerService.java revision ab2340996a515ea0c437ad5bb1ea1fa88ab9edff
1/* 2 * Copyright (C) 2014 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 17package com.android.server.pm; 18 19import static com.android.internal.util.XmlUtils.readBitmapAttribute; 20import static com.android.internal.util.XmlUtils.readBooleanAttribute; 21import static com.android.internal.util.XmlUtils.readIntAttribute; 22import static com.android.internal.util.XmlUtils.readLongAttribute; 23import static com.android.internal.util.XmlUtils.readStringAttribute; 24import static com.android.internal.util.XmlUtils.readUriAttribute; 25import static com.android.internal.util.XmlUtils.writeBooleanAttribute; 26import static com.android.internal.util.XmlUtils.writeIntAttribute; 27import static com.android.internal.util.XmlUtils.writeLongAttribute; 28import static com.android.internal.util.XmlUtils.writeStringAttribute; 29import static com.android.internal.util.XmlUtils.writeUriAttribute; 30import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 31import static org.xmlpull.v1.XmlPullParser.START_TAG; 32 33import android.Manifest; 34import android.app.ActivityManager; 35import android.app.AppGlobals; 36import android.app.AppOpsManager; 37import android.app.Notification; 38import android.app.NotificationManager; 39import android.app.PackageDeleteObserver; 40import android.app.PackageInstallObserver; 41import android.app.admin.DevicePolicyManager; 42import android.content.Context; 43import android.content.Intent; 44import android.content.IntentSender; 45import android.content.IntentSender.SendIntentException; 46import android.content.pm.ApplicationInfo; 47import android.content.pm.IPackageInstaller; 48import android.content.pm.IPackageInstallerCallback; 49import android.content.pm.IPackageInstallerSession; 50import android.content.pm.PackageInfo; 51import android.content.pm.PackageInstaller; 52import android.content.pm.PackageInstaller.SessionInfo; 53import android.content.pm.PackageInstaller.SessionParams; 54import android.content.pm.PackageManager; 55import android.content.pm.ParceledListSlice; 56import android.graphics.Bitmap; 57import android.graphics.Bitmap.CompressFormat; 58import android.graphics.BitmapFactory; 59import android.net.Uri; 60import android.os.Binder; 61import android.os.Bundle; 62import android.os.Environment; 63import android.os.Handler; 64import android.os.HandlerThread; 65import android.os.Looper; 66import android.os.Message; 67import android.os.Process; 68import android.os.RemoteCallbackList; 69import android.os.RemoteException; 70import android.os.SELinux; 71import android.os.UserHandle; 72import android.os.UserManager; 73import android.os.storage.StorageManager; 74import android.os.storage.VolumeInfo; 75import android.system.ErrnoException; 76import android.system.Os; 77import android.text.TextUtils; 78import android.text.format.DateUtils; 79import android.util.ArraySet; 80import android.util.AtomicFile; 81import android.util.ExceptionUtils; 82import android.util.Slog; 83import android.util.SparseArray; 84import android.util.SparseBooleanArray; 85import android.util.Xml; 86 87import libcore.io.IoUtils; 88 89import com.android.internal.R; 90import com.android.internal.annotations.GuardedBy; 91import com.android.internal.content.PackageHelper; 92import com.android.internal.util.FastXmlSerializer; 93import com.android.internal.util.ImageUtils; 94import com.android.internal.util.IndentingPrintWriter; 95import com.android.server.IoThread; 96import com.google.android.collect.Sets; 97 98import org.xmlpull.v1.XmlPullParser; 99import org.xmlpull.v1.XmlPullParserException; 100import org.xmlpull.v1.XmlSerializer; 101 102import java.io.File; 103import java.io.FileInputStream; 104import java.io.FileNotFoundException; 105import java.io.FileOutputStream; 106import java.io.FilenameFilter; 107import java.io.IOException; 108import java.nio.charset.StandardCharsets; 109import java.security.SecureRandom; 110import java.util.ArrayList; 111import java.util.List; 112import java.util.Objects; 113import java.util.Random; 114 115public class PackageInstallerService extends IPackageInstaller.Stub { 116 private static final String TAG = "PackageInstaller"; 117 private static final boolean LOGD = false; 118 119 // TODO: remove outstanding sessions when installer package goes away 120 // TODO: notify listeners in other users when package has been installed there 121 // TODO: purge expired sessions periodically in addition to at reboot 122 123 /** XML constants used in {@link #mSessionsFile} */ 124 private static final String TAG_SESSIONS = "sessions"; 125 private static final String TAG_SESSION = "session"; 126 private static final String ATTR_SESSION_ID = "sessionId"; 127 private static final String ATTR_USER_ID = "userId"; 128 private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; 129 private static final String ATTR_INSTALLER_UID = "installerUid"; 130 private static final String ATTR_CREATED_MILLIS = "createdMillis"; 131 private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; 132 private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid"; 133 private static final String ATTR_PREPARED = "prepared"; 134 private static final String ATTR_SEALED = "sealed"; 135 private static final String ATTR_MODE = "mode"; 136 private static final String ATTR_INSTALL_FLAGS = "installFlags"; 137 private static final String ATTR_INSTALL_LOCATION = "installLocation"; 138 private static final String ATTR_SIZE_BYTES = "sizeBytes"; 139 private static final String ATTR_APP_PACKAGE_NAME = "appPackageName"; 140 @Deprecated 141 private static final String ATTR_APP_ICON = "appIcon"; 142 private static final String ATTR_APP_LABEL = "appLabel"; 143 private static final String ATTR_ORIGINATING_URI = "originatingUri"; 144 private static final String ATTR_REFERRER_URI = "referrerUri"; 145 private static final String ATTR_ABI_OVERRIDE = "abiOverride"; 146 private static final String ATTR_VOLUME_UUID = "volumeUuid"; 147 148 /** Automatically destroy sessions older than this */ 149 private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS; 150 /** Upper bound on number of active sessions for a UID */ 151 private static final long MAX_ACTIVE_SESSIONS = 1024; 152 /** Upper bound on number of historical sessions for a UID */ 153 private static final long MAX_HISTORICAL_SESSIONS = 1048576; 154 155 private final Context mContext; 156 private final PackageManagerService mPm; 157 158 private AppOpsManager mAppOps; 159 private StorageManager mStorage; 160 161 private final HandlerThread mInstallThread; 162 private final Handler mInstallHandler; 163 164 private final Callbacks mCallbacks; 165 166 /** 167 * File storing persisted {@link #mSessions} metadata. 168 */ 169 private final AtomicFile mSessionsFile; 170 171 /** 172 * Directory storing persisted {@link #mSessions} metadata which is too 173 * heavy to store directly in {@link #mSessionsFile}. 174 */ 175 private final File mSessionsDir; 176 177 private final InternalCallback mInternalCallback = new InternalCallback(); 178 179 /** 180 * Used for generating session IDs. Since this is created at boot time, 181 * normal random might be predictable. 182 */ 183 private final Random mRandom = new SecureRandom(); 184 185 @GuardedBy("mSessions") 186 private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>(); 187 188 /** Historical sessions kept around for debugging purposes */ 189 @GuardedBy("mSessions") 190 private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); 191 192 /** Sessions allocated to legacy users */ 193 @GuardedBy("mSessions") 194 private final SparseBooleanArray mLegacySessions = new SparseBooleanArray(); 195 196 private static final FilenameFilter sStageFilter = new FilenameFilter() { 197 @Override 198 public boolean accept(File dir, String name) { 199 return isStageName(name); 200 } 201 }; 202 203 public PackageInstallerService(Context context, PackageManagerService pm) { 204 mContext = context; 205 mPm = pm; 206 207 mInstallThread = new HandlerThread(TAG); 208 mInstallThread.start(); 209 210 mInstallHandler = new Handler(mInstallThread.getLooper()); 211 212 mCallbacks = new Callbacks(mInstallThread.getLooper()); 213 214 mSessionsFile = new AtomicFile( 215 new File(Environment.getSystemSecureDirectory(), "install_sessions.xml")); 216 mSessionsDir = new File(Environment.getSystemSecureDirectory(), "install_sessions"); 217 mSessionsDir.mkdirs(); 218 219 synchronized (mSessions) { 220 readSessionsLocked(); 221 222 final File internalStagingDir = buildInternalStagingDir(); 223 final ArraySet<File> unclaimedStages = Sets.newArraySet( 224 internalStagingDir.listFiles(sStageFilter)); 225 final ArraySet<File> unclaimedIcons = Sets.newArraySet( 226 mSessionsDir.listFiles()); 227 228 // Ignore stages and icons claimed by active sessions 229 for (int i = 0; i < mSessions.size(); i++) { 230 final PackageInstallerSession session = mSessions.valueAt(i); 231 unclaimedStages.remove(session.stageDir); 232 unclaimedIcons.remove(buildAppIconFile(session.sessionId)); 233 } 234 235 // Clean up orphaned staging directories 236 for (File stage : unclaimedStages) { 237 Slog.w(TAG, "Deleting orphan stage " + stage); 238 if (stage.isDirectory()) { 239 mPm.mInstaller.rmPackageDir(stage.getAbsolutePath()); 240 } else { 241 stage.delete(); 242 } 243 } 244 245 // Clean up orphaned icons 246 for (File icon : unclaimedIcons) { 247 Slog.w(TAG, "Deleting orphan icon " + icon); 248 icon.delete(); 249 } 250 } 251 } 252 253 public void systemReady() { 254 mAppOps = mContext.getSystemService(AppOpsManager.class); 255 mStorage = mContext.getSystemService(StorageManager.class); 256 } 257 258 public void onSecureContainersAvailable() { 259 synchronized (mSessions) { 260 final ArraySet<String> unclaimed = new ArraySet<>(); 261 for (String cid : PackageHelper.getSecureContainerList()) { 262 if (isStageName(cid)) { 263 unclaimed.add(cid); 264 } 265 } 266 267 // Ignore stages claimed by active sessions 268 for (int i = 0; i < mSessions.size(); i++) { 269 final PackageInstallerSession session = mSessions.valueAt(i); 270 final String cid = session.stageCid; 271 272 if (unclaimed.remove(cid)) { 273 // Claimed by active session, mount it 274 PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(), 275 Process.SYSTEM_UID); 276 } 277 } 278 279 // Clean up orphaned staging containers 280 for (String cid : unclaimed) { 281 Slog.w(TAG, "Deleting orphan container " + cid); 282 PackageHelper.destroySdDir(cid); 283 } 284 } 285 } 286 287 public static boolean isStageName(String name) { 288 final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp"); 289 final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp"); 290 final boolean isLegacyContainer = name.startsWith("smdl2tmp"); 291 return isFile || isContainer || isLegacyContainer; 292 } 293 294 @Deprecated 295 public File allocateStageDirLegacy(String volumeUuid) throws IOException { 296 synchronized (mSessions) { 297 try { 298 final int sessionId = allocateSessionIdLocked(); 299 mLegacySessions.put(sessionId, true); 300 final File stageDir = buildStageDir(volumeUuid, sessionId); 301 prepareStageDir(stageDir); 302 return stageDir; 303 } catch (IllegalStateException e) { 304 throw new IOException(e); 305 } 306 } 307 } 308 309 @Deprecated 310 public String allocateExternalStageCidLegacy() { 311 synchronized (mSessions) { 312 final int sessionId = allocateSessionIdLocked(); 313 mLegacySessions.put(sessionId, true); 314 return "smdl" + sessionId + ".tmp"; 315 } 316 } 317 318 private void readSessionsLocked() { 319 if (LOGD) Slog.v(TAG, "readSessionsLocked()"); 320 321 mSessions.clear(); 322 323 FileInputStream fis = null; 324 try { 325 fis = mSessionsFile.openRead(); 326 final XmlPullParser in = Xml.newPullParser(); 327 in.setInput(fis, StandardCharsets.UTF_8.name()); 328 329 int type; 330 while ((type = in.next()) != END_DOCUMENT) { 331 if (type == START_TAG) { 332 final String tag = in.getName(); 333 if (TAG_SESSION.equals(tag)) { 334 final PackageInstallerSession session = readSessionLocked(in); 335 final long age = System.currentTimeMillis() - session.createdMillis; 336 337 final boolean valid; 338 if (age >= MAX_AGE_MILLIS) { 339 Slog.w(TAG, "Abandoning old session first created at " 340 + session.createdMillis); 341 valid = false; 342 } else { 343 valid = true; 344 } 345 346 if (valid) { 347 mSessions.put(session.sessionId, session); 348 } else { 349 // Since this is early during boot we don't send 350 // any observer events about the session, but we 351 // keep details around for dumpsys. 352 mHistoricalSessions.put(session.sessionId, session); 353 } 354 } 355 } 356 } 357 } catch (FileNotFoundException e) { 358 // Missing sessions are okay, probably first boot 359 } catch (IOException e) { 360 Slog.wtf(TAG, "Failed reading install sessions", e); 361 } catch (XmlPullParserException e) { 362 Slog.wtf(TAG, "Failed reading install sessions", e); 363 } finally { 364 IoUtils.closeQuietly(fis); 365 } 366 } 367 368 private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException { 369 final int sessionId = readIntAttribute(in, ATTR_SESSION_ID); 370 final int userId = readIntAttribute(in, ATTR_USER_ID); 371 final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); 372 final int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, 373 mPm.getPackageUid(installerPackageName, userId)); 374 final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); 375 final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR); 376 final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null; 377 final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID); 378 final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true); 379 final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); 380 381 final SessionParams params = new SessionParams( 382 SessionParams.MODE_INVALID); 383 params.mode = readIntAttribute(in, ATTR_MODE); 384 params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); 385 params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); 386 params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES); 387 params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME); 388 params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON); 389 params.appLabel = readStringAttribute(in, ATTR_APP_LABEL); 390 params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI); 391 params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI); 392 params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE); 393 params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID); 394 395 final File appIconFile = buildAppIconFile(sessionId); 396 if (appIconFile.exists()) { 397 params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath()); 398 params.appIconLastModified = appIconFile.lastModified(); 399 } 400 401 return new PackageInstallerSession(mInternalCallback, mContext, mPm, 402 mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid, 403 params, createdMillis, stageDir, stageCid, prepared, sealed); 404 } 405 406 private void writeSessionsLocked() { 407 if (LOGD) Slog.v(TAG, "writeSessionsLocked()"); 408 409 FileOutputStream fos = null; 410 try { 411 fos = mSessionsFile.startWrite(); 412 413 XmlSerializer out = new FastXmlSerializer(); 414 out.setOutput(fos, StandardCharsets.UTF_8.name()); 415 out.startDocument(null, true); 416 out.startTag(null, TAG_SESSIONS); 417 final int size = mSessions.size(); 418 for (int i = 0; i < size; i++) { 419 final PackageInstallerSession session = mSessions.valueAt(i); 420 writeSessionLocked(out, session); 421 } 422 out.endTag(null, TAG_SESSIONS); 423 out.endDocument(); 424 425 mSessionsFile.finishWrite(fos); 426 } catch (IOException e) { 427 if (fos != null) { 428 mSessionsFile.failWrite(fos); 429 } 430 } 431 } 432 433 private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session) 434 throws IOException { 435 final SessionParams params = session.params; 436 437 out.startTag(null, TAG_SESSION); 438 439 writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId); 440 writeIntAttribute(out, ATTR_USER_ID, session.userId); 441 writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, 442 session.installerPackageName); 443 writeIntAttribute(out, ATTR_INSTALLER_UID, session.installerUid); 444 writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); 445 if (session.stageDir != null) { 446 writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, 447 session.stageDir.getAbsolutePath()); 448 } 449 if (session.stageCid != null) { 450 writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid); 451 } 452 writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared()); 453 writeBooleanAttribute(out, ATTR_SEALED, session.isSealed()); 454 455 writeIntAttribute(out, ATTR_MODE, params.mode); 456 writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); 457 writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation); 458 writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes); 459 writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName); 460 writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel); 461 writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri); 462 writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri); 463 writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride); 464 writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid); 465 466 // Persist app icon if changed since last written 467 final File appIconFile = buildAppIconFile(session.sessionId); 468 if (params.appIcon == null && appIconFile.exists()) { 469 appIconFile.delete(); 470 } else if (params.appIcon != null 471 && appIconFile.lastModified() != params.appIconLastModified) { 472 if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile); 473 FileOutputStream os = null; 474 try { 475 os = new FileOutputStream(appIconFile); 476 params.appIcon.compress(CompressFormat.PNG, 90, os); 477 } catch (IOException e) { 478 Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage()); 479 } finally { 480 IoUtils.closeQuietly(os); 481 } 482 483 params.appIconLastModified = appIconFile.lastModified(); 484 } 485 486 out.endTag(null, TAG_SESSION); 487 } 488 489 private File buildAppIconFile(int sessionId) { 490 return new File(mSessionsDir, "app_icon." + sessionId + ".png"); 491 } 492 493 private void writeSessionsAsync() { 494 IoThread.getHandler().post(new Runnable() { 495 @Override 496 public void run() { 497 synchronized (mSessions) { 498 writeSessionsLocked(); 499 } 500 } 501 }); 502 } 503 504 @Override 505 public int createSession(SessionParams params, String installerPackageName, int userId) { 506 try { 507 return createSessionInternal(params, installerPackageName, userId); 508 } catch (IOException e) { 509 throw ExceptionUtils.wrap(e); 510 } 511 } 512 513 private int createSessionInternal(SessionParams params, String installerPackageName, int userId) 514 throws IOException { 515 final int callingUid = Binder.getCallingUid(); 516 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession"); 517 518 if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) { 519 throw new SecurityException("User restriction prevents installing"); 520 } 521 522 if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { 523 params.installFlags |= PackageManager.INSTALL_FROM_ADB; 524 525 } else { 526 mAppOps.checkPackage(callingUid, installerPackageName); 527 528 params.installFlags &= ~PackageManager.INSTALL_FROM_ADB; 529 params.installFlags &= ~PackageManager.INSTALL_ALL_USERS; 530 params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING; 531 } 532 533 // Only system components can circumvent runtime permissions when installing. 534 if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0 535 && mContext.checkCallingOrSelfPermission(Manifest.permission 536 .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) { 537 throw new SecurityException("You need the " 538 + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission " 539 + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag"); 540 } 541 542 // Defensively resize giant app icons 543 if (params.appIcon != null) { 544 final ActivityManager am = (ActivityManager) mContext.getSystemService( 545 Context.ACTIVITY_SERVICE); 546 final int iconSize = am.getLauncherLargeIconSize(); 547 if ((params.appIcon.getWidth() > iconSize * 2) 548 || (params.appIcon.getHeight() > iconSize * 2)) { 549 params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize, 550 true); 551 } 552 } 553 554 switch (params.mode) { 555 case SessionParams.MODE_FULL_INSTALL: 556 case SessionParams.MODE_INHERIT_EXISTING: 557 break; 558 default: 559 throw new IllegalArgumentException("Invalid install mode: " + params.mode); 560 } 561 562 // If caller requested explicit location, sanity check it, otherwise 563 // resolve the best internal or adopted location. 564 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 565 if (!PackageHelper.fitsOnInternal(mContext, params.sizeBytes)) { 566 throw new IOException("No suitable internal storage available"); 567 } 568 569 } else if ((params.installFlags & PackageManager.INSTALL_EXTERNAL) != 0) { 570 if (!PackageHelper.fitsOnExternal(mContext, params.sizeBytes)) { 571 throw new IOException("No suitable external storage available"); 572 } 573 574 } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) { 575 // For now, installs to adopted media are treated as internal from 576 // an install flag point-of-view. 577 params.setInstallFlagsInternal(); 578 579 } else { 580 // For now, installs to adopted media are treated as internal from 581 // an install flag point-of-view. 582 params.setInstallFlagsInternal(); 583 584 // Resolve best location for install, based on combination of 585 // requested install flags, delta size, and manifest settings. 586 final long ident = Binder.clearCallingIdentity(); 587 try { 588 params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, 589 params.appPackageName, params.installLocation, params.sizeBytes); 590 } finally { 591 Binder.restoreCallingIdentity(ident); 592 } 593 } 594 595 final int sessionId; 596 final PackageInstallerSession session; 597 synchronized (mSessions) { 598 // Sanity check that installer isn't going crazy 599 final int activeCount = getSessionCount(mSessions, callingUid); 600 if (activeCount >= MAX_ACTIVE_SESSIONS) { 601 throw new IllegalStateException( 602 "Too many active sessions for UID " + callingUid); 603 } 604 final int historicalCount = getSessionCount(mHistoricalSessions, callingUid); 605 if (historicalCount >= MAX_HISTORICAL_SESSIONS) { 606 throw new IllegalStateException( 607 "Too many historical sessions for UID " + callingUid); 608 } 609 610 final long createdMillis = System.currentTimeMillis(); 611 sessionId = allocateSessionIdLocked(); 612 613 // We're staging to exactly one location 614 File stageDir = null; 615 String stageCid = null; 616 if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) { 617 stageDir = buildStageDir(params.volumeUuid, sessionId); 618 } else { 619 stageCid = buildExternalStageCid(sessionId); 620 } 621 622 session = new PackageInstallerSession(mInternalCallback, mContext, mPm, 623 mInstallThread.getLooper(), sessionId, userId, installerPackageName, callingUid, 624 params, createdMillis, stageDir, stageCid, false, false); 625 mSessions.put(sessionId, session); 626 } 627 628 mCallbacks.notifySessionCreated(session.sessionId, session.userId); 629 writeSessionsAsync(); 630 return sessionId; 631 } 632 633 @Override 634 public void updateSessionAppIcon(int sessionId, Bitmap appIcon) { 635 synchronized (mSessions) { 636 final PackageInstallerSession session = mSessions.get(sessionId); 637 if (session == null || !isCallingUidOwner(session)) { 638 throw new SecurityException("Caller has no access to session " + sessionId); 639 } 640 641 // Defensively resize giant app icons 642 if (appIcon != null) { 643 final ActivityManager am = (ActivityManager) mContext.getSystemService( 644 Context.ACTIVITY_SERVICE); 645 final int iconSize = am.getLauncherLargeIconSize(); 646 if ((appIcon.getWidth() > iconSize * 2) 647 || (appIcon.getHeight() > iconSize * 2)) { 648 appIcon = Bitmap.createScaledBitmap(appIcon, iconSize, iconSize, true); 649 } 650 } 651 652 session.params.appIcon = appIcon; 653 session.params.appIconLastModified = -1; 654 655 mInternalCallback.onSessionBadgingChanged(session); 656 } 657 } 658 659 @Override 660 public void updateSessionAppLabel(int sessionId, String appLabel) { 661 synchronized (mSessions) { 662 final PackageInstallerSession session = mSessions.get(sessionId); 663 if (session == null || !isCallingUidOwner(session)) { 664 throw new SecurityException("Caller has no access to session " + sessionId); 665 } 666 session.params.appLabel = appLabel; 667 mInternalCallback.onSessionBadgingChanged(session); 668 } 669 } 670 671 @Override 672 public void abandonSession(int sessionId) { 673 synchronized (mSessions) { 674 final PackageInstallerSession session = mSessions.get(sessionId); 675 if (session == null || !isCallingUidOwner(session)) { 676 throw new SecurityException("Caller has no access to session " + sessionId); 677 } 678 session.abandon(); 679 } 680 } 681 682 @Override 683 public IPackageInstallerSession openSession(int sessionId) { 684 try { 685 return openSessionInternal(sessionId); 686 } catch (IOException e) { 687 throw ExceptionUtils.wrap(e); 688 } 689 } 690 691 private IPackageInstallerSession openSessionInternal(int sessionId) throws IOException { 692 synchronized (mSessions) { 693 final PackageInstallerSession session = mSessions.get(sessionId); 694 if (session == null || !isCallingUidOwner(session)) { 695 throw new SecurityException("Caller has no access to session " + sessionId); 696 } 697 session.open(); 698 return session; 699 } 700 } 701 702 private int allocateSessionIdLocked() { 703 int n = 0; 704 int sessionId; 705 do { 706 sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 707 if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null 708 && !mLegacySessions.get(sessionId, false)) { 709 return sessionId; 710 } 711 } while (n++ < 32); 712 713 throw new IllegalStateException("Failed to allocate session ID"); 714 } 715 716 private File buildInternalStagingDir() { 717 return new File(Environment.getDataDirectory(), "app"); 718 } 719 720 private File buildStagingDir(String volumeUuid) throws FileNotFoundException { 721 if (volumeUuid == null) { 722 return buildInternalStagingDir(); 723 } else { 724 final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); 725 if (vol != null && vol.type == VolumeInfo.TYPE_PRIVATE 726 && vol.isMountedWritable()) { 727 return new File(vol.path, "app"); 728 } else { 729 throw new FileNotFoundException("Failed to find volume for UUID " + volumeUuid); 730 } 731 } 732 } 733 734 private File buildStageDir(String volumeUuid, int sessionId) throws FileNotFoundException { 735 final File stagingDir = buildStagingDir(volumeUuid); 736 return new File(stagingDir, "vmdl" + sessionId + ".tmp"); 737 } 738 739 static void prepareStageDir(File stageDir) throws IOException { 740 if (stageDir.exists()) { 741 throw new IOException("Session dir already exists: " + stageDir); 742 } 743 744 try { 745 Os.mkdir(stageDir.getAbsolutePath(), 0755); 746 Os.chmod(stageDir.getAbsolutePath(), 0755); 747 } catch (ErrnoException e) { 748 // This purposefully throws if directory already exists 749 throw new IOException("Failed to prepare session dir: " + stageDir, e); 750 } 751 752 if (!SELinux.restorecon(stageDir)) { 753 throw new IOException("Failed to restorecon session dir: " + stageDir); 754 } 755 } 756 757 private String buildExternalStageCid(int sessionId) { 758 return "smdl" + sessionId + ".tmp"; 759 } 760 761 static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException { 762 if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(), 763 Process.SYSTEM_UID, true) == null) { 764 throw new IOException("Failed to create session cid: " + stageCid); 765 } 766 } 767 768 @Override 769 public SessionInfo getSessionInfo(int sessionId) { 770 synchronized (mSessions) { 771 final PackageInstallerSession session = mSessions.get(sessionId); 772 return session != null ? session.generateInfo() : null; 773 } 774 } 775 776 @Override 777 public ParceledListSlice<SessionInfo> getAllSessions(int userId) { 778 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions"); 779 780 final List<SessionInfo> result = new ArrayList<>(); 781 synchronized (mSessions) { 782 for (int i = 0; i < mSessions.size(); i++) { 783 final PackageInstallerSession session = mSessions.valueAt(i); 784 if (session.userId == userId) { 785 result.add(session.generateInfo()); 786 } 787 } 788 } 789 return new ParceledListSlice<>(result); 790 } 791 792 @Override 793 public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) { 794 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions"); 795 mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName); 796 797 final List<SessionInfo> result = new ArrayList<>(); 798 synchronized (mSessions) { 799 for (int i = 0; i < mSessions.size(); i++) { 800 final PackageInstallerSession session = mSessions.valueAt(i); 801 if (Objects.equals(session.installerPackageName, installerPackageName) 802 && session.userId == userId) { 803 result.add(session.generateInfo()); 804 } 805 } 806 } 807 return new ParceledListSlice<>(result); 808 } 809 810 @Override 811 public void uninstall(String packageName, String callerPackageName, int flags, 812 IntentSender statusReceiver, int userId) { 813 final int callingUid = Binder.getCallingUid(); 814 mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall"); 815 if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) { 816 mAppOps.checkPackage(callingUid, callerPackageName); 817 } 818 819 // Check whether the caller is device owner 820 DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService( 821 Context.DEVICE_POLICY_SERVICE); 822 boolean isDeviceOwner = (dpm != null) && dpm.isDeviceOwnerApp(callerPackageName); 823 824 final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext, 825 statusReceiver, packageName, isDeviceOwner, userId); 826 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DELETE_PACKAGES) 827 == PackageManager.PERMISSION_GRANTED) { 828 // Sweet, call straight through! 829 mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); 830 } else if (isDeviceOwner) { 831 // Allow the DeviceOwner to silently delete packages 832 // Need to clear the calling identity to get DELETE_PACKAGES permission 833 long ident = Binder.clearCallingIdentity(); 834 try { 835 mPm.deletePackage(packageName, adapter.getBinder(), userId, flags); 836 } finally { 837 Binder.restoreCallingIdentity(ident); 838 } 839 } else { 840 // Take a short detour to confirm with user 841 final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE); 842 intent.setData(Uri.fromParts("package", packageName, null)); 843 intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder()); 844 adapter.onUserActionRequired(intent); 845 } 846 } 847 848 @Override 849 public void setPermissionsResult(int sessionId, boolean accepted) { 850 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG); 851 852 synchronized (mSessions) { 853 mSessions.get(sessionId).setPermissionsResult(accepted); 854 } 855 } 856 857 @Override 858 public void registerCallback(IPackageInstallerCallback callback, int userId) { 859 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback"); 860 mCallbacks.register(callback, userId); 861 } 862 863 @Override 864 public void unregisterCallback(IPackageInstallerCallback callback) { 865 mCallbacks.unregister(callback); 866 } 867 868 private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, 869 int installerUid) { 870 int count = 0; 871 final int size = sessions.size(); 872 for (int i = 0; i < size; i++) { 873 final PackageInstallerSession session = sessions.valueAt(i); 874 if (session.installerUid == installerUid) { 875 count++; 876 } 877 } 878 return count; 879 } 880 881 private boolean isCallingUidOwner(PackageInstallerSession session) { 882 final int callingUid = Binder.getCallingUid(); 883 if (callingUid == Process.ROOT_UID) { 884 return true; 885 } else { 886 return (session != null) && (callingUid == session.installerUid); 887 } 888 } 889 890 static class PackageDeleteObserverAdapter extends PackageDeleteObserver { 891 private final Context mContext; 892 private final IntentSender mTarget; 893 private final String mPackageName; 894 private final Notification mNotification; 895 896 public PackageDeleteObserverAdapter(Context context, IntentSender target, 897 String packageName, boolean showNotification, int userId) { 898 mContext = context; 899 mTarget = target; 900 mPackageName = packageName; 901 if (showNotification) { 902 mNotification = buildSuccessNotification(mContext, 903 mContext.getResources().getString(R.string.package_deleted_device_owner), 904 packageName, 905 userId); 906 } else { 907 mNotification = null; 908 } 909 } 910 911 @Override 912 public void onUserActionRequired(Intent intent) { 913 final Intent fillIn = new Intent(); 914 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); 915 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 916 PackageInstaller.STATUS_PENDING_USER_ACTION); 917 fillIn.putExtra(Intent.EXTRA_INTENT, intent); 918 try { 919 mTarget.sendIntent(mContext, 0, fillIn, null, null); 920 } catch (SendIntentException ignored) { 921 } 922 } 923 924 @Override 925 public void onPackageDeleted(String basePackageName, int returnCode, String msg) { 926 if (PackageManager.DELETE_SUCCEEDED == returnCode && mNotification != null) { 927 NotificationManager notificationManager = (NotificationManager) 928 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 929 notificationManager.notify(basePackageName, 0, mNotification); 930 } 931 final Intent fillIn = new Intent(); 932 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, mPackageName); 933 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 934 PackageManager.deleteStatusToPublicStatus(returnCode)); 935 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, 936 PackageManager.deleteStatusToString(returnCode, msg)); 937 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); 938 try { 939 mTarget.sendIntent(mContext, 0, fillIn, null, null); 940 } catch (SendIntentException ignored) { 941 } 942 } 943 } 944 945 static class PackageInstallObserverAdapter extends PackageInstallObserver { 946 private final Context mContext; 947 private final IntentSender mTarget; 948 private final int mSessionId; 949 private final boolean mShowNotification; 950 private final int mUserId; 951 952 public PackageInstallObserverAdapter(Context context, IntentSender target, int sessionId, 953 boolean showNotification, int userId) { 954 mContext = context; 955 mTarget = target; 956 mSessionId = sessionId; 957 mShowNotification = showNotification; 958 mUserId = userId; 959 } 960 961 @Override 962 public void onUserActionRequired(Intent intent) { 963 final Intent fillIn = new Intent(); 964 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); 965 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 966 PackageInstaller.STATUS_PENDING_USER_ACTION); 967 fillIn.putExtra(Intent.EXTRA_INTENT, intent); 968 try { 969 mTarget.sendIntent(mContext, 0, fillIn, null, null); 970 } catch (SendIntentException ignored) { 971 } 972 } 973 974 @Override 975 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 976 Bundle extras) { 977 if (PackageManager.INSTALL_SUCCEEDED == returnCode && mShowNotification) { 978 boolean update = (extras != null) && extras.getBoolean(Intent.EXTRA_REPLACING); 979 Notification notification = buildSuccessNotification(mContext, 980 mContext.getResources() 981 .getString(update ? R.string.package_updated_device_owner : 982 R.string.package_installed_device_owner), 983 basePackageName, 984 mUserId); 985 if (notification != null) { 986 NotificationManager notificationManager = (NotificationManager) 987 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 988 notificationManager.notify(basePackageName, 0, notification); 989 } 990 } 991 final Intent fillIn = new Intent(); 992 fillIn.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, basePackageName); 993 fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, mSessionId); 994 fillIn.putExtra(PackageInstaller.EXTRA_STATUS, 995 PackageManager.installStatusToPublicStatus(returnCode)); 996 fillIn.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, 997 PackageManager.installStatusToString(returnCode, msg)); 998 fillIn.putExtra(PackageInstaller.EXTRA_LEGACY_STATUS, returnCode); 999 if (extras != null) { 1000 final String existing = extras.getString( 1001 PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE); 1002 if (!TextUtils.isEmpty(existing)) { 1003 fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); 1004 } 1005 } 1006 try { 1007 mTarget.sendIntent(mContext, 0, fillIn, null, null); 1008 } catch (SendIntentException ignored) { 1009 } 1010 } 1011 } 1012 1013 /** 1014 * Build a notification for package installation / deletion by device owners that is shown if 1015 * the operation succeeds. 1016 */ 1017 private static Notification buildSuccessNotification(Context context, String contentText, 1018 String basePackageName, int userId) { 1019 PackageInfo packageInfo = null; 1020 try { 1021 packageInfo = AppGlobals.getPackageManager().getPackageInfo( 1022 basePackageName, 0, userId); 1023 } catch (RemoteException ignored) { 1024 } 1025 if (packageInfo == null || packageInfo.applicationInfo == null) { 1026 Slog.w(TAG, "Notification not built for package: " + basePackageName); 1027 return null; 1028 } 1029 PackageManager pm = context.getPackageManager(); 1030 Bitmap packageIcon = ImageUtils.buildScaledBitmap( 1031 packageInfo.applicationInfo.loadIcon(pm), 1032 context.getResources().getDimensionPixelSize( 1033 android.R.dimen.notification_large_icon_width), 1034 context.getResources().getDimensionPixelSize( 1035 android.R.dimen.notification_large_icon_height)); 1036 CharSequence packageLabel = packageInfo.applicationInfo.loadLabel(pm); 1037 return new Notification.Builder(context) 1038 .setSmallIcon(R.drawable.ic_check_circle_24px) 1039 .setColor(context.getResources().getColor( 1040 R.color.system_notification_accent_color)) 1041 .setContentTitle(packageLabel) 1042 .setContentText(contentText) 1043 .setStyle(new Notification.BigTextStyle().bigText(contentText)) 1044 .setLargeIcon(packageIcon) 1045 .build(); 1046 } 1047 1048 private static class Callbacks extends Handler { 1049 private static final int MSG_SESSION_CREATED = 1; 1050 private static final int MSG_SESSION_BADGING_CHANGED = 2; 1051 private static final int MSG_SESSION_ACTIVE_CHANGED = 3; 1052 private static final int MSG_SESSION_PROGRESS_CHANGED = 4; 1053 private static final int MSG_SESSION_FINISHED = 5; 1054 1055 private final RemoteCallbackList<IPackageInstallerCallback> 1056 mCallbacks = new RemoteCallbackList<>(); 1057 1058 public Callbacks(Looper looper) { 1059 super(looper); 1060 } 1061 1062 public void register(IPackageInstallerCallback callback, int userId) { 1063 mCallbacks.register(callback, new UserHandle(userId)); 1064 } 1065 1066 public void unregister(IPackageInstallerCallback callback) { 1067 mCallbacks.unregister(callback); 1068 } 1069 1070 @Override 1071 public void handleMessage(Message msg) { 1072 final int userId = msg.arg2; 1073 final int n = mCallbacks.beginBroadcast(); 1074 for (int i = 0; i < n; i++) { 1075 final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); 1076 final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); 1077 // TODO: dispatch notifications for slave profiles 1078 if (userId == user.getIdentifier()) { 1079 try { 1080 invokeCallback(callback, msg); 1081 } catch (RemoteException ignored) { 1082 } 1083 } 1084 } 1085 mCallbacks.finishBroadcast(); 1086 } 1087 1088 private void invokeCallback(IPackageInstallerCallback callback, Message msg) 1089 throws RemoteException { 1090 final int sessionId = msg.arg1; 1091 switch (msg.what) { 1092 case MSG_SESSION_CREATED: 1093 callback.onSessionCreated(sessionId); 1094 break; 1095 case MSG_SESSION_BADGING_CHANGED: 1096 callback.onSessionBadgingChanged(sessionId); 1097 break; 1098 case MSG_SESSION_ACTIVE_CHANGED: 1099 callback.onSessionActiveChanged(sessionId, (boolean) msg.obj); 1100 break; 1101 case MSG_SESSION_PROGRESS_CHANGED: 1102 callback.onSessionProgressChanged(sessionId, (float) msg.obj); 1103 break; 1104 case MSG_SESSION_FINISHED: 1105 callback.onSessionFinished(sessionId, (boolean) msg.obj); 1106 break; 1107 } 1108 } 1109 1110 private void notifySessionCreated(int sessionId, int userId) { 1111 obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget(); 1112 } 1113 1114 private void notifySessionBadgingChanged(int sessionId, int userId) { 1115 obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, userId).sendToTarget(); 1116 } 1117 1118 private void notifySessionActiveChanged(int sessionId, int userId, boolean active) { 1119 obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, userId, active).sendToTarget(); 1120 } 1121 1122 private void notifySessionProgressChanged(int sessionId, int userId, float progress) { 1123 obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget(); 1124 } 1125 1126 public void notifySessionFinished(int sessionId, int userId, boolean success) { 1127 obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget(); 1128 } 1129 } 1130 1131 void dump(IndentingPrintWriter pw) { 1132 synchronized (mSessions) { 1133 pw.println("Active install sessions:"); 1134 pw.increaseIndent(); 1135 int N = mSessions.size(); 1136 for (int i = 0; i < N; i++) { 1137 final PackageInstallerSession session = mSessions.valueAt(i); 1138 session.dump(pw); 1139 pw.println(); 1140 } 1141 pw.println(); 1142 pw.decreaseIndent(); 1143 1144 pw.println("Historical install sessions:"); 1145 pw.increaseIndent(); 1146 N = mHistoricalSessions.size(); 1147 for (int i = 0; i < N; i++) { 1148 final PackageInstallerSession session = mHistoricalSessions.valueAt(i); 1149 session.dump(pw); 1150 pw.println(); 1151 } 1152 pw.println(); 1153 pw.decreaseIndent(); 1154 1155 pw.println("Legacy install sessions:"); 1156 pw.increaseIndent(); 1157 pw.println(mLegacySessions.toString()); 1158 pw.decreaseIndent(); 1159 } 1160 } 1161 1162 class InternalCallback { 1163 public void onSessionBadgingChanged(PackageInstallerSession session) { 1164 mCallbacks.notifySessionBadgingChanged(session.sessionId, session.userId); 1165 writeSessionsAsync(); 1166 } 1167 1168 public void onSessionActiveChanged(PackageInstallerSession session, boolean active) { 1169 mCallbacks.notifySessionActiveChanged(session.sessionId, session.userId, active); 1170 } 1171 1172 public void onSessionProgressChanged(PackageInstallerSession session, float progress) { 1173 mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); 1174 } 1175 1176 public void onSessionFinished(final PackageInstallerSession session, boolean success) { 1177 mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); 1178 1179 mInstallHandler.post(new Runnable() { 1180 @Override 1181 public void run() { 1182 synchronized (mSessions) { 1183 mSessions.remove(session.sessionId); 1184 mHistoricalSessions.put(session.sessionId, session); 1185 1186 final File appIconFile = buildAppIconFile(session.sessionId); 1187 if (appIconFile.exists()) { 1188 appIconFile.delete(); 1189 } 1190 1191 writeSessionsLocked(); 1192 } 1193 } 1194 }); 1195 } 1196 1197 public void onSessionPrepared(PackageInstallerSession session) { 1198 // We prepared the destination to write into; we want to persist 1199 // this, but it's not critical enough to block for. 1200 writeSessionsAsync(); 1201 } 1202 1203 public void onSessionSealedBlocking(PackageInstallerSession session) { 1204 // It's very important that we block until we've recorded the 1205 // session as being sealed, since we never want to allow mutation 1206 // after sealing. 1207 synchronized (mSessions) { 1208 writeSessionsLocked(); 1209 } 1210 } 1211 } 1212} 1213