diff options
author | Jeff Sharkey <jsharkey@android.com> | 2014-07-14 22:44:30 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2014-07-14 23:06:52 -0700 |
commit | 6c833e07a05c48ca60ee4d72421bf8b1e78dc710 (patch) | |
tree | f16f3c2a12dea6fd5131d82d007fd75cd61fb915 /core/java/android/content | |
parent | 5c6a8e322227354acbded5c49f44c0b289021bf5 (diff) | |
download | frameworks_base-6c833e07a05c48ca60ee4d72421bf8b1e78dc710.zip frameworks_base-6c833e07a05c48ca60ee4d72421bf8b1e78dc710.tar.gz frameworks_base-6c833e07a05c48ca60ee4d72421bf8b1e78dc710.tar.bz2 |
Public API for PackageInstaller!
Flesh out documentation and finalize first cut of API. Also surface
installLocation and splitNames through PackageInfo.
Bug: 14975160, 15348430
Change-Id: Ic27696d20ed06e508aa3526218e9cb20835af6a0
Diffstat (limited to 'core/java/android/content')
-rw-r--r-- | core/java/android/content/pm/InstallSessionInfo.java | 68 | ||||
-rw-r--r-- | core/java/android/content/pm/InstallSessionParams.java | 83 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageInfo.java | 32 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageInstaller.java | 157 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageManager.java | 10 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageParser.java | 1 |
6 files changed, 306 insertions, 45 deletions
diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java index 45606d1..3336727 100644 --- a/core/java/android/content/pm/InstallSessionInfo.java +++ b/core/java/android/content/pm/InstallSessionInfo.java @@ -20,15 +20,25 @@ import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; -/** {@hide} */ +/** + * Details for an active install session. + */ public class InstallSessionInfo implements Parcelable { + + /** {@hide} */ public int sessionId; + /** {@hide} */ public String installerPackageName; + /** {@hide} */ public int progress; - public boolean fullInstall; + /** {@hide} */ + public int mode; + /** {@hide} */ public String packageName; + /** {@hide} */ public Bitmap icon; + /** {@hide} */ public CharSequence title; /** {@hide} */ @@ -41,12 +51,62 @@ public class InstallSessionInfo implements Parcelable { installerPackageName = source.readString(); progress = source.readInt(); - fullInstall = source.readInt() != 0; + mode = source.readInt(); packageName = source.readString(); icon = source.readParcelable(null); title = source.readString(); } + /** + * Return the ID for this session. + */ + public int getSessionId() { + return sessionId; + } + + /** + * Return the package name of the app that owns this session. + */ + public String getInstallerPackageName() { + return installerPackageName; + } + + /** + * Return current overall progress of this session, between 0 and 100. + * <p> + * Note that this progress may not directly correspond to the value reported + * by {@link PackageInstaller.Session#setProgress(int)}, as the system may + * carve out a portion of the overall progress to represent its own internal + * installation work. + */ + public int getProgress() { + return progress; + } + + /** + * Return the package name this session is working with. May be {@code null} + * if unknown. + */ + public String getPackageName() { + return packageName; + } + + /** + * Return an icon representing the app being installed. May be {@code null} + * if unavailable. + */ + public Bitmap getIcon() { + return icon; + } + + /** + * Return a title representing the app being installed. May be {@code null} + * if unavailable. + */ + public CharSequence getTitle() { + return title; + } + @Override public int describeContents() { return 0; @@ -58,7 +118,7 @@ public class InstallSessionInfo implements Parcelable { dest.writeString(installerPackageName); dest.writeInt(progress); - dest.writeInt(fullInstall ? 1 : 0); + dest.writeInt(mode); dest.writeString(packageName); dest.writeParcelable(icon, flags); dest.writeString(title != null ? title.toString() : null); diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java index 43e3314..e039699 100644 --- a/core/java/android/content/pm/InstallSessionParams.java +++ b/core/java/android/content/pm/InstallSessionParams.java @@ -16,6 +16,7 @@ package android.content.pm; +import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Parcel; @@ -23,13 +24,22 @@ import android.os.Parcelable; import com.android.internal.util.IndentingPrintWriter; -/** {@hide} */ +/** + * Parameters for creating a new {@link PackageInstaller.Session}. + */ public class InstallSessionParams implements Parcelable { // TODO: extend to support remaining VerificationParams /** {@hide} */ - public boolean fullInstall; + public static final int MODE_INVALID = 0; + /** {@hide} */ + public static final int MODE_FULL_INSTALL = 1; + /** {@hide} */ + public static final int MODE_INHERIT_EXISTING = 2; + + /** {@hide} */ + public int mode = MODE_INVALID; /** {@hide} */ public int installFlags; /** {@hide} */ @@ -58,7 +68,7 @@ public class InstallSessionParams implements Parcelable { /** {@hide} */ public InstallSessionParams(Parcel source) { - fullInstall = source.readInt() != 0; + mode = source.readInt(); installFlags = source.readInt(); installLocation = source.readInt(); signatures = (Signature[]) source.readParcelableArray(null); @@ -72,53 +82,108 @@ public class InstallSessionParams implements Parcelable { abiOverride = source.readString(); } - public void setFullInstall(boolean fullInstall) { - this.fullInstall = fullInstall; + /** + * Set session mode indicating that it should fully replace any existing + * APKs for this application. + */ + public void setModeFullInstall() { + this.mode = MODE_FULL_INSTALL; } - public void setInstallFlags(int installFlags) { - this.installFlags = installFlags; + /** + * Set session mode indicating that it should inherit any existing APKs for + * this application, unless they are explicitly overridden (by split name) + * in the session. + */ + public void setModeInheritExisting() { + this.mode = MODE_INHERIT_EXISTING; } + /** + * Provide value of {@link PackageInfo#installLocation}, which may be used + * to determine where the app will be staged. Defaults to + * {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}. + */ public void setInstallLocation(int installLocation) { this.installLocation = installLocation; } + /** + * Optionally provide the required value of {@link PackageInfo#signatures}. + * This can be used to assert that all staged APKs have been signed with + * this set of specific certificates. Regardless of this value, all APKs in + * the application must have the same signing certificates. + */ public void setSignatures(Signature[] signatures) { this.signatures = signatures; } + /** + * Indicate the expected growth in disk usage resulting from this session. + * This may be used to ensure enough disk space exists before proceeding, or + * to estimate container size for installations living on external storage. + * <p> + * This value should only reflect APK sizes. + */ public void setDeltaSize(long deltaSize) { this.deltaSize = deltaSize; } + /** + * Set the maximum progress for this session, used for normalization + * purposes. + * + * @see PackageInstaller.Session#setProgress(int) + */ public void setProgressMax(int progressMax) { this.progressMax = progressMax; } + /** + * Optionally set the package name this session will be working with. It's + * strongly recommended that you provide this value when known. + */ public void setPackageName(String packageName) { this.packageName = packageName; } + /** + * Optionally set an icon representing the app being installed. + */ public void setIcon(Bitmap icon) { this.icon = icon; } + /** + * Optionally set a title representing the app being installed. + */ public void setTitle(CharSequence title) { this.title = title; } + /** + * Optionally set the URI where this package was downloaded from. Used for + * verification purposes. + * + * @see Intent#EXTRA_ORIGINATING_URI + */ public void setOriginatingUri(Uri originatingUri) { this.originatingUri = originatingUri; } + /** + * Optionally set the URI that referred you to install this package. Used + * for verification purposes. + * + * @see Intent#EXTRA_REFERRER + */ public void setReferrerUri(Uri referrerUri) { this.referrerUri = referrerUri; } /** {@hide} */ public void dump(IndentingPrintWriter pw) { - pw.printPair("fullInstall", fullInstall); + pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); pw.printPair("installLocation", installLocation); pw.printPair("signatures", (signatures != null)); @@ -140,7 +205,7 @@ public class InstallSessionParams implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { - dest.writeInt(fullInstall ? 1 : 0); + dest.writeInt(mode); dest.writeInt(installFlags); dest.writeInt(installLocation); dest.writeParcelableArray(signatures, flags); diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index ef0c4d5..8f0c249 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -31,6 +31,11 @@ public class PackageInfo implements Parcelable { public String packageName; /** + * The names of any installed split APKs for this package. + */ + public String[] splitNames; + + /** * The version number of this package, as specified by the <manifest> * tag's {@link android.R.styleable#AndroidManifest_versionCode versionCode} * attribute. @@ -190,24 +195,25 @@ public class PackageInfo implements Parcelable { * @hide */ public static final int INSTALL_LOCATION_UNSPECIFIED = -1; + /** - * Constant corresponding to <code>auto</code> in - * the {@link android.R.attr#installLocation} attribute. - * @hide + * Constant corresponding to <code>auto</code> in the + * {@link android.R.attr#installLocation} attribute. */ public static final int INSTALL_LOCATION_AUTO = 0; + /** - * Constant corresponding to <code>internalOnly</code> in - * the {@link android.R.attr#installLocation} attribute. - * @hide + * Constant corresponding to <code>internalOnly</code> in the + * {@link android.R.attr#installLocation} attribute. */ public static final int INSTALL_LOCATION_INTERNAL_ONLY = 1; + /** - * Constant corresponding to <code>preferExternal</code> in - * the {@link android.R.attr#installLocation} attribute. - * @hide + * Constant corresponding to <code>preferExternal</code> in the + * {@link android.R.attr#installLocation} attribute. */ public static final int INSTALL_LOCATION_PREFER_EXTERNAL = 2; + /** * Flag for {@link #requiredForProfile} * The application will always be installed for a restricted profile. @@ -222,12 +228,10 @@ public class PackageInfo implements Parcelable { public static final int MANAGED_PROFILE = 2; /** - * The install location requested by the activity. From the + * The install location requested by the package. From the * {@link android.R.attr#installLocation} attribute, one of - * {@link #INSTALL_LOCATION_AUTO}, - * {@link #INSTALL_LOCATION_INTERNAL_ONLY}, + * {@link #INSTALL_LOCATION_AUTO}, {@link #INSTALL_LOCATION_INTERNAL_ONLY}, * {@link #INSTALL_LOCATION_PREFER_EXTERNAL} - * @hide */ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; @@ -269,6 +273,7 @@ public class PackageInfo implements Parcelable { public void writeToParcel(Parcel dest, int parcelableFlags) { dest.writeString(packageName); + dest.writeStringArray(splitNames); dest.writeInt(versionCode); dest.writeString(versionName); dest.writeString(sharedUserId); @@ -314,6 +319,7 @@ public class PackageInfo implements Parcelable { private PackageInfo(Parcel source) { packageName = source.readString(); + splitNames = source.readStringArray(); versionCode = source.readInt(); versionName = source.readString(); sharedUserId = source.readString(); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 401be06..348a7ad 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -30,7 +30,31 @@ import java.io.IOException; import java.io.OutputStream; import java.util.List; -/** {@hide} */ +/** + * Offers the ability to install, upgrade, and remove applications on the + * device. This includes support for apps packaged either as a single + * "monolithic" APK, or apps packaged as multiple "split" APKs. + * <p> + * An app is delivered for installation through a + * {@link PackageInstaller.Session}, which any app can create. Once the session + * is created, the installer can stream one or more APKs into place until it + * decides to either commit or destroy the session. Committing may require user + * intervention to complete the installation. + * <p> + * Sessions can install brand new apps, upgrade existing apps, or add new splits + * onto an existing app. + * <p> + * Apps packaged into multiple split APKs always consist of a single "base" APK + * (with a {@code null} split name) and zero or more "split" APKs (with unique + * split names). Any subset of these APKs can be installed together, as long as + * the following constraints are met: + * <ul> + * <li>All APKs must have the exact same package name, version code, and signing + * certificates. + * <li>All installations must contain a single base APK. + * <li>All APKs must have unique split names. + * </ul> + */ public class PackageInstaller { private final PackageManager mPm; private final IPackageInstaller mInstaller; @@ -46,16 +70,18 @@ public class PackageInstaller { mUserId = userId; } + /** + * Quickly test if the given package is already available on the device. + * This is typically used in multi-user scenarios where another user on the + * device has already installed the package. + * + * @hide + */ public boolean isPackageAvailable(String packageName) { - try { - final ApplicationInfo info = mPm.getApplicationInfo(packageName, - PackageManager.GET_UNINSTALLED_PACKAGES); - return ((info.flags & ApplicationInfo.FLAG_INSTALLED) != 0); - } catch (NameNotFoundException e) { - return false; - } + return mPm.isPackageAvailable(packageName); } + /** {@hide} */ public void installAvailablePackage(String packageName, PackageInstallObserver observer) { int returnCode; try { @@ -66,6 +92,18 @@ public class PackageInstaller { observer.packageInstalled(packageName, null, returnCode); } + /** + * Create a new session using the given parameters, returning a unique ID + * that represents the session. Once created, the session can be opened + * multiple times across multiple device boots. + * <p> + * The system may automatically destroy sessions that have not been + * finalized (either committed or abandoned) within a reasonable period of + * time, typically on the order of a day. + * + * @throws IOException if parameters were unsatisfiable, such as lack of + * disk space or unavailable media. + */ public int createSession(InstallSessionParams params) throws IOException { try { return mInstaller.createSession(mInstallerPackageName, params, mUserId); @@ -77,6 +115,9 @@ public class PackageInstaller { } } + /** + * Open an existing session to actively perform work. + */ public Session openSession(int sessionId) { try { return new Session(mInstaller.openSession(sessionId)); @@ -85,7 +126,10 @@ public class PackageInstaller { } } - public List<InstallSessionInfo> getSessions() { + /** + * Return list of all active install sessions on the device. + */ + public List<InstallSessionInfo> getActiveSessions() { // TODO: filter based on caller // TODO: let launcher app see all active sessions try { @@ -95,6 +139,11 @@ public class PackageInstaller { } } + /** + * Uninstall the given package, removing it completely from the device. This + * method is only available to the current "installer of record" for the + * package. + */ public void uninstall(String packageName, UninstallResultCallback callback) { try { mInstaller.uninstall(packageName, 0, @@ -104,6 +153,11 @@ public class PackageInstaller { } } + /** + * Uninstall only a specific split from the given package. + * + * @hide + */ public void uninstall(String packageName, String splitName, UninstallResultCallback callback) { try { mInstaller.uninstallSplit(packageName, splitName, 0, @@ -113,6 +167,9 @@ public class PackageInstaller { } } + /** + * Events for observing session lifecycle. + */ public static abstract class SessionObserver { private final IPackageInstallerObserver.Stub mBinder = new IPackageInstallerObserver.Stub() { @Override @@ -127,7 +184,7 @@ public class PackageInstaller { @Override public void onSessionFinished(int sessionId, boolean success) { - SessionObserver.this.onFinished(sessionId, success); + SessionObserver.this.onFinalized(sessionId, success); } }; @@ -136,11 +193,30 @@ public class PackageInstaller { return mBinder; } + /** + * New session has been created. + */ public abstract void onCreated(InstallSessionInfo info); + + /** + * Progress for given session has been updated. + * <p> + * Note that this progress may not directly correspond to the value + * reported by {@link PackageInstaller.Session#setProgress(int)}, as the + * system may carve out a portion of the overall progress to represent + * its own internal installation work. + */ public abstract void onProgress(int sessionId, int progress); - public abstract void onFinished(int sessionId, boolean success); + + /** + * Session has been finalized, either with success or failure. + */ + public abstract void onFinalized(int sessionId, boolean success); } + /** + * Register to watch for session lifecycle events. + */ public void registerSessionObserver(SessionObserver observer) { try { mInstaller.registerObserver(observer.getBinder(), mUserId); @@ -149,6 +225,9 @@ public class PackageInstaller { } } + /** + * Unregister an existing observer. + */ public void unregisterSessionObserver(SessionObserver observer) { try { mInstaller.unregisterObserver(observer.getBinder(), mUserId); @@ -177,6 +256,10 @@ public class PackageInstaller { mSession = session; } + /** + * Set current progress. Valid values are anywhere between 0 and + * {@link InstallSessionParams#setProgressMax(int)}. + */ public void setProgress(int progress) { try { mSession.setClientProgress(progress); @@ -197,7 +280,7 @@ public class PackageInstaller { /** * Open an APK file for writing, starting at the given offset. You can * then stream data into the file, periodically calling - * {@link OutputStream#flush()} to ensure bytes have been written to + * {@link #fsync(OutputStream)} to ensure bytes have been written to * disk. */ public OutputStream openWrite(String splitName, long offsetBytes, long lengthBytes) @@ -214,6 +297,11 @@ public class PackageInstaller { } } + /** + * Ensure that any outstanding data for given stream has been committed + * to disk. This is only valid for streams returned from + * {@link #openWrite(String, long, long)}. + */ public void fsync(OutputStream out) throws IOException { if (out instanceof FileBridge.FileBridgeOutputStream) { ((FileBridge.FileBridgeOutputStream) out).fsync(); @@ -222,6 +310,15 @@ public class PackageInstaller { } } + /** + * Attempt to commit everything staged in this session. This may require + * user intervention, and so it may not happen immediately. The final + * result of the commit will be reported through the given callback. + * <p> + * Once this method is called, no additional mutations may be performed + * on the session. If the device reboots before the session has been + * finalized, you may commit the session again. + */ public void commit(CommitResultCallback callback) { try { mSession.install(new CommitResultCallbackDelegate(callback).getBinder()); @@ -230,11 +327,18 @@ public class PackageInstaller { } } + /** + * Release this session object. You can open the session again if it + * hasn't been finalized. + */ @Override public void close() { // No resources to release at the moment } + /** + * Completely destroy this session, rendering it invalid. + */ public void destroy() { try { mSession.destroy(); @@ -244,11 +348,15 @@ public class PackageInstaller { } } + /** + * Final result of an uninstall request. + */ public static abstract class UninstallResultCallback { public abstract void onSuccess(); public abstract void onFailure(String msg); } + /** {@hide} */ private static class UninstallResultCallbackDelegate extends PackageUninstallObserver { private final UninstallResultCallback target; @@ -271,8 +379,18 @@ public class PackageInstaller { } } + /** + * Final result of a session commit request. + */ public static abstract class CommitResultCallback { public abstract void onSuccess(); + + /** + * Generic failure occurred. You can override methods (such as + * {@link #onFailureInvalid(String)}) to handle more specific categories + * of failure. By default, those specific categories all flow into this + * generic failure. + */ public abstract void onFailure(String msg); /** @@ -286,12 +404,16 @@ public class PackageInstaller { } /** - * This install session conflicts (or is inconsistent with) with - * another package already installed on the device. For example, an - * existing permission, incompatible certificates, etc. The user may - * be able to uninstall another app to fix the issue. + * This install session conflicts (or is inconsistent with) with another + * package already installed on the device. For example, an existing + * permission, incompatible certificates, etc. The user may be able to + * uninstall another app to fix the issue. + * + * @param otherPackageName if one specific package was identified as the + * cause of the conflict, it's named here. If unknown, or + * multiple packages, this may be {@code null}. */ - public void onFailureConflict(String msg, String packageName) { + public void onFailureConflict(String msg, String otherPackageName) { onFailure(msg); } @@ -317,6 +439,7 @@ public class PackageInstaller { } } + /** {@hide} */ private static class CommitResultCallbackDelegate extends PackageInstallObserver { private final CommitResultCallback target; diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 03d4701..04ca4a3 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3629,8 +3629,11 @@ public abstract class PackageManager { */ public abstract VerifierDeviceIdentity getVerifierDeviceIdentity(); - /** {@hide} */ - public abstract PackageInstaller getPackageInstaller(); + /** + * Return interface that offers the ability to install, upgrade, and remove + * applications on the device. + */ + public abstract PackageInstaller getInstaller(); /** * Returns the data directory for a particular user and package, given the uid of the package. @@ -3676,4 +3679,7 @@ public abstract class PackageManager { * @hide */ public abstract Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo); + + /** {@hide} */ + public abstract boolean isPackageAvailable(String packageName); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index d92a90d..db2da42 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -380,6 +380,7 @@ public class PackageParser { } PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; + pi.splitNames = p.splitNames; pi.versionCode = p.mVersionCode; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; |