PackageInstaller.java revision 7328a1b39b3dae1c0cd390c0a3695c6a46b8e9d8
1/* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.content.pm; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.annotation.SdkConstant; 22import android.annotation.SdkConstant.SdkConstantType; 23import android.app.PackageDeleteObserver; 24import android.app.PackageInstallObserver; 25import android.content.Intent; 26import android.os.Bundle; 27import android.os.FileBridge; 28import android.os.Handler; 29import android.os.Looper; 30import android.os.Message; 31import android.os.ParcelFileDescriptor; 32import android.os.RemoteException; 33import android.util.ExceptionUtils; 34 35import java.io.Closeable; 36import java.io.IOException; 37import java.io.InputStream; 38import java.io.OutputStream; 39import java.security.MessageDigest; 40import java.util.ArrayList; 41import java.util.Iterator; 42import java.util.List; 43 44/** 45 * Offers the ability to install, upgrade, and remove applications on the 46 * device. This includes support for apps packaged either as a single 47 * "monolithic" APK, or apps packaged as multiple "split" APKs. 48 * <p> 49 * An app is delivered for installation through a 50 * {@link PackageInstaller.Session}, which any app can create. Once the session 51 * is created, the installer can stream one or more APKs into place until it 52 * decides to either commit or destroy the session. Committing may require user 53 * intervention to complete the installation. 54 * <p> 55 * Sessions can install brand new apps, upgrade existing apps, or add new splits 56 * into an existing app. 57 * <p> 58 * Apps packaged as multiple split APKs always consist of a single "base" APK 59 * (with a {@code null} split name) and zero or more "split" APKs (with unique 60 * split names). Any subset of these APKs can be installed together, as long as 61 * the following constraints are met: 62 * <ul> 63 * <li>All APKs must have the exact same package name, version code, and signing 64 * certificates. 65 * <li>All APKs must have unique split names. 66 * <li>All installations must contain a single base APK. 67 * </ul> 68 */ 69public class PackageInstaller { 70 /** 71 * Activity Action: Show details about a particular install session. This 72 * may surface actions such as pause, resume, or cancel. 73 * <p> 74 * This should always be scoped to the installer package that owns the 75 * session. Clients should use {@link InstallSessionInfo#getDetailsIntent()} 76 * to build this intent correctly. 77 * <p> 78 * In some cases, a matching Activity may not exist, so ensure you safeguard 79 * against this. 80 */ 81 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 82 public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; 83 84 /** {@hide} */ 85 public static final String 86 ACTION_CONFIRM_PERMISSIONS = "android.content.pm.action.CONFIRM_PERMISSIONS"; 87 88 /** 89 * An integer session ID. 90 * 91 * @see #ACTION_SESSION_DETAILS 92 */ 93 public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; 94 95 private final PackageManager mPm; 96 private final IPackageInstaller mInstaller; 97 private final int mUserId; 98 private final String mInstallerPackageName; 99 100 private final ArrayList<SessionCallbackDelegate> mDelegates = new ArrayList<>(); 101 102 /** {@hide} */ 103 public PackageInstaller(PackageManager pm, IPackageInstaller installer, 104 String installerPackageName, int userId) { 105 mPm = pm; 106 mInstaller = installer; 107 mInstallerPackageName = installerPackageName; 108 mUserId = userId; 109 } 110 111 /** 112 * Create a new session using the given parameters, returning a unique ID 113 * that represents the session. Once created, the session can be opened 114 * multiple times across multiple device boots. 115 * <p> 116 * The system may automatically destroy sessions that have not been 117 * finalized (either committed or abandoned) within a reasonable period of 118 * time, typically on the order of a day. 119 * 120 * @throws IOException if parameters were unsatisfiable, such as lack of 121 * disk space or unavailable media. 122 * @return positive, non-zero unique ID that represents the created session. 123 * This ID remains consistent across device reboots until the 124 * session is finalized. IDs are not reused during a given boot. 125 */ 126 public int createSession(@NonNull InstallSessionParams params) throws IOException { 127 try { 128 return mInstaller.createSession(params, mInstallerPackageName, mUserId); 129 } catch (RuntimeException e) { 130 ExceptionUtils.maybeUnwrapIOException(e); 131 throw e; 132 } catch (RemoteException e) { 133 throw e.rethrowAsRuntimeException(); 134 } 135 } 136 137 /** 138 * Open an existing session to actively perform work. To succeed, the caller 139 * must be the owner of the install session. 140 */ 141 public @NonNull Session openSession(int sessionId) { 142 try { 143 return new Session(mInstaller.openSession(sessionId)); 144 } catch (RemoteException e) { 145 throw e.rethrowAsRuntimeException(); 146 } 147 } 148 149 /** 150 * Return details for a specific session. To succeed, the caller must either 151 * own this session, or be the current home app. 152 */ 153 public @Nullable InstallSessionInfo getSessionInfo(int sessionId) { 154 try { 155 return mInstaller.getSessionInfo(sessionId); 156 } catch (RemoteException e) { 157 throw e.rethrowAsRuntimeException(); 158 } 159 } 160 161 /** 162 * Return list of all active install sessions, regardless of the installer. 163 * To succeed, the caller must be the current home app. 164 */ 165 public @NonNull List<InstallSessionInfo> getAllSessions() { 166 try { 167 return mInstaller.getAllSessions(mUserId); 168 } catch (RemoteException e) { 169 throw e.rethrowAsRuntimeException(); 170 } 171 } 172 173 /** 174 * Return list of all install sessions owned by the calling app. 175 */ 176 public @NonNull List<InstallSessionInfo> getMySessions() { 177 try { 178 return mInstaller.getMySessions(mInstallerPackageName, mUserId); 179 } catch (RemoteException e) { 180 throw e.rethrowAsRuntimeException(); 181 } 182 } 183 184 /** 185 * Uninstall the given package, removing it completely from the device. This 186 * method is only available to the current "installer of record" for the 187 * package. 188 */ 189 public void uninstall(@NonNull String packageName, @NonNull UninstallCallback callback) { 190 try { 191 mInstaller.uninstall(packageName, 0, 192 new UninstallCallbackDelegate(callback).getBinder(), mUserId); 193 } catch (RemoteException e) { 194 throw e.rethrowAsRuntimeException(); 195 } 196 } 197 198 /** 199 * Uninstall only a specific split from the given package. 200 * 201 * @hide 202 */ 203 public void uninstall(@NonNull String packageName, @NonNull String splitName, 204 @NonNull UninstallCallback callback) { 205 try { 206 mInstaller.uninstallSplit(packageName, splitName, 0, 207 new UninstallCallbackDelegate(callback).getBinder(), mUserId); 208 } catch (RemoteException e) { 209 throw e.rethrowAsRuntimeException(); 210 } 211 } 212 213 /** {@hide} */ 214 public void setPermissionsResult(int sessionId, boolean accepted) { 215 try { 216 mInstaller.setPermissionsResult(sessionId, accepted); 217 } catch (RemoteException e) { 218 throw e.rethrowAsRuntimeException(); 219 } 220 } 221 222 /** 223 * Events for observing session lifecycle. 224 * <p> 225 * A typical session lifecycle looks like this: 226 * <ul> 227 * <li>An installer creates a session to indicate pending app delivery. All 228 * install details are available at this point. 229 * <li>The installer opens the session to deliver APK data. Note that a 230 * session may be opened and closed multiple times as network connectivity 231 * changes. The installer may deliver periodic progress updates. 232 * <li>The installer commits or abandons the session, resulting in the 233 * session being finished. 234 * </ul> 235 */ 236 public static abstract class SessionCallback { 237 /** 238 * New session has been created. Details about the session can be 239 * obtained from {@link PackageInstaller#getSessionInfo(int)}. 240 */ 241 public abstract void onCreated(int sessionId); 242 243 /** 244 * Session has been opened. A session is usually opened when the 245 * installer is actively writing data. 246 */ 247 public abstract void onOpened(int sessionId); 248 249 /** 250 * Progress for given session has been updated. 251 * <p> 252 * Note that this progress may not directly correspond to the value 253 * reported by {@link PackageInstaller.Session#setProgress(float)}, as 254 * the system may carve out a portion of the overall progress to 255 * represent its own internal installation work. 256 */ 257 public abstract void onProgressChanged(int sessionId, float progress); 258 259 /** 260 * Session has been closed. 261 */ 262 public abstract void onClosed(int sessionId); 263 264 /** 265 * Session has completely finished, either with success or failure. 266 */ 267 public abstract void onFinished(int sessionId, boolean success); 268 } 269 270 /** {@hide} */ 271 private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements 272 Handler.Callback { 273 private static final int MSG_SESSION_CREATED = 1; 274 private static final int MSG_SESSION_OPENED = 2; 275 private static final int MSG_SESSION_PROGRESS_CHANGED = 3; 276 private static final int MSG_SESSION_CLOSED = 4; 277 private static final int MSG_SESSION_FINISHED = 5; 278 279 final SessionCallback mCallback; 280 final Handler mHandler; 281 282 public SessionCallbackDelegate(SessionCallback callback, Looper looper) { 283 mCallback = callback; 284 mHandler = new Handler(looper, this); 285 } 286 287 @Override 288 public boolean handleMessage(Message msg) { 289 switch (msg.what) { 290 case MSG_SESSION_CREATED: 291 mCallback.onCreated(msg.arg1); 292 return true; 293 case MSG_SESSION_OPENED: 294 mCallback.onOpened(msg.arg1); 295 return true; 296 case MSG_SESSION_PROGRESS_CHANGED: 297 mCallback.onProgressChanged(msg.arg1, (float) msg.obj); 298 return true; 299 case MSG_SESSION_CLOSED: 300 mCallback.onClosed(msg.arg1); 301 return true; 302 case MSG_SESSION_FINISHED: 303 mCallback.onFinished(msg.arg1, msg.arg2 != 0); 304 return true; 305 } 306 return false; 307 } 308 309 @Override 310 public void onSessionCreated(int sessionId) { 311 mHandler.obtainMessage(MSG_SESSION_CREATED, sessionId, 0).sendToTarget(); 312 } 313 314 @Override 315 public void onSessionOpened(int sessionId) { 316 mHandler.obtainMessage(MSG_SESSION_OPENED, sessionId, 0).sendToTarget(); 317 } 318 319 @Override 320 public void onSessionProgressChanged(int sessionId, float progress) { 321 mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress) 322 .sendToTarget(); 323 } 324 325 @Override 326 public void onSessionClosed(int sessionId) { 327 mHandler.obtainMessage(MSG_SESSION_CLOSED, sessionId, 0).sendToTarget(); 328 } 329 330 @Override 331 public void onSessionFinished(int sessionId, boolean success) { 332 mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0) 333 .sendToTarget(); 334 } 335 } 336 337 /** 338 * Register to watch for session lifecycle events. To succeed, the caller 339 * must be the current home app. 340 */ 341 public void addSessionCallback(@NonNull SessionCallback callback) { 342 addSessionCallback(callback, new Handler()); 343 } 344 345 /** 346 * Register to watch for session lifecycle events. To succeed, the caller 347 * must be the current home app. 348 * 349 * @param handler to dispatch callback events through, otherwise uses 350 * calling thread. 351 */ 352 public void addSessionCallback(@NonNull SessionCallback callback, @NonNull Handler handler) { 353 synchronized (mDelegates) { 354 final SessionCallbackDelegate delegate = new SessionCallbackDelegate(callback, 355 handler.getLooper()); 356 try { 357 mInstaller.registerCallback(delegate, mUserId); 358 } catch (RemoteException e) { 359 throw e.rethrowAsRuntimeException(); 360 } 361 mDelegates.add(delegate); 362 } 363 } 364 365 /** 366 * Unregister an existing callback. 367 */ 368 public void removeSessionCallback(@NonNull SessionCallback callback) { 369 synchronized (mDelegates) { 370 for (Iterator<SessionCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) { 371 final SessionCallbackDelegate delegate = i.next(); 372 if (delegate.mCallback == callback) { 373 try { 374 mInstaller.unregisterCallback(delegate); 375 } catch (RemoteException e) { 376 throw e.rethrowAsRuntimeException(); 377 } 378 i.remove(); 379 } 380 } 381 } 382 } 383 384 /** 385 * An installation that is being actively staged. For an install to succeed, 386 * all existing and new packages must have identical package names, version 387 * codes, and signing certificates. 388 * <p> 389 * A session may contain any number of split packages. If the application 390 * does not yet exist, this session must include a base package. 391 * <p> 392 * If an APK included in this session is already defined by the existing 393 * installation (for example, the same split name), the APK in this session 394 * will replace the existing APK. 395 */ 396 public static class Session implements Closeable { 397 private IPackageInstallerSession mSession; 398 399 /** {@hide} */ 400 public Session(IPackageInstallerSession session) { 401 mSession = session; 402 } 403 404 /** 405 * Set current progress. Valid values are anywhere between 0 and 1. 406 */ 407 public void setProgress(float progress) { 408 try { 409 mSession.setClientProgress(progress); 410 } catch (RemoteException e) { 411 throw e.rethrowAsRuntimeException(); 412 } 413 } 414 415 /** {@hide} */ 416 public void addProgress(float progress) { 417 try { 418 mSession.addClientProgress(progress); 419 } catch (RemoteException e) { 420 throw e.rethrowAsRuntimeException(); 421 } 422 } 423 424 /** 425 * Open a stream to write an APK file into the session. 426 * <p> 427 * The returned stream will start writing data at the requested offset 428 * in the underlying file, which can be used to resume a partially 429 * written file. If a valid file length is specified, the system will 430 * preallocate the underlying disk space to optimize placement on disk. 431 * It's strongly recommended to provide a valid file length when known. 432 * <p> 433 * You can write data into the returned stream, optionally call 434 * {@link #fsync(OutputStream)} as needed to ensure bytes have been 435 * persisted to disk, and then close when finished. All streams must be 436 * closed before calling {@link #commit(CommitCallback)}. 437 * 438 * @param name arbitrary, unique name of your choosing to identify the 439 * APK being written. You can open a file again for 440 * additional writes (such as after a reboot) by using the 441 * same name. This name is only meaningful within the context 442 * of a single install session. 443 * @param offsetBytes offset into the file to begin writing at, or 0 to 444 * start at the beginning of the file. 445 * @param lengthBytes total size of the file being written, used to 446 * preallocate the underlying disk space, or -1 if unknown. 447 */ 448 public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes, 449 long lengthBytes) throws IOException { 450 try { 451 final ParcelFileDescriptor clientSocket = mSession.openWrite(name, 452 offsetBytes, lengthBytes); 453 return new FileBridge.FileBridgeOutputStream(clientSocket.getFileDescriptor()); 454 } catch (RuntimeException e) { 455 ExceptionUtils.maybeUnwrapIOException(e); 456 throw e; 457 } catch (RemoteException e) { 458 throw e.rethrowAsRuntimeException(); 459 } 460 } 461 462 /** 463 * Ensure that any outstanding data for given stream has been committed 464 * to disk. This is only valid for streams returned from 465 * {@link #openWrite(String, long, long)}. 466 */ 467 public void fsync(@NonNull OutputStream out) throws IOException { 468 if (out instanceof FileBridge.FileBridgeOutputStream) { 469 ((FileBridge.FileBridgeOutputStream) out).fsync(); 470 } else { 471 throw new IllegalArgumentException("Unrecognized stream"); 472 } 473 } 474 475 /** 476 * List all APK names contained in this session. 477 * <p> 478 * This returns all names which have been previously written through 479 * {@link #openWrite(String, long, long)} as part of this session. 480 */ 481 public @NonNull String[] list() { 482 try { 483 return mSession.list(); 484 } catch (RemoteException e) { 485 throw e.rethrowAsRuntimeException(); 486 } 487 } 488 489 /** 490 * Open a stream to read an APK file from the session. 491 * <p> 492 * This is only valid for names which have been previously written 493 * through {@link #openWrite(String, long, long)} as part of this 494 * session. For example, this stream may be used to calculate a 495 * {@link MessageDigest} of a written APK before committing. 496 */ 497 public @NonNull InputStream openRead(@NonNull String name) throws IOException { 498 try { 499 final ParcelFileDescriptor pfd = mSession.openRead(name); 500 return new ParcelFileDescriptor.AutoCloseInputStream(pfd); 501 } catch (RuntimeException e) { 502 ExceptionUtils.maybeUnwrapIOException(e); 503 throw e; 504 } catch (RemoteException e) { 505 throw e.rethrowAsRuntimeException(); 506 } 507 } 508 509 /** 510 * Attempt to commit everything staged in this session. This may require 511 * user intervention, and so it may not happen immediately. The final 512 * result of the commit will be reported through the given callback. 513 * <p> 514 * Once this method is called, no additional mutations may be performed 515 * on the session. If the device reboots before the session has been 516 * finalized, you may commit the session again. 517 */ 518 public void commit(@NonNull CommitCallback callback) { 519 try { 520 mSession.commit(new CommitCallbackDelegate(callback).getBinder()); 521 } catch (RemoteException e) { 522 throw e.rethrowAsRuntimeException(); 523 } 524 } 525 526 /** 527 * Release this session object. You can open the session again if it 528 * hasn't been finalized. 529 */ 530 @Override 531 public void close() { 532 try { 533 mSession.close(); 534 } catch (RemoteException e) { 535 throw e.rethrowAsRuntimeException(); 536 } 537 } 538 539 /** 540 * Completely abandon this session, destroying all staged data and 541 * rendering it invalid. 542 */ 543 public void abandon() { 544 try { 545 mSession.abandon(); 546 } catch (RemoteException e) { 547 throw e.rethrowAsRuntimeException(); 548 } 549 } 550 } 551 552 /** 553 * Events for a specific uninstall request. 554 */ 555 public static abstract class UninstallCallback { 556 /** 557 * User action is required to proceed. You can start the given intent 558 * activity to involve the user and continue. 559 * <p> 560 * You may choose to immediately launch the intent if the user is 561 * actively using your app. However, you should use a notification to 562 * guide the user back into your app if not currently active. 563 */ 564 public abstract void onUserActionRequired(Intent intent); 565 566 public abstract void onSuccess(); 567 public abstract void onFailure(String msg); 568 } 569 570 /** {@hide} */ 571 private static class UninstallCallbackDelegate extends PackageDeleteObserver { 572 private final UninstallCallback target; 573 574 public UninstallCallbackDelegate(UninstallCallback target) { 575 this.target = target; 576 } 577 578 @Override 579 public void onUserActionRequired(Intent intent) { 580 target.onUserActionRequired(intent); 581 } 582 583 @Override 584 public void onPackageDeleted(String basePackageName, int returnCode, String msg) { 585 if (returnCode == PackageManager.DELETE_SUCCEEDED) { 586 target.onSuccess(); 587 } else { 588 msg = PackageManager.deleteStatusToString(returnCode) + ": " + msg; 589 target.onFailure(msg); 590 } 591 } 592 } 593 594 /** 595 * Final result of a session commit request. 596 */ 597 public static abstract class CommitCallback { 598 /** 599 * Generic unknown failure. The system will always try to provide a more 600 * specific failure reason, but in some rare cases this may be 601 * delivered. 602 */ 603 public static final int FAILURE_UNKNOWN = 0; 604 605 /** 606 * One or more of the APKs included in the session was invalid. For 607 * example, they might be malformed, corrupt, incorrectly signed, 608 * mismatched, etc. The installer may want to try downloading and 609 * installing again. 610 */ 611 public static final int FAILURE_INVALID = 1; 612 613 /** 614 * This install session conflicts (or is inconsistent with) with another 615 * package already installed on the device. For example, an existing 616 * permission, incompatible certificates, etc. The user may be able to 617 * uninstall another app to fix the issue. 618 * <p> 619 * The extras bundle may contain {@link #EXTRA_PACKAGE_NAME} with the 620 * specific packages identified as the cause of the conflict. 621 */ 622 public static final int FAILURE_CONFLICT = 2; 623 624 /** 625 * This install session failed due to storage issues. For example, 626 * the device may be running low on space, or the required external 627 * media may be unavailable. The user may be able to help free space 628 * or insert the correct media. 629 */ 630 public static final int FAILURE_STORAGE = 3; 631 632 /** 633 * This install session is fundamentally incompatible with this 634 * device. For example, the package may require a hardware feature 635 * that doesn't exist, it may be missing native code for the device 636 * ABI, or it requires a newer SDK version, etc. This install would 637 * never succeed. 638 */ 639 public static final int FAILURE_INCOMPATIBLE = 4; 640 641 /** 642 * This install session failed because it was rejected. For example, the 643 * user declined requested permissions, or a package verifier rejected 644 * the session. 645 * 646 * @see PackageManager#VERIFICATION_REJECT 647 */ 648 public static final int FAILURE_REJECTED = 5; 649 650 public static final String EXTRA_PACKAGE_NAME = "android.content.pm.extra.PACKAGE_NAME"; 651 652 /** 653 * User action is required to proceed. You can start the given intent 654 * activity to involve the user and continue. 655 * <p> 656 * You may choose to immediately launch the intent if the user is 657 * actively using your app. However, you should use a notification to 658 * guide the user back into your app if not currently active. 659 */ 660 public abstract void onUserActionRequired(Intent intent); 661 662 public abstract void onSuccess(); 663 public abstract void onFailure(int failureReason, String msg, Bundle extras); 664 } 665 666 /** {@hide} */ 667 private static class CommitCallbackDelegate extends PackageInstallObserver { 668 private final CommitCallback target; 669 670 public CommitCallbackDelegate(CommitCallback target) { 671 this.target = target; 672 } 673 674 @Override 675 public void onUserActionRequired(Intent intent) { 676 target.onUserActionRequired(intent); 677 } 678 679 @Override 680 public void onPackageInstalled(String basePackageName, int returnCode, String msg, 681 Bundle extras) { 682 if (returnCode == PackageManager.INSTALL_SUCCEEDED) { 683 target.onSuccess(); 684 } else { 685 final int failureReason = PackageManager.installStatusToFailureReason(returnCode); 686 msg = PackageManager.installStatusToString(returnCode) + ": " + msg; 687 688 if (extras != null) { 689 extras.putString(CommitCallback.EXTRA_PACKAGE_NAME, 690 extras.getString(PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE)); 691 } 692 693 target.onFailure(failureReason, msg, extras); 694 } 695 } 696 } 697} 698