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