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