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