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