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