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 android.content.pm; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SdkConstant; 22 import android.annotation.SdkConstant.SdkConstantType; 23 import android.app.ActivityManager; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentSender; 27 import android.graphics.Bitmap; 28 import android.net.Uri; 29 import android.os.FileBridge; 30 import android.os.Handler; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.Parcel; 34 import android.os.ParcelFileDescriptor; 35 import android.os.Parcelable; 36 import android.os.RemoteException; 37 import android.util.ExceptionUtils; 38 import android.util.Log; 39 40 import com.android.internal.util.IndentingPrintWriter; 41 42 import java.io.Closeable; 43 import java.io.IOException; 44 import java.io.InputStream; 45 import java.io.OutputStream; 46 import java.security.MessageDigest; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.Iterator; 50 import java.util.List; 51 52 /** 53 * Offers the ability to install, upgrade, and remove applications on the 54 * device. This includes support for apps packaged either as a single 55 * "monolithic" APK, or apps packaged as multiple "split" APKs. 56 * <p> 57 * An app is delivered for installation through a 58 * {@link PackageInstaller.Session}, which any app can create. Once the session 59 * is created, the installer can stream one or more APKs into place until it 60 * decides to either commit or destroy the session. Committing may require user 61 * intervention to complete the installation. 62 * <p> 63 * Sessions can install brand new apps, upgrade existing apps, or add new splits 64 * into an existing app. 65 * <p> 66 * Apps packaged as multiple split APKs always consist of a single "base" APK 67 * (with a {@code null} split name) and zero or more "split" APKs (with unique 68 * split names). Any subset of these APKs can be installed together, as long as 69 * the following constraints are met: 70 * <ul> 71 * <li>All APKs must have the exact same package name, version code, and signing 72 * certificates. 73 * <li>All APKs must have unique split names. 74 * <li>All installations must contain a single base APK. 75 * </ul> 76 */ 77 public class PackageInstaller { 78 private static final String TAG = "PackageInstaller"; 79 80 /** 81 * Activity Action: Show details about a particular install session. This 82 * may surface actions such as pause, resume, or cancel. 83 * <p> 84 * This should always be scoped to the installer package that owns the 85 * session. Clients should use {@link SessionInfo#createDetailsIntent()} to 86 * build this intent correctly. 87 * <p> 88 * In some cases, a matching Activity may not exist, so ensure you safeguard 89 * against this. 90 * <p> 91 * The session to show details for is defined in {@link #EXTRA_SESSION_ID}. 92 */ 93 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 94 public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; 95 96 /** {@hide} */ 97 public static final String 98 ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS"; 99 100 /** 101 * An integer session ID that an operation is working with. 102 * 103 * @see Intent#getIntExtra(String, int) 104 */ 105 public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; 106 107 /** 108 * Package name that an operation is working with. 109 * 110 * @see Intent#getStringExtra(String) 111 */ 112 public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; 113 114 /** 115 * Current status of an operation. Will be one of 116 * {@link #STATUS_PENDING_USER_ACTION}, {@link #STATUS_SUCCESS}, 117 * {@link #STATUS_FAILURE}, {@link #STATUS_FAILURE_ABORTED}, 118 * {@link #STATUS_FAILURE_BLOCKED}, {@link #STATUS_FAILURE_CONFLICT}, 119 * {@link #STATUS_FAILURE_INCOMPATIBLE}, {@link #STATUS_FAILURE_INVALID}, or 120 * {@link #STATUS_FAILURE_STORAGE}. 121 * <p> 122 * More information about a status may be available through additional 123 * extras; see the individual status documentation for details. 124 * 125 * @see Intent#getIntExtra(String, int) 126 */ 127 public static final String EXTRA_STATUS = "android.content.pm.extra.STATUS"; 128 129 /** 130 * Detailed string representation of the status, including raw details that 131 * are useful for debugging. 132 * 133 * @see Intent#getStringExtra(String) 134 */ 135 public static final String EXTRA_STATUS_MESSAGE = "android.content.pm.extra.STATUS_MESSAGE"; 136 137 /** 138 * Another package name relevant to a status. This is typically the package 139 * responsible for causing an operation failure. 140 * 141 * @see Intent#getStringExtra(String) 142 */ 143 public static final String 144 EXTRA_OTHER_PACKAGE_NAME = "android.content.pm.extra.OTHER_PACKAGE_NAME"; 145 146 /** 147 * Storage path relevant to a status. 148 * 149 * @see Intent#getStringExtra(String) 150 */ 151 public static final String EXTRA_STORAGE_PATH = "android.content.pm.extra.STORAGE_PATH"; 152 153 /** {@hide} */ 154 @Deprecated 155 public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES"; 156 157 /** {@hide} */ 158 public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; 159 /** {@hide} */ 160 public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE"; 161 /** {@hide} */ 162 public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK"; 163 164 /** 165 * User action is currently required to proceed. You can launch the intent 166 * activity described by {@link Intent#EXTRA_INTENT} to involve the user and 167 * continue. 168 * <p> 169 * You may choose to immediately launch the intent if the user is actively 170 * using your app. Otherwise, you should use a notification to guide the 171 * user back into your app before launching. 172 * 173 * @see Intent#getParcelableExtra(String) 174 */ 175 public static final int STATUS_PENDING_USER_ACTION = -1; 176 177 /** 178 * The operation succeeded. 179 */ 180 public static final int STATUS_SUCCESS = 0; 181 182 /** 183 * The operation failed in a generic way. The system will always try to 184 * provide a more specific failure reason, but in some rare cases this may 185 * be delivered. 186 * 187 * @see #EXTRA_STATUS_MESSAGE 188 */ 189 public static final int STATUS_FAILURE = 1; 190 191 /** 192 * The operation failed because it was blocked. For example, a device policy 193 * may be blocking the operation, a package verifier may have blocked the 194 * operation, or the app may be required for core system operation. 195 * <p> 196 * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the 197 * specific package blocking the install. 198 * 199 * @see #EXTRA_STATUS_MESSAGE 200 * @see #EXTRA_OTHER_PACKAGE_NAME 201 */ 202 public static final int STATUS_FAILURE_BLOCKED = 2; 203 204 /** 205 * The operation failed because it was actively aborted. For example, the 206 * user actively declined requested permissions, or the session was 207 * abandoned. 208 * 209 * @see #EXTRA_STATUS_MESSAGE 210 */ 211 public static final int STATUS_FAILURE_ABORTED = 3; 212 213 /** 214 * The operation failed because one or more of the APKs was invalid. For 215 * example, they might be malformed, corrupt, incorrectly signed, 216 * mismatched, etc. 217 * 218 * @see #EXTRA_STATUS_MESSAGE 219 */ 220 public static final int STATUS_FAILURE_INVALID = 4; 221 222 /** 223 * The operation failed because it conflicts (or is inconsistent with) with 224 * another package already installed on the device. For example, an existing 225 * permission, incompatible certificates, etc. The user may be able to 226 * uninstall another app to fix the issue. 227 * <p> 228 * The result may also contain {@link #EXTRA_OTHER_PACKAGE_NAME} with the 229 * specific package identified as the cause of the conflict. 230 * 231 * @see #EXTRA_STATUS_MESSAGE 232 * @see #EXTRA_OTHER_PACKAGE_NAME 233 */ 234 public static final int STATUS_FAILURE_CONFLICT = 5; 235 236 /** 237 * The operation failed because of storage issues. For example, the device 238 * may be running low on space, or external media may be unavailable. The 239 * user may be able to help free space or insert different external media. 240 * <p> 241 * The result may also contain {@link #EXTRA_STORAGE_PATH} with the path to 242 * the storage device that caused the failure. 243 * 244 * @see #EXTRA_STATUS_MESSAGE 245 * @see #EXTRA_STORAGE_PATH 246 */ 247 public static final int STATUS_FAILURE_STORAGE = 6; 248 249 /** 250 * The operation failed because it is fundamentally incompatible with this 251 * device. For example, the app may require a hardware feature that doesn't 252 * exist, it may be missing native code for the ABIs supported by the 253 * device, or it requires a newer SDK version, etc. 254 * 255 * @see #EXTRA_STATUS_MESSAGE 256 */ 257 public static final int STATUS_FAILURE_INCOMPATIBLE = 7; 258 259 private final Context mContext; 260 private final PackageManager mPm; 261 private final IPackageInstaller mInstaller; 262 private final int mUserId; 263 private final String mInstallerPackageName; 264 265 private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>(); 266 267 /** {@hide} */ PackageInstaller(Context context, PackageManager pm, IPackageInstaller installer, String installerPackageName, int userId)268 public PackageInstaller(Context context, PackageManager pm, IPackageInstaller installer, 269 String installerPackageName, int userId) { 270 mContext = context; 271 mPm = pm; 272 mInstaller = installer; 273 mInstallerPackageName = installerPackageName; 274 mUserId = userId; 275 } 276 277 /** 278 * Create a new session using the given parameters, returning a unique ID 279 * that represents the session. Once created, the session can be opened 280 * multiple times across multiple device boots. 281 * <p> 282 * The system may automatically destroy sessions that have not been 283 * finalized (either committed or abandoned) within a reasonable period of 284 * time, typically on the order of a day. 285 * 286 * @throws IOException if parameters were unsatisfiable, such as lack of 287 * disk space or unavailable media. 288 * @throws SecurityException when installation services are unavailable, 289 * such as when called from a restricted user. 290 * @throws IllegalArgumentException when {@link SessionParams} is invalid. 291 * @return positive, non-zero unique ID that represents the created session. 292 * This ID remains consistent across device reboots until the 293 * session is finalized. IDs are not reused during a given boot. 294 */ createSession(@onNull SessionParams params)295 public int createSession(@NonNull SessionParams params) throws IOException { 296 try { 297 return mInstaller.createSession(params, mInstallerPackageName, mUserId); 298 } catch (RuntimeException e) { 299 ExceptionUtils.maybeUnwrapIOException(e); 300 throw e; 301 } catch (RemoteException e) { 302 throw e.rethrowAsRuntimeException(); 303 } 304 } 305 306 /** 307 * Open an existing session to actively perform work. To succeed, the caller 308 * must be the owner of the install session. 309 * 310 * @throws IOException if parameters were unsatisfiable, such as lack of 311 * disk space or unavailable media. 312 * @throws SecurityException when the caller does not own the session, or 313 * the session is invalid. 314 */ openSession(int sessionId)315 public @NonNull Session openSession(int sessionId) throws IOException { 316 try { 317 return new Session(mInstaller.openSession(sessionId)); 318 } catch (RuntimeException e) { 319 ExceptionUtils.maybeUnwrapIOException(e); 320 throw e; 321 } catch (RemoteException e) { 322 throw e.rethrowAsRuntimeException(); 323 } 324 } 325 326 /** 327 * Update the icon representing the app being installed in a specific 328 * session. This should be roughly 329 * {@link ActivityManager#getLauncherLargeIconSize()} in both dimensions. 330 * 331 * @throws SecurityException when the caller does not own the session, or 332 * the session is invalid. 333 */ updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon)334 public void updateSessionAppIcon(int sessionId, @Nullable Bitmap appIcon) { 335 try { 336 mInstaller.updateSessionAppIcon(sessionId, appIcon); 337 } catch (RemoteException e) { 338 throw e.rethrowAsRuntimeException(); 339 } 340 } 341 342 /** 343 * Update the label representing the app being installed in a specific 344 * session. 345 * 346 * @throws SecurityException when the caller does not own the session, or 347 * the session is invalid. 348 */ updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel)349 public void updateSessionAppLabel(int sessionId, @Nullable CharSequence appLabel) { 350 try { 351 final String val = (appLabel != null) ? appLabel.toString() : null; 352 mInstaller.updateSessionAppLabel(sessionId, val); 353 } catch (RemoteException e) { 354 throw e.rethrowAsRuntimeException(); 355 } 356 } 357 358 /** 359 * Completely abandon the given session, destroying all staged data and 360 * rendering it invalid. Abandoned sessions will be reported to 361 * {@link SessionCallback} listeners as failures. This is equivalent to 362 * opening the session and calling {@link Session#abandon()}. 363 * 364 * @throws SecurityException when the caller does not own the session, or 365 * the session is invalid. 366 */ abandonSession(int sessionId)367 public void abandonSession(int sessionId) { 368 try { 369 mInstaller.abandonSession(sessionId); 370 } catch (RemoteException e) { 371 throw e.rethrowAsRuntimeException(); 372 } 373 } 374 375 /** 376 * Return details for a specific session. No special permissions are 377 * required to retrieve these details. 378 * 379 * @return details for the requested session, or {@code null} if the session 380 * does not exist. 381 */ getSessionInfo(int sessionId)382 public @Nullable SessionInfo getSessionInfo(int sessionId) { 383 try { 384 return mInstaller.getSessionInfo(sessionId); 385 } catch (RemoteException e) { 386 throw e.rethrowAsRuntimeException(); 387 } 388 } 389 390 /** 391 * Return list of all known install sessions, regardless of the installer. 392 */ getAllSessions()393 public @NonNull List<SessionInfo> getAllSessions() { 394 final ApplicationInfo info = mContext.getApplicationInfo(); 395 if ("com.google.android.googlequicksearchbox".equals(info.packageName) 396 && info.versionCode <= 300400110) { 397 Log.d(TAG, "Ignoring callback request from old prebuilt"); 398 return Collections.EMPTY_LIST; 399 } 400 401 try { 402 return mInstaller.getAllSessions(mUserId).getList(); 403 } catch (RemoteException e) { 404 throw e.rethrowAsRuntimeException(); 405 } 406 } 407 408 /** 409 * Return list of all known install sessions owned by the calling app. 410 */ getMySessions()411 public @NonNull List<SessionInfo> getMySessions() { 412 try { 413 return mInstaller.getMySessions(mInstallerPackageName, mUserId).getList(); 414 } catch (RemoteException e) { 415 throw e.rethrowAsRuntimeException(); 416 } 417 } 418 419 /** 420 * Uninstall the given package, removing it completely from the device. This 421 * method is only available to the current "installer of record" for the 422 * package. 423 */ uninstall(@onNull String packageName, @NonNull IntentSender statusReceiver)424 public void uninstall(@NonNull String packageName, @NonNull IntentSender statusReceiver) { 425 try { 426 mInstaller.uninstall(packageName, 0, statusReceiver, mUserId); 427 } catch (RemoteException e) { 428 throw e.rethrowAsRuntimeException(); 429 } 430 } 431 432 /** {@hide} */ setPermissionsResult(int sessionId, boolean accepted)433 public void setPermissionsResult(int sessionId, boolean accepted) { 434 try { 435 mInstaller.setPermissionsResult(sessionId, accepted); 436 } catch (RemoteException e) { 437 throw e.rethrowAsRuntimeException(); 438 } 439 } 440 441 /** 442 * Events for observing session lifecycle. 443 * <p> 444 * A typical session lifecycle looks like this: 445 * <ul> 446 * <li>An installer creates a session to indicate pending app delivery. All 447 * install details are available at this point. 448 * <li>The installer opens the session to deliver APK data. Note that a 449 * session may be opened and closed multiple times as network connectivity 450 * changes. The installer may deliver periodic progress updates. 451 * <li>The installer commits or abandons the session, resulting in the 452 * session being finished. 453 * </ul> 454 */ 455 public static abstract class SessionCallback { 456 /** 457 * New session has been created. Details about the session can be 458 * obtained from {@link PackageInstaller#getSessionInfo(int)}. 459 */ onCreated(int sessionId)460 public abstract void onCreated(int sessionId); 461 462 /** 463 * Badging details for an existing session has changed. For example, the 464 * app icon or label has been updated. 465 */ onBadgingChanged(int sessionId)466 public abstract void onBadgingChanged(int sessionId); 467 468 /** 469 * Active state for session has been changed. 470 * <p> 471 * A session is considered active whenever there is ongoing forward 472 * progress being made, such as the installer holding an open 473 * {@link Session} instance while streaming data into place, or the 474 * system optimizing code as the result of 475 * {@link Session#commit(IntentSender)}. 476 * <p> 477 * If the installer closes the {@link Session} without committing, the 478 * session is considered inactive until the installer opens the session 479 * again. 480 */ onActiveChanged(int sessionId, boolean active)481 public abstract void onActiveChanged(int sessionId, boolean active); 482 483 /** 484 * Progress for given session has been updated. 485 * <p> 486 * Note that this progress may not directly correspond to the value 487 * reported by 488 * {@link PackageInstaller.Session#setStagingProgress(float)}, as the 489 * system may carve out a portion of the overall progress to represent 490 * its own internal installation work. 491 */ onProgressChanged(int sessionId, float progress)492 public abstract void onProgressChanged(int sessionId, float progress); 493 494 /** 495 * Session has completely finished, either with success or failure. 496 */ onFinished(int sessionId, boolean success)497 public abstract void onFinished(int sessionId, boolean success); 498 } 499 500 /** {@hide} */ 501 private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements 502 Handler.Callback { 503 private static final int MSG_SESSION_CREATED = 1; 504 private static final int MSG_SESSION_BADGING_CHANGED = 2; 505 private static final int MSG_SESSION_ACTIVE_CHANGED = 3; 506 private static final int MSG_SESSION_PROGRESS_CHANGED = 4; 507 private static final int MSG_SESSION_FINISHED = 5; 508 509 final SessionCallback mCallback; 510 final Handler mHandler; 511 SessionCallbackDelegate(SessionCallback callback, Looper looper)512 public SessionCallbackDelegate(SessionCallback callback, Looper looper) { 513 mCallback = callback; 514 mHandler = new Handler(looper, this); 515 } 516 517 @Override handleMessage(Message msg)518 public boolean handleMessage(Message msg) { 519 final int sessionId = msg.arg1; 520 switch (msg.what) { 521 case MSG_SESSION_CREATED: 522 mCallback.onCreated(sessionId); 523 return true; 524 case MSG_SESSION_BADGING_CHANGED: 525 mCallback.onBadgingChanged(sessionId); 526 return true; 527 case MSG_SESSION_ACTIVE_CHANGED: 528 final boolean active = msg.arg2 != 0; 529 mCallback.onActiveChanged(sessionId, active); 530 return true; 531 case MSG_SESSION_PROGRESS_CHANGED: 532 mCallback.onProgressChanged(sessionId, (float) msg.obj); 533 return true; 534 case MSG_SESSION_FINISHED: 535 mCallback.onFinished(sessionId, msg.arg2 != 0); 536 return true; 537 } 538 return false; 539 } 540 541 @Override onSessionCreated(int sessionId)542 public void onSessionCreated(int sessionId) { 543 mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget(); 544 } 545 546 @Override onSessionBadgingChanged(int sessionId)547 public void onSessionBadgingChanged(int sessionId) { 548 mHandler.obtainMessage(MSG_SESSION_BADGING_CHANGED, sessionId, 0).sendToTarget(); 549 } 550 551 @Override onSessionActiveChanged(int sessionId, boolean active)552 public void onSessionActiveChanged(int sessionId, boolean active) { 553 mHandler.obtainMessage(MSG_SESSION_ACTIVE_CHANGED, sessionId, active ? 1 : 0) 554 .sendToTarget(); 555 } 556 557 @Override onSessionProgressChanged(int sessionId, float progress)558 public void onSessionProgressChanged(int sessionId, float progress) { 559 mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress) 560 .sendToTarget(); 561 } 562 563 @Override onSessionFinished(int sessionId, boolean success)564 public void onSessionFinished(int sessionId, boolean success) { 565 mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0) 566 .sendToTarget(); 567 } 568 } 569 570 /** {@hide} */ 571 @Deprecated addSessionCallback(@onNull SessionCallback callback)572 public void addSessionCallback(@NonNull SessionCallback callback) { 573 registerSessionCallback(callback); 574 } 575 576 /** 577 * Register to watch for session lifecycle events. No special permissions 578 * are required to watch for these events. 579 */ registerSessionCallback(@onNull SessionCallback callback)580 public void registerSessionCallback(@NonNull SessionCallback callback) { 581 registerSessionCallback(callback, new Handler()); 582 } 583 584 /** {@hide} */ 585 @Deprecated addSessionCallback(@onNull SessionCallback callback, @NonNull Handler handler)586 public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { 587 registerSessionCallback(callback, handler); 588 } 589 590 /** 591 * Register to watch for session lifecycle events. No special permissions 592 * are required to watch for these events. 593 * 594 * @param handler to dispatch callback events through, otherwise uses 595 * calling thread. 596 */ registerSessionCallback(@onNull SessionCallback callback, @NonNull Handler handler)597 public void registerSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { 598 // TODO: remove this temporary guard once we have new prebuilts 599 final ApplicationInfo info = mContext.getApplicationInfo(); 600 if ("com.google.android.googlequicksearchbox".equals(info.packageName) 601 && info.versionCode <= 300400110) { 602 Log.d(TAG, "Ignoring callback request from old prebuilt"); 603 return; 604 } 605 606 synchronized (mDelegates) { 607 final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback, 608 handler.getLooper()); 609 try { 610 mInstaller.registerCallback(delegate, mUserId); 611 } catch (RemoteException e) { 612 throw e.rethrowAsRuntimeException(); 613 } 614 mDelegates.add(delegate); 615 } 616 } 617 618 /** {@hide} */ 619 @Deprecated removeSessionCallback(@onNull SessionCallback callback)620 public void removeSessionCallback(@NonNull SessionCallback callback) { 621 unregisterSessionCallback(callback); 622 } 623 624 /** 625 * Unregister a previously registered callback. 626 */ unregisterSessionCallback(@onNull SessionCallback callback)627 public void unregisterSessionCallback(@NonNull SessionCallback callback) { 628 synchronized (mDelegates) { 629 for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) { 630 final SessionCallbackDelegate delegate = i.next(); 631 if (delegate.mCallback == callback) { 632 try { 633 mInstaller.unregisterCallback(delegate); 634 } catch (RemoteException e) { 635 throw e.rethrowAsRuntimeException(); 636 } 637 i.remove(); 638 } 639 } 640 } 641 } 642 643 /** 644 * An installation that is being actively staged. For an install to succeed, 645 * all existing and new packages must have identical package names, version 646 * codes, and signing certificates. 647 * <p> 648 * A session may contain any number of split packages. If the application 649 * does not yet exist, this session must include a base package. 650 * <p> 651 * If an APK included in this session is already defined by the existing 652 * installation (for example, the same split name), the APK in this session 653 * will replace the existing APK. 654 */ 655 public static class Session implements Closeable { 656 private IPackageInstallerSession mSession; 657 658 /** {@hide} */ Session(IPackageInstallerSession session)659 public Session(IPackageInstallerSession session) { 660 mSession = session; 661 } 662 663 /** {@hide} */ 664 @Deprecated setProgress(float progress)665 public void setProgress(float progress) { 666 setStagingProgress(progress); 667 } 668 669 /** 670 * Set current progress of staging this session. Valid values are 671 * anywhere between 0 and 1. 672 * <p> 673 * Note that this progress may not directly correspond to the value 674 * reported by {@link SessionCallback#onProgressChanged(int, float)}, as 675 * the system may carve out a portion of the overall progress to 676 * represent its own internal installation work. 677 */ setStagingProgress(float progress)678 public void setStagingProgress(float progress) { 679 try { 680 mSession.setClientProgress(progress); 681 } catch (RemoteException e) { 682 throw e.rethrowAsRuntimeException(); 683 } 684 } 685 686 /** {@hide} */ addProgress(float progress)687 public void addProgress(float progress) { 688 try { 689 mSession.addClientProgress(progress); 690 } catch (RemoteException e) { 691 throw e.rethrowAsRuntimeException(); 692 } 693 } 694 695 /** 696 * Open a stream to write an APK file into the session. 697 * <p> 698 * The returned stream will start writing data at the requested offset 699 * in the underlying file, which can be used to resume a partially 700 * written file. If a valid file length is specified, the system will 701 * preallocate the underlying disk space to optimize placement on disk. 702 * It's strongly recommended to provide a valid file length when known. 703 * <p> 704 * You can write data into the returned stream, optionally call 705 * {@link #fsync(OutputStream)} as needed to ensure bytes have been 706 * persisted to disk, and then close when finished. All streams must be 707 * closed before calling {@link #commit(IntentSender)}. 708 * 709 * @param name arbitrary, unique name of your choosing to identify the 710 * APK being written. You can open a file again for 711 * additional writes (such as after a reboot) by using the 712 * same name. This name is only meaningful within the context 713 * of a single install session. 714 * @param offsetBytes offset into the file to begin writing at, or 0 to 715 * start at the beginning of the file. 716 * @param lengthBytes total size of the file being written, used to 717 * preallocate the underlying disk space, or -1 if unknown. 718 * The system may clear various caches as needed to allocate 719 * this space. 720 * @throws IOException if trouble opening the file for writing, such as 721 * lack of disk space or unavailable media. 722 * @throws SecurityException if called after the session has been 723 * committed or abandoned. 724 */ openWrite(@onNull String name, long offsetBytes, long lengthBytes)725 public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, 726 long lengthBytes) throws IOException { 727 try { 728 final ParcelFileDescriptor clientSocket = mSession.openWrite(name, 729 offsetBytes, lengthBytes); 730 return new FileBridge.FileBridgeOutputStream(clientSocket); 731 } catch (RuntimeException e) { 732 ExceptionUtils.maybeUnwrapIOException(e); 733 throw e; 734 } catch (RemoteException e) { 735 throw e.rethrowAsRuntimeException(); 736 } 737 } 738 739 /** 740 * Ensure that any outstanding data for given stream has been committed 741 * to disk. This is only valid for streams returned from 742 * {@link #openWrite(String, long, long)}. 743 */ fsync(@onNull OutputStream out)744 public void fsync(@NonNull OutputStream out) throws IOException { 745 if (out instanceof FileBridge.FileBridgeOutputStream) { 746 ((FileBridge.FileBridgeOutputStream) out).fsync(); 747 } else { 748 throw new IllegalArgumentException("Unrecognized stream"); 749 } 750 } 751 752 /** 753 * Return all APK names contained in this session. 754 * <p> 755 * This returns all names which have been previously written through 756 * {@link #openWrite(String, long, long)} as part of this session. 757 * 758 * @throws SecurityException if called after the session has been 759 * committed or abandoned. 760 */ getNames()761 public @NonNull String[] getNames() throws IOException { 762 try { 763 return mSession.getNames(); 764 } catch (RuntimeException e) { 765 ExceptionUtils.maybeUnwrapIOException(e); 766 throw e; 767 } catch (RemoteException e) { 768 throw e.rethrowAsRuntimeException(); 769 } 770 } 771 772 /** 773 * Open a stream to read an APK file from the session. 774 * <p> 775 * This is only valid for names which have been previously written 776 * through {@link #openWrite(String, long, long)} as part of this 777 * session. For example, this stream may be used to calculate a 778 * {@link MessageDigest} of a written APK before committing. 779 * 780 * @throws SecurityException if called after the session has been 781 * committed or abandoned. 782 */ openRead(@onNull String name)783 public @NonNull InputStream openRead(@NonNull String name) throws IOException { 784 try { 785 final ParcelFileDescriptor pfd = mSession.openRead(name); 786 return new ParcelFileDescriptor.AutoCloseInputStream(pfd); 787 } catch (RuntimeException e) { 788 ExceptionUtils.maybeUnwrapIOException(e); 789 throw e; 790 } catch (RemoteException e) { 791 throw e.rethrowAsRuntimeException(); 792 } 793 } 794 795 /** 796 * Attempt to commit everything staged in this session. This may require 797 * user intervention, and so it may not happen immediately. The final 798 * result of the commit will be reported through the given callback. 799 * <p> 800 * Once this method is called, no additional mutations may be performed 801 * on the session. If the device reboots before the session has been 802 * finalized, you may commit the session again. 803 * 804 * @throws SecurityException if streams opened through 805 * {@link #openWrite(String, long, long)} are still open. 806 */ commit(@onNull IntentSender statusReceiver)807 public void commit(@NonNull IntentSender statusReceiver) { 808 try { 809 mSession.commit(statusReceiver); 810 } catch (RemoteException e) { 811 throw e.rethrowAsRuntimeException(); 812 } 813 } 814 815 /** 816 * Release this session object. You can open the session again if it 817 * hasn't been finalized. 818 */ 819 @Override close()820 public void close() { 821 try { 822 mSession.close(); 823 } catch (RemoteException e) { 824 throw e.rethrowAsRuntimeException(); 825 } 826 } 827 828 /** 829 * Completely abandon this session, destroying all staged data and 830 * rendering it invalid. Abandoned sessions will be reported to 831 * {@link SessionCallback} listeners as failures. This is equivalent to 832 * opening the session and calling {@link Session#abandon()}. 833 */ abandon()834 public void abandon() { 835 try { 836 mSession.abandon(); 837 } catch (RemoteException e) { 838 throw e.rethrowAsRuntimeException(); 839 } 840 } 841 } 842 843 /** 844 * Parameters for creating a new {@link PackageInstaller.Session}. 845 */ 846 public static class SessionParams implements Parcelable { 847 848 /** {@hide} */ 849 public static final int MODE_INVALID = -1; 850 851 /** 852 * Mode for an install session whose staged APKs should fully replace any 853 * existing APKs for the target app. 854 */ 855 public static final int MODE_FULL_INSTALL = 1; 856 857 /** 858 * Mode for an install session that should inherit any existing APKs for the 859 * target app, unless they have been explicitly overridden (based on split 860 * name) by the session. For example, this can be used to add one or more 861 * split APKs to an existing installation. 862 * <p> 863 * If there are no existing APKs for the target app, this behaves like 864 * {@link #MODE_FULL_INSTALL}. 865 */ 866 public static final int MODE_INHERIT_EXISTING = 2; 867 868 /** {@hide} */ 869 public int mode = MODE_INVALID; 870 /** {@hide} */ 871 public int installFlags; 872 /** {@hide} */ 873 public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; 874 /** {@hide} */ 875 public long sizeBytes = -1; 876 /** {@hide} */ 877 public String appPackageName; 878 /** {@hide} */ 879 public Bitmap appIcon; 880 /** {@hide} */ 881 public String appLabel; 882 /** {@hide} */ 883 public long appIconLastModified = -1; 884 /** {@hide} */ 885 public Uri originatingUri; 886 /** {@hide} */ 887 public Uri referrerUri; 888 /** {@hide} */ 889 public String abiOverride; 890 891 /** 892 * Construct parameters for a new package install session. 893 * 894 * @param mode one of {@link #MODE_FULL_INSTALL} or 895 * {@link #MODE_INHERIT_EXISTING} describing how the session 896 * should interact with an existing app. 897 */ SessionParams(int mode)898 public SessionParams(int mode) { 899 this.mode = mode; 900 } 901 902 /** {@hide} */ SessionParams(Parcel source)903 public SessionParams(Parcel source) { 904 mode = source.readInt(); 905 installFlags = source.readInt(); 906 installLocation = source.readInt(); 907 sizeBytes = source.readLong(); 908 appPackageName = source.readString(); 909 appIcon = source.readParcelable(null); 910 appLabel = source.readString(); 911 originatingUri = source.readParcelable(null); 912 referrerUri = source.readParcelable(null); 913 abiOverride = source.readString(); 914 } 915 916 /** 917 * Provide value of {@link PackageInfo#installLocation}, which may be used 918 * to determine where the app will be staged. Defaults to 919 * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}. 920 */ setInstallLocation(int installLocation)921 public void setInstallLocation(int installLocation) { 922 this.installLocation = installLocation; 923 } 924 925 /** 926 * Optionally indicate the total size (in bytes) of all APKs that will be 927 * delivered in this session. The system may use this to ensure enough disk 928 * space exists before proceeding, or to estimate container size for 929 * installations living on external storage. 930 * 931 * @see PackageInfo#INSTALL_LOCATION_AUTO 932 * @see PackageInfo#INSTALL_LOCATION_PREFER_EXTERNAL 933 */ setSize(long sizeBytes)934 public void setSize(long sizeBytes) { 935 this.sizeBytes = sizeBytes; 936 } 937 938 /** 939 * Optionally set the package name of the app being installed. It's strongly 940 * recommended that you provide this value when known, so that observers can 941 * communicate installing apps to users. 942 * <p> 943 * If the APKs staged in the session aren't consistent with this package 944 * name, the install will fail. Regardless of this value, all APKs in the 945 * app must have the same package name. 946 */ setAppPackageName(@ullable String appPackageName)947 public void setAppPackageName(@Nullable String appPackageName) { 948 this.appPackageName = appPackageName; 949 } 950 951 /** 952 * Optionally set an icon representing the app being installed. This should 953 * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both 954 * dimensions. 955 */ setAppIcon(@ullable Bitmap appIcon)956 public void setAppIcon(@Nullable Bitmap appIcon) { 957 this.appIcon = appIcon; 958 } 959 960 /** 961 * Optionally set a label representing the app being installed. 962 */ setAppLabel(@ullable CharSequence appLabel)963 public void setAppLabel(@Nullable CharSequence appLabel) { 964 this.appLabel = (appLabel != null) ? appLabel.toString() : null; 965 } 966 967 /** 968 * Optionally set the URI where this package was downloaded from. Used for 969 * verification purposes. 970 * 971 * @see Intent#EXTRA_ORIGINATING_URI 972 */ setOriginatingUri(@ullable Uri originatingUri)973 public void setOriginatingUri(@Nullable Uri originatingUri) { 974 this.originatingUri = originatingUri; 975 } 976 977 /** 978 * Optionally set the URI that referred you to install this package. Used 979 * for verification purposes. 980 * 981 * @see Intent#EXTRA_REFERRER 982 */ setReferrerUri(@ullable Uri referrerUri)983 public void setReferrerUri(@Nullable Uri referrerUri) { 984 this.referrerUri = referrerUri; 985 } 986 987 /** {@hide} */ setInstallFlagsInternal()988 public void setInstallFlagsInternal() { 989 installFlags |= PackageManager.INSTALL_INTERNAL; 990 installFlags &= ~PackageManager.INSTALL_EXTERNAL; 991 } 992 993 /** {@hide} */ setInstallFlagsExternal()994 public void setInstallFlagsExternal() { 995 installFlags |= PackageManager.INSTALL_EXTERNAL; 996 installFlags &= ~PackageManager.INSTALL_INTERNAL; 997 } 998 999 /** {@hide} */ dump(IndentingPrintWriter pw)1000 public void dump(IndentingPrintWriter pw) { 1001 pw.printPair("mode", mode); 1002 pw.printHexPair("installFlags", installFlags); 1003 pw.printPair("installLocation", installLocation); 1004 pw.printPair("sizeBytes", sizeBytes); 1005 pw.printPair("appPackageName", appPackageName); 1006 pw.printPair("appIcon", (appIcon != null)); 1007 pw.printPair("appLabel", appLabel); 1008 pw.printPair("originatingUri", originatingUri); 1009 pw.printPair("referrerUri", referrerUri); 1010 pw.printPair("abiOverride", abiOverride); 1011 pw.println(); 1012 } 1013 1014 @Override describeContents()1015 public int describeContents() { 1016 return 0; 1017 } 1018 1019 @Override writeToParcel(Parcel dest, int flags)1020 public void writeToParcel(Parcel dest, int flags) { 1021 dest.writeInt(mode); 1022 dest.writeInt(installFlags); 1023 dest.writeInt(installLocation); 1024 dest.writeLong(sizeBytes); 1025 dest.writeString(appPackageName); 1026 dest.writeParcelable(appIcon, flags); 1027 dest.writeString(appLabel); 1028 dest.writeParcelable(originatingUri, flags); 1029 dest.writeParcelable(referrerUri, flags); 1030 dest.writeString(abiOverride); 1031 } 1032 1033 public static final Parcelable.Creator<SessionParams> 1034 CREATOR = new Parcelable.Creator<SessionParams>() { 1035 @Override 1036 public SessionParams createFromParcel(Parcel p) { 1037 return new SessionParams(p); 1038 } 1039 1040 @Override 1041 public SessionParams[] newArray(int size) { 1042 return new SessionParams[size]; 1043 } 1044 }; 1045 } 1046 1047 /** 1048 * Details for an active install session. 1049 */ 1050 public static class SessionInfo implements Parcelable { 1051 1052 /** {@hide} */ 1053 public int sessionId; 1054 /** {@hide} */ 1055 public String installerPackageName; 1056 /** {@hide} */ 1057 public String resolvedBaseCodePath; 1058 /** {@hide} */ 1059 public float progress; 1060 /** {@hide} */ 1061 public boolean sealed; 1062 /** {@hide} */ 1063 public boolean active; 1064 1065 /** {@hide} */ 1066 public int mode; 1067 /** {@hide} */ 1068 public long sizeBytes; 1069 /** {@hide} */ 1070 public String appPackageName; 1071 /** {@hide} */ 1072 public Bitmap appIcon; 1073 /** {@hide} */ 1074 public CharSequence appLabel; 1075 1076 /** {@hide} */ SessionInfo()1077 public SessionInfo() { 1078 } 1079 1080 /** {@hide} */ SessionInfo(Parcel source)1081 public SessionInfo(Parcel source) { 1082 sessionId = source.readInt(); 1083 installerPackageName = source.readString(); 1084 resolvedBaseCodePath = source.readString(); 1085 progress = source.readFloat(); 1086 sealed = source.readInt() != 0; 1087 active = source.readInt() != 0; 1088 1089 mode = source.readInt(); 1090 sizeBytes = source.readLong(); 1091 appPackageName = source.readString(); 1092 appIcon = source.readParcelable(null); 1093 appLabel = source.readString(); 1094 } 1095 1096 /** 1097 * Return the ID for this session. 1098 */ getSessionId()1099 public int getSessionId() { 1100 return sessionId; 1101 } 1102 1103 /** 1104 * Return the package name of the app that owns this session. 1105 */ getInstallerPackageName()1106 public @Nullable String getInstallerPackageName() { 1107 return installerPackageName; 1108 } 1109 1110 /** 1111 * Return current overall progress of this session, between 0 and 1. 1112 * <p> 1113 * Note that this progress may not directly correspond to the value 1114 * reported by 1115 * {@link PackageInstaller.Session#setStagingProgress(float)}, as the 1116 * system may carve out a portion of the overall progress to represent 1117 * its own internal installation work. 1118 */ getProgress()1119 public float getProgress() { 1120 return progress; 1121 } 1122 1123 /** 1124 * Return if this session is currently active. 1125 * <p> 1126 * A session is considered active whenever there is ongoing forward 1127 * progress being made, such as the installer holding an open 1128 * {@link Session} instance while streaming data into place, or the 1129 * system optimizing code as the result of 1130 * {@link Session#commit(IntentSender)}. 1131 * <p> 1132 * If the installer closes the {@link Session} without committing, the 1133 * session is considered inactive until the installer opens the session 1134 * again. 1135 */ isActive()1136 public boolean isActive() { 1137 return active; 1138 } 1139 1140 /** {@hide} */ 1141 @Deprecated isOpen()1142 public boolean isOpen() { 1143 return isActive(); 1144 } 1145 1146 /** 1147 * Return the package name this session is working with. May be {@code null} 1148 * if unknown. 1149 */ getAppPackageName()1150 public @Nullable String getAppPackageName() { 1151 return appPackageName; 1152 } 1153 1154 /** 1155 * Return an icon representing the app being installed. May be {@code null} 1156 * if unavailable. 1157 */ getAppIcon()1158 public @Nullable Bitmap getAppIcon() { 1159 return appIcon; 1160 } 1161 1162 /** 1163 * Return a label representing the app being installed. May be {@code null} 1164 * if unavailable. 1165 */ getAppLabel()1166 public @Nullable CharSequence getAppLabel() { 1167 return appLabel; 1168 } 1169 1170 /** 1171 * Return an Intent that can be started to view details about this install 1172 * session. This may surface actions such as pause, resume, or cancel. 1173 * <p> 1174 * In some cases, a matching Activity may not exist, so ensure you safeguard 1175 * against this. 1176 * 1177 * @see PackageInstaller#ACTION_SESSION_DETAILS 1178 */ createDetailsIntent()1179 public @Nullable Intent createDetailsIntent() { 1180 final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS); 1181 intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); 1182 intent.setPackage(installerPackageName); 1183 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1184 return intent; 1185 } 1186 1187 /** {@hide} */ 1188 @Deprecated getDetailsIntent()1189 public @Nullable Intent getDetailsIntent() { 1190 return createDetailsIntent(); 1191 } 1192 1193 @Override describeContents()1194 public int describeContents() { 1195 return 0; 1196 } 1197 1198 @Override writeToParcel(Parcel dest, int flags)1199 public void writeToParcel(Parcel dest, int flags) { 1200 dest.writeInt(sessionId); 1201 dest.writeString(installerPackageName); 1202 dest.writeString(resolvedBaseCodePath); 1203 dest.writeFloat(progress); 1204 dest.writeInt(sealed ? 1 : 0); 1205 dest.writeInt(active ? 1 : 0); 1206 1207 dest.writeInt(mode); 1208 dest.writeLong(sizeBytes); 1209 dest.writeString(appPackageName); 1210 dest.writeParcelable(appIcon, flags); 1211 dest.writeString(appLabel != null ? appLabel.toString() : null); 1212 } 1213 1214 public static final Parcelable.Creator<SessionInfo> 1215 CREATOR = new Parcelable.Creator<SessionInfo>() { 1216 @Override 1217 public SessionInfo createFromParcel(Parcel p) { 1218 return new SessionInfo(p); 1219 } 1220 1221 @Override 1222 public SessionInfo[] newArray(int size) { 1223 return new SessionInfo[size]; 1224 } 1225 }; 1226 } 1227 } 1228