PackageInstallerService.java revision 7328a1b39b3dae1c0cd390c0a3695c6a46b8e9d8
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 android.content.pm.PackageManager.INSTALL_ALL_USERS; 20import static android.content.pm.PackageManager.INSTALL_FROM_ADB; 21import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING; 22import static com.android.internal.util.XmlUtils.readBitmapAttribute; 23import static com.android.internal.util.XmlUtils.readBooleanAttribute; 24import static com.android.internal.util.XmlUtils.readIntAttribute; 25import static com.android.internal.util.XmlUtils.readLongAttribute; 26import static com.android.internal.util.XmlUtils.readStringAttribute; 27import static com.android.internal.util.XmlUtils.readUriAttribute; 28import static com.android.internal.util.XmlUtils.writeBitmapAttribute; 29import static com.android.internal.util.XmlUtils.writeBooleanAttribute; 30import static com.android.internal.util.XmlUtils.writeIntAttribute; 31import static com.android.internal.util.XmlUtils.writeLongAttribute; 32import static com.android.internal.util.XmlUtils.writeStringAttribute; 33import static com.android.internal.util.XmlUtils.writeUriAttribute; 34import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; 35import static org.xmlpull.v1.XmlPullParser.START_TAG; 36 37import android.app.ActivityManager; 38import android.app.AppOpsManager; 39import android.content.Context; 40import android.content.pm.IPackageDeleteObserver2; 41import android.content.pm.IPackageInstaller; 42import android.content.pm.IPackageInstallerCallback; 43import android.content.pm.IPackageInstallerSession; 44import android.content.pm.InstallSessionInfo; 45import android.content.pm.InstallSessionParams; 46import android.content.pm.PackageManager; 47import android.graphics.Bitmap; 48import android.os.Binder; 49import android.os.Environment; 50import android.os.FileUtils; 51import android.os.Handler; 52import android.os.HandlerThread; 53import android.os.Looper; 54import android.os.Message; 55import android.os.Process; 56import android.os.RemoteCallbackList; 57import android.os.RemoteException; 58import android.os.SELinux; 59import android.os.UserHandle; 60import android.os.UserManager; 61import android.system.ErrnoException; 62import android.system.Os; 63import android.text.format.DateUtils; 64import android.util.ArraySet; 65import android.util.AtomicFile; 66import android.util.ExceptionUtils; 67import android.util.Log; 68import android.util.Slog; 69import android.util.SparseArray; 70import android.util.Xml; 71 72import com.android.internal.annotations.GuardedBy; 73import com.android.internal.util.FastXmlSerializer; 74import com.android.internal.util.IndentingPrintWriter; 75import com.android.server.IoThread; 76import com.android.server.pm.PackageInstallerSession.Snapshot; 77import com.google.android.collect.Sets; 78 79import libcore.io.IoUtils; 80 81import org.xmlpull.v1.XmlPullParser; 82import org.xmlpull.v1.XmlPullParserException; 83import org.xmlpull.v1.XmlSerializer; 84 85import java.io.File; 86import java.io.FileInputStream; 87import java.io.FileNotFoundException; 88import java.io.FileOutputStream; 89import java.io.FilenameFilter; 90import java.io.IOException; 91import java.security.SecureRandom; 92import java.util.ArrayList; 93import java.util.List; 94import java.util.Objects; 95import java.util.Random; 96 97public class PackageInstallerService extends IPackageInstaller.Stub { 98 private static final String TAG = "PackageInstaller"; 99 private static final boolean LOGD = true; 100 101 // TODO: remove outstanding sessions when installer package goes away 102 // TODO: notify listeners in other users when package has been installed there 103 104 /** XML constants used in {@link #mSessionsFile} */ 105 private static final String TAG_SESSIONS = "sessions"; 106 private static final String TAG_SESSION = "session"; 107 private static final String ATTR_SESSION_ID = "sessionId"; 108 private static final String ATTR_USER_ID = "userId"; 109 private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName"; 110 private static final String ATTR_CREATED_MILLIS = "createdMillis"; 111 private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir"; 112 private static final String ATTR_SEALED = "sealed"; 113 private static final String ATTR_MODE = "mode"; 114 private static final String ATTR_INSTALL_FLAGS = "installFlags"; 115 private static final String ATTR_INSTALL_LOCATION = "installLocation"; 116 private static final String ATTR_SIZE_BYTES = "sizeBytes"; 117 private static final String ATTR_APP_PACKAGE_NAME = "appPackageName"; 118 private static final String ATTR_APP_ICON = "appIcon"; 119 private static final String ATTR_APP_LABEL = "appLabel"; 120 private static final String ATTR_ORIGINATING_URI = "originatingUri"; 121 private static final String ATTR_REFERRER_URI = "referrerUri"; 122 private static final String ATTR_ABI_OVERRIDE = "abiOverride"; 123 124 /** Automatically destroy sessions older than this */ 125 private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS; 126 /** Upper bound on number of active sessions for a UID */ 127 private static final long MAX_ACTIVE_SESSIONS = 1024; 128 /** Upper bound on number of historical sessions for a UID */ 129 private static final long MAX_HISTORICAL_SESSIONS = 1048576; 130 131 private final Context mContext; 132 private final PackageManagerService mPm; 133 private final AppOpsManager mAppOps; 134 135 private final File mStagingDir; 136 private final HandlerThread mInstallThread; 137 138 private final Callbacks mCallbacks; 139 140 /** 141 * File storing persisted {@link #mSessions}. 142 */ 143 private final AtomicFile mSessionsFile; 144 145 private final InternalCallback mInternalCallback = new InternalCallback(); 146 147 /** 148 * Used for generating session IDs. Since this is created at boot time, 149 * normal random might be predictable. 150 */ 151 private final Random mRandom = new SecureRandom(); 152 153 @GuardedBy("mSessions") 154 private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>(); 155 156 /** Historical sessions kept around for debugging purposes */ 157 @GuardedBy("mSessions") 158 private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>(); 159 160 private static final FilenameFilter sStageFilter = new FilenameFilter() { 161 @Override 162 public boolean accept(File dir, String name) { 163 return name.startsWith("vmdl") && name.endsWith(".tmp"); 164 } 165 }; 166 167 public PackageInstallerService(Context context, PackageManagerService pm, File stagingDir) { 168 mContext = context; 169 mPm = pm; 170 mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); 171 172 mStagingDir = stagingDir; 173 174 mInstallThread = new HandlerThread(TAG); 175 mInstallThread.start(); 176 177 mCallbacks = new Callbacks(mInstallThread.getLooper()); 178 179 mSessionsFile = new AtomicFile( 180 new File(Environment.getSystemSecureDirectory(), "install_sessions.xml")); 181 182 synchronized (mSessions) { 183 readSessionsLocked(); 184 185 // Clean up orphaned staging directories 186 final ArraySet<File> stages = Sets.newArraySet(mStagingDir.listFiles(sStageFilter)); 187 for (int i = 0; i < mSessions.size(); i++) { 188 final PackageInstallerSession session = mSessions.valueAt(i); 189 stages.remove(session.sessionStageDir); 190 } 191 for (File stage : stages) { 192 Slog.w(TAG, "Deleting orphan stage " + stage); 193 if (stage.isDirectory()) { 194 FileUtils.deleteContents(stage); 195 } 196 stage.delete(); 197 } 198 } 199 } 200 201 public static boolean isStageFile(File file) { 202 return sStageFilter.accept(null, file.getName()); 203 } 204 205 @Deprecated 206 public File allocateSessionDir() throws IOException { 207 synchronized (mSessions) { 208 try { 209 final int sessionId = allocateSessionIdLocked(); 210 return prepareSessionStageDir(sessionId); 211 } catch (IllegalStateException e) { 212 throw new IOException(e); 213 } 214 } 215 } 216 217 private void readSessionsLocked() { 218 if (LOGD) Slog.v(TAG, "readSessionsLocked()"); 219 220 mSessions.clear(); 221 222 FileInputStream fis = null; 223 try { 224 fis = mSessionsFile.openRead(); 225 final XmlPullParser in = Xml.newPullParser(); 226 in.setInput(fis, null); 227 228 int type; 229 while ((type = in.next()) != END_DOCUMENT) { 230 if (type == START_TAG) { 231 final String tag = in.getName(); 232 if (TAG_SESSION.equals(tag)) { 233 final PackageInstallerSession session = readSessionLocked(in); 234 final long age = System.currentTimeMillis() - session.createdMillis; 235 236 final boolean valid; 237 if (age >= MAX_AGE_MILLIS) { 238 Slog.w(TAG, "Abandoning old session first created at " 239 + session.createdMillis); 240 valid = false; 241 } else if (!session.sessionStageDir.exists()) { 242 Slog.w(TAG, "Abandoning session with missing stage " 243 + session.sessionStageDir); 244 valid = false; 245 } else { 246 valid = true; 247 } 248 249 if (valid) { 250 mSessions.put(session.sessionId, session); 251 } else { 252 // Since this is early during boot we don't send 253 // any observer events about the session, but we 254 // keep details around for dumpsys. 255 mHistoricalSessions.put(session.sessionId, session); 256 } 257 } 258 } 259 } 260 } catch (FileNotFoundException e) { 261 // Missing sessions are okay, probably first boot 262 } catch (IOException e) { 263 Log.wtf(TAG, "Failed reading install sessions", e); 264 } catch (XmlPullParserException e) { 265 Log.wtf(TAG, "Failed reading install sessions", e); 266 } finally { 267 IoUtils.closeQuietly(fis); 268 } 269 } 270 271 private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException { 272 final int sessionId = readIntAttribute(in, ATTR_SESSION_ID); 273 final int userId = readIntAttribute(in, ATTR_USER_ID); 274 final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME); 275 final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS); 276 final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR)); 277 final boolean sealed = readBooleanAttribute(in, ATTR_SEALED); 278 279 final InstallSessionParams params = new InstallSessionParams( 280 InstallSessionParams.MODE_INVALID); 281 params.mode = readIntAttribute(in, ATTR_MODE); 282 params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS); 283 params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION); 284 params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES); 285 params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME); 286 params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON); 287 params.appLabel = readStringAttribute(in, ATTR_APP_LABEL); 288 params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI); 289 params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI); 290 params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE); 291 292 return new PackageInstallerSession(mInternalCallback, mPm, mInstallThread.getLooper(), 293 sessionId, userId, installerPackageName, params, createdMillis, sessionStageDir, 294 sealed); 295 } 296 297 private void writeSessionsLocked() { 298 if (LOGD) Slog.v(TAG, "writeSessionsLocked()"); 299 300 FileOutputStream fos = null; 301 try { 302 fos = mSessionsFile.startWrite(); 303 304 XmlSerializer out = new FastXmlSerializer(); 305 out.setOutput(fos, "utf-8"); 306 out.startDocument(null, true); 307 out.startTag(null, TAG_SESSIONS); 308 final int size = mSessions.size(); 309 for (int i = 0; i < size; i++) { 310 final PackageInstallerSession session = mSessions.valueAt(i); 311 writeSessionLocked(out, session); 312 } 313 out.endTag(null, TAG_SESSIONS); 314 out.endDocument(); 315 316 mSessionsFile.finishWrite(fos); 317 } catch (IOException e) { 318 if (fos != null) { 319 mSessionsFile.failWrite(fos); 320 } 321 } 322 } 323 324 private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session) 325 throws IOException { 326 final InstallSessionParams params = session.params; 327 final Snapshot snapshot = session.snapshot(); 328 329 out.startTag(null, TAG_SESSION); 330 331 writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId); 332 writeIntAttribute(out, ATTR_USER_ID, session.userId); 333 writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME, 334 session.installerPackageName); 335 writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis); 336 writeStringAttribute(out, ATTR_SESSION_STAGE_DIR, 337 session.sessionStageDir.getAbsolutePath()); 338 writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed); 339 340 writeIntAttribute(out, ATTR_MODE, params.mode); 341 writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags); 342 writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation); 343 writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes); 344 writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName); 345 writeBitmapAttribute(out, ATTR_APP_ICON, params.appIcon); 346 writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel); 347 writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri); 348 writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri); 349 writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride); 350 351 out.endTag(null, TAG_SESSION); 352 } 353 354 private void writeSessionsAsync() { 355 IoThread.getHandler().post(new Runnable() { 356 @Override 357 public void run() { 358 synchronized (mSessions) { 359 writeSessionsLocked(); 360 } 361 } 362 }); 363 } 364 365 @Override 366 public int createSession(InstallSessionParams params, String installerPackageName, int userId) { 367 final int callingUid = Binder.getCallingUid(); 368 mPm.enforceCrossUserPermission(callingUid, userId, true, "createSession"); 369 370 if (mPm.isUserRestricted(UserHandle.getUserId(callingUid), 371 UserManager.DISALLOW_INSTALL_APPS)) { 372 throw new SecurityException("User restriction prevents installing"); 373 } 374 375 if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) { 376 installerPackageName = "com.android.shell"; 377 378 params.installFlags |= INSTALL_FROM_ADB; 379 380 } else { 381 mAppOps.checkPackage(callingUid, installerPackageName); 382 383 params.installFlags &= ~INSTALL_FROM_ADB; 384 params.installFlags &= ~INSTALL_ALL_USERS; 385 params.installFlags |= INSTALL_REPLACE_EXISTING; 386 } 387 388 switch (params.mode) { 389 case InstallSessionParams.MODE_FULL_INSTALL: 390 case InstallSessionParams.MODE_INHERIT_EXISTING: 391 break; 392 default: 393 throw new IllegalArgumentException("Params must have valid mode set"); 394 } 395 396 // Defensively resize giant app icons 397 if (params.appIcon != null) { 398 final ActivityManager am = (ActivityManager) mContext.getSystemService( 399 Context.ACTIVITY_SERVICE); 400 final int iconSize = am.getLauncherLargeIconSize(); 401 if ((params.appIcon.getWidth() > iconSize * 2) 402 || (params.appIcon.getHeight() > iconSize * 2)) { 403 params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize, 404 true); 405 } 406 } 407 408 // Sanity check that install could fit 409 if (params.sizeBytes > 0) { 410 try { 411 mPm.freeStorage(params.sizeBytes); 412 } catch (IOException e) { 413 throw ExceptionUtils.wrap(e); 414 } 415 } 416 417 final int sessionId; 418 final PackageInstallerSession session; 419 synchronized (mSessions) { 420 // Sanity check that installer isn't going crazy 421 final int activeCount = getSessionCount(mSessions, callingUid); 422 if (activeCount >= MAX_ACTIVE_SESSIONS) { 423 throw new IllegalStateException( 424 "Too many active sessions for UID " + callingUid); 425 } 426 final int historicalCount = getSessionCount(mHistoricalSessions, callingUid); 427 if (historicalCount >= MAX_HISTORICAL_SESSIONS) { 428 throw new IllegalStateException( 429 "Too many historical sessions for UID " + callingUid); 430 } 431 432 sessionId = allocateSessionIdLocked(); 433 434 final long createdMillis = System.currentTimeMillis(); 435 final File sessionStageDir = prepareSessionStageDir(sessionId); 436 437 session = new PackageInstallerSession(mInternalCallback, mPm, 438 mInstallThread.getLooper(), sessionId, userId, installerPackageName, params, 439 createdMillis, sessionStageDir, false); 440 mSessions.put(sessionId, session); 441 } 442 443 mCallbacks.notifySessionCreated(session.sessionId, session.userId); 444 writeSessionsAsync(); 445 return sessionId; 446 } 447 448 @Override 449 public IPackageInstallerSession openSession(int sessionId) { 450 synchronized (mSessions) { 451 final PackageInstallerSession session = mSessions.get(sessionId); 452 if (session == null) { 453 throw new IllegalStateException("Missing session " + sessionId); 454 } 455 if (!isCallingUidOwner(session)) { 456 throw new SecurityException("Caller has no access to session " + sessionId); 457 } 458 if (session.openCount.getAndIncrement() == 0) { 459 mCallbacks.notifySessionOpened(sessionId, session.userId); 460 } 461 return session; 462 } 463 } 464 465 private int allocateSessionIdLocked() { 466 int n = 0; 467 int sessionId; 468 do { 469 sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; 470 if (mSessions.get(sessionId) == null && mHistoricalSessions.get(sessionId) == null) { 471 return sessionId; 472 } 473 } while (n++ < 32); 474 475 throw new IllegalStateException("Failed to allocate session ID"); 476 } 477 478 private File prepareSessionStageDir(int sessionId) { 479 final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp"); 480 481 if (file.exists()) { 482 throw new IllegalStateException("Session dir already exists: " + file); 483 } 484 485 try { 486 Os.mkdir(file.getAbsolutePath(), 0755); 487 Os.chmod(file.getAbsolutePath(), 0755); 488 } catch (ErrnoException e) { 489 // This purposefully throws if directory already exists 490 throw new IllegalStateException("Failed to prepare session dir", e); 491 } 492 493 if (!SELinux.restorecon(file)) { 494 throw new IllegalStateException("Failed to restorecon session dir"); 495 } 496 497 return file; 498 } 499 500 @Override 501 public InstallSessionInfo getSessionInfo(int sessionId) { 502 synchronized (mSessions) { 503 final PackageInstallerSession session = mSessions.get(sessionId); 504 if (!isCallingUidOwner(session)) { 505 enforceCallerCanReadSessions(); 506 } 507 return session != null ? session.generateInfo() : null; 508 } 509 } 510 511 @Override 512 public List<InstallSessionInfo> getAllSessions(int userId) { 513 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getAllSessions"); 514 enforceCallerCanReadSessions(); 515 516 final List<InstallSessionInfo> result = new ArrayList<>(); 517 synchronized (mSessions) { 518 for (int i = 0; i < mSessions.size(); i++) { 519 final PackageInstallerSession session = mSessions.valueAt(i); 520 if (session.userId == userId) { 521 result.add(session.generateInfo()); 522 } 523 } 524 } 525 return result; 526 } 527 528 @Override 529 public List<InstallSessionInfo> getMySessions(String installerPackageName, int userId) { 530 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "getMySessions"); 531 mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName); 532 533 final List<InstallSessionInfo> result = new ArrayList<>(); 534 synchronized (mSessions) { 535 for (int i = 0; i < mSessions.size(); i++) { 536 final PackageInstallerSession session = mSessions.valueAt(i); 537 if (Objects.equals(session.installerPackageName, installerPackageName) 538 && session.userId == userId) { 539 result.add(session.generateInfo()); 540 } 541 } 542 } 543 return result; 544 } 545 546 @Override 547 public void uninstall(String packageName, int flags, IPackageDeleteObserver2 observer, 548 int userId) { 549 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstall"); 550 551 // TODO: enforce installer of record or permission 552 mPm.deletePackage(packageName, observer, userId, flags); 553 } 554 555 @Override 556 public void uninstallSplit(String basePackageName, String overlayName, int flags, 557 IPackageDeleteObserver2 observer, int userId) { 558 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "uninstallSplit"); 559 560 // TODO: flesh out once PM has split support 561 throw new UnsupportedOperationException(); 562 } 563 564 @Override 565 public void setPermissionsResult(int sessionId, boolean accepted) { 566 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, TAG); 567 568 synchronized (mSessions) { 569 mSessions.get(sessionId).setPermissionsResult(accepted); 570 } 571 } 572 573 @Override 574 public void registerCallback(IPackageInstallerCallback callback, int userId) { 575 mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback"); 576 enforceCallerCanReadSessions(); 577 578 mCallbacks.register(callback, userId); 579 } 580 581 @Override 582 public void unregisterCallback(IPackageInstallerCallback callback) { 583 mCallbacks.unregister(callback); 584 } 585 586 private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, 587 int installerUid) { 588 int count = 0; 589 final int size = sessions.size(); 590 for (int i = 0; i < size; i++) { 591 final PackageInstallerSession session = sessions.valueAt(i); 592 if (session.installerUid == installerUid) { 593 count++; 594 } 595 } 596 return count; 597 } 598 599 private boolean isCallingUidOwner(PackageInstallerSession session) { 600 final int callingUid = Binder.getCallingUid(); 601 if (callingUid == Process.ROOT_UID) { 602 return true; 603 } else { 604 return (session != null) && (callingUid == session.installerUid); 605 } 606 } 607 608 /** 609 * We allow those with permission, or the current home app. 610 */ 611 private void enforceCallerCanReadSessions() { 612 final boolean hasPermission = (mContext.checkCallingOrSelfPermission( 613 android.Manifest.permission.READ_INSTALL_SESSIONS) 614 == PackageManager.PERMISSION_GRANTED); 615 final boolean isHomeApp = mPm.checkCallerIsHomeApp(); 616 if (hasPermission || isHomeApp) { 617 return; 618 } else { 619 throw new SecurityException("Caller must be current home app to read install sessions"); 620 } 621 } 622 623 private static class Callbacks extends Handler { 624 private static final int MSG_SESSION_CREATED = 1; 625 private static final int MSG_SESSION_OPENED = 2; 626 private static final int MSG_SESSION_PROGRESS_CHANGED = 3; 627 private static final int MSG_SESSION_CLOSED = 4; 628 private static final int MSG_SESSION_FINISHED = 5; 629 630 private final RemoteCallbackList<IPackageInstallerCallback> 631 mCallbacks = new RemoteCallbackList<>(); 632 633 public Callbacks(Looper looper) { 634 super(looper); 635 } 636 637 public void register(IPackageInstallerCallback callback, int userId) { 638 mCallbacks.register(callback, new UserHandle(userId)); 639 } 640 641 public void unregister(IPackageInstallerCallback callback) { 642 mCallbacks.unregister(callback); 643 } 644 645 @Override 646 public void handleMessage(Message msg) { 647 final int userId = msg.arg2; 648 final int n = mCallbacks.beginBroadcast(); 649 for (int i = 0; i < n; i++) { 650 final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i); 651 final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i); 652 // TODO: dispatch notifications for slave profiles 653 if (userId == user.getIdentifier()) { 654 try { 655 invokeCallback(callback, msg); 656 } catch (RemoteException ignored) { 657 } 658 } 659 } 660 mCallbacks.finishBroadcast(); 661 } 662 663 private void invokeCallback(IPackageInstallerCallback callback, Message msg) 664 throws RemoteException { 665 final int sessionId = msg.arg1; 666 switch (msg.what) { 667 case MSG_SESSION_CREATED: 668 callback.onSessionCreated(sessionId); 669 break; 670 case MSG_SESSION_OPENED: 671 callback.onSessionOpened(sessionId); 672 break; 673 case MSG_SESSION_PROGRESS_CHANGED: 674 callback.onSessionProgressChanged(sessionId, (float) msg.obj); 675 break; 676 case MSG_SESSION_CLOSED: 677 callback.onSessionClosed(sessionId); 678 break; 679 case MSG_SESSION_FINISHED: 680 callback.onSessionFinished(sessionId, (boolean) msg.obj); 681 break; 682 } 683 } 684 685 private void notifySessionCreated(int sessionId, int userId) { 686 obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget(); 687 } 688 689 private void notifySessionOpened(int sessionId, int userId) { 690 obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget(); 691 } 692 693 private void notifySessionProgressChanged(int sessionId, int userId, float progress) { 694 obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget(); 695 } 696 697 private void notifySessionClosed(int sessionId, int userId) { 698 obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget(); 699 } 700 701 public void notifySessionFinished(int sessionId, int userId, boolean success) { 702 obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget(); 703 } 704 } 705 706 void dump(IndentingPrintWriter pw) { 707 synchronized (mSessions) { 708 pw.println("Active install sessions:"); 709 pw.increaseIndent(); 710 int N = mSessions.size(); 711 for (int i = 0; i < N; i++) { 712 final PackageInstallerSession session = mSessions.valueAt(i); 713 session.dump(pw); 714 pw.println(); 715 } 716 pw.println(); 717 pw.decreaseIndent(); 718 719 pw.println("Historical install sessions:"); 720 pw.increaseIndent(); 721 N = mHistoricalSessions.size(); 722 for (int i = 0; i < N; i++) { 723 final PackageInstallerSession session = mHistoricalSessions.valueAt(i); 724 session.dump(pw); 725 pw.println(); 726 } 727 pw.println(); 728 pw.decreaseIndent(); 729 } 730 } 731 732 class InternalCallback { 733 public void onSessionProgressChanged(PackageInstallerSession session, float progress) { 734 mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress); 735 } 736 737 public void onSessionClosed(PackageInstallerSession session) { 738 mCallbacks.notifySessionClosed(session.sessionId, session.userId); 739 } 740 741 public void onSessionFinished(PackageInstallerSession session, boolean success) { 742 mCallbacks.notifySessionFinished(session.sessionId, session.userId, success); 743 synchronized (mSessions) { 744 mSessions.remove(session.sessionId); 745 mHistoricalSessions.put(session.sessionId, session); 746 } 747 writeSessionsAsync(); 748 } 749 } 750} 751