diff options
author | Jeff Sharkey <jsharkey@android.com> | 2014-07-31 22:24:57 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2014-07-25 23:00:08 +0000 |
commit | ceb2adca4905bc1f80545792d82bed5d877ed583 (patch) | |
tree | 86b5aa99228e89d26747470841f2570d6c6eef98 /core | |
parent | f5f45bc224d69f478ce446d85720a514a3d12f7e (diff) | |
parent | 1cb2d0d4bba387665128c62c342e59103ea4be26 (diff) | |
download | frameworks_base-ceb2adca4905bc1f80545792d82bed5d877ed583.zip frameworks_base-ceb2adca4905bc1f80545792d82bed5d877ed583.tar.gz frameworks_base-ceb2adca4905bc1f80545792d82bed5d877ed583.tar.bz2 |
Merge "Persist install sessions, more lifecycle." into lmp-dev
Diffstat (limited to 'core')
7 files changed, 236 insertions, 23 deletions
diff --git a/core/java/android/content/pm/IPackageInstallerCallback.aidl b/core/java/android/content/pm/IPackageInstallerCallback.aidl index a31ae54..39ae1a0 100644 --- a/core/java/android/content/pm/IPackageInstallerCallback.aidl +++ b/core/java/android/content/pm/IPackageInstallerCallback.aidl @@ -19,6 +19,8 @@ package android.content.pm; /** {@hide} */ oneway interface IPackageInstallerCallback { void onSessionCreated(int sessionId); + void onSessionOpened(int sessionId); void onSessionProgressChanged(int sessionId, float progress); + void onSessionClosed(int sessionId); void onSessionFinished(int sessionId, boolean success); } diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl index 2fd7ddb..af0323f 100644 --- a/core/java/android/content/pm/IPackageInstallerSession.aidl +++ b/core/java/android/content/pm/IPackageInstallerSession.aidl @@ -24,7 +24,9 @@ interface IPackageInstallerSession { void setClientProgress(float progress); void addClientProgress(float progress); + String[] list(); ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes); + ParcelFileDescriptor openRead(String name); void close(); void commit(in IPackageInstallObserver2 observer); diff --git a/core/java/android/content/pm/InstallSessionInfo.java b/core/java/android/content/pm/InstallSessionInfo.java index a9c574a..f263885 100644 --- a/core/java/android/content/pm/InstallSessionInfo.java +++ b/core/java/android/content/pm/InstallSessionInfo.java @@ -16,7 +16,9 @@ package android.content.pm; +import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Intent; import android.graphics.Bitmap; import android.os.Parcel; import android.os.Parcelable; @@ -32,6 +34,8 @@ public class InstallSessionInfo implements Parcelable { public String installerPackageName; /** {@hide} */ public float progress; + /** {@hide} */ + public boolean open; /** {@hide} */ public int mode; @@ -53,6 +57,7 @@ public class InstallSessionInfo implements Parcelable { sessionId = source.readInt(); installerPackageName = source.readString(); progress = source.readFloat(); + open = source.readInt() != 0; mode = source.readInt(); sizeBytes = source.readLong(); @@ -88,6 +93,13 @@ public class InstallSessionInfo implements Parcelable { } /** + * Return if this session is currently open. + */ + public boolean isOpen() { + return open; + } + + /** * Return the package name this session is working with. May be {@code null} * if unknown. */ @@ -111,6 +123,23 @@ public class InstallSessionInfo implements Parcelable { return appLabel; } + /** + * Return an Intent that can be started to view details about this install + * session. This may surface actions such as pause, resume, or cancel. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard + * against this. + * + * @see PackageInstaller#ACTION_SESSION_DETAILS + */ + public @Nullable Intent getDetailsIntent() { + final Intent intent = new Intent(PackageInstaller.ACTION_SESSION_DETAILS); + intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId); + intent.setPackage(installerPackageName); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return intent; + } + @Override public int describeContents() { return 0; @@ -121,6 +150,7 @@ public class InstallSessionInfo implements Parcelable { dest.writeInt(sessionId); dest.writeString(installerPackageName); dest.writeFloat(progress); + dest.writeInt(open ? 1 : 0); dest.writeInt(mode); dest.writeLong(sizeBytes); diff --git a/core/java/android/content/pm/InstallSessionParams.java b/core/java/android/content/pm/InstallSessionParams.java index 3de9863..1716e39 100644 --- a/core/java/android/content/pm/InstallSessionParams.java +++ b/core/java/android/content/pm/InstallSessionParams.java @@ -17,6 +17,7 @@ package android.content.pm; import android.annotation.Nullable; +import android.app.ActivityManager; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; @@ -30,6 +31,9 @@ import com.android.internal.util.IndentingPrintWriter; */ public class InstallSessionParams implements Parcelable { + /** {@hide} */ + public static final int MODE_INVALID = -1; + /** * Mode for an install session whose staged APKs should fully replace any * existing APKs for the target app. @@ -48,21 +52,19 @@ public class InstallSessionParams implements Parcelable { public static final int MODE_INHERIT_EXISTING = 2; /** {@hide} */ - public int mode; + public int mode = MODE_INVALID; /** {@hide} */ public int installFlags; /** {@hide} */ public int installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY; /** {@hide} */ - public Signature[] signatures; - /** {@hide} */ public long sizeBytes = -1; /** {@hide} */ public String appPackageName; /** {@hide} */ public Bitmap appIcon; /** {@hide} */ - public CharSequence appLabel; + public String appLabel; /** {@hide} */ public Uri originatingUri; /** {@hide} */ @@ -86,7 +88,6 @@ public class InstallSessionParams implements Parcelable { mode = source.readInt(); installFlags = source.readInt(); installLocation = source.readInt(); - signatures = (Signature[]) source.readParcelableArray(null); sizeBytes = source.readLong(); appPackageName = source.readString(); appIcon = source.readParcelable(null); @@ -106,16 +107,13 @@ public class InstallSessionParams implements Parcelable { } /** - * Optionally provide a set of certificates for the app being installed. - * <p> - * If the APKs staged in the session aren't consistent with these - * signatures, the install will fail. Regardless of this value, all APKs in - * the app must have the same signing certificates. - * - * @see PackageInfo#signatures + * @deprecated use {@link PackageInstaller.Session#openRead(String)} to + * calculate message digest instead. + * @hide */ + @Deprecated public void setSignatures(@Nullable Signature[] signatures) { - this.signatures = signatures; + throw new UnsupportedOperationException(); } /** @@ -146,7 +144,8 @@ public class InstallSessionParams implements Parcelable { /** * Optionally set an icon representing the app being installed. This should - * be at least {@link android.R.dimen#app_icon_size} in both dimensions. + * be roughly {@link ActivityManager#getLauncherLargeIconSize()} in both + * dimensions. */ public void setAppIcon(@Nullable Bitmap appIcon) { this.appIcon = appIcon; @@ -156,7 +155,7 @@ public class InstallSessionParams implements Parcelable { * Optionally set a label representing the app being installed. */ public void setAppLabel(@Nullable CharSequence appLabel) { - this.appLabel = appLabel; + this.appLabel = (appLabel != null) ? appLabel.toString() : null; } /** @@ -184,7 +183,6 @@ public class InstallSessionParams implements Parcelable { pw.printPair("mode", mode); pw.printHexPair("installFlags", installFlags); pw.printPair("installLocation", installLocation); - pw.printPair("signatures", (signatures != null)); pw.printPair("sizeBytes", sizeBytes); pw.printPair("appPackageName", appPackageName); pw.printPair("appIcon", (appIcon != null)); @@ -205,11 +203,10 @@ public class InstallSessionParams implements Parcelable { dest.writeInt(mode); dest.writeInt(installFlags); dest.writeInt(installLocation); - dest.writeParcelableArray(signatures, flags); dest.writeLong(sizeBytes); dest.writeString(appPackageName); dest.writeParcelable(appIcon, flags); - dest.writeString(appLabel != null ? appLabel.toString() : null); + dest.writeString(appLabel); dest.writeParcelable(originatingUri, flags); dest.writeParcelable(referrerUri, flags); dest.writeString(abiOverride); diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index a114bb8..8af827e 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -18,9 +18,10 @@ package android.content.pm; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.app.PackageInstallObserver; import android.app.PackageUninstallObserver; -import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.FileBridge; import android.os.Handler; @@ -32,7 +33,9 @@ import android.util.ExceptionUtils; import java.io.Closeable; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; +import java.security.MessageDigest; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -63,6 +66,27 @@ import java.util.List; * </ul> */ public class PackageInstaller { + /** + * Activity Action: Show details about a particular install session. This + * may surface actions such as pause, resume, or cancel. + * <p> + * This should always be scoped to the installer package that owns the + * session. Clients should use {@link InstallSessionInfo#getDetailsIntent()} + * to build this intent correctly. + * <p> + * In some cases, a matching Activity may not exist, so ensure you safeguard + * against this. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS"; + + /** + * An integer session ID. + * + * @see #ACTION_SESSION_DETAILS + */ + public static final String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID"; + private final PackageManager mPm; private final IPackageInstaller mInstaller; private final int mUserId; @@ -180,14 +204,32 @@ public class PackageInstaller { /** * Events for observing session lifecycle. + * <p> + * A typical session lifecycle looks like this: + * <ul> + * <li>An installer creates a session to indicate pending app delivery. All + * install details are available at this point. + * <li>The installer opens the session to deliver APK data. Note that a + * session may be opened and closed multiple times as network connectivity + * changes. The installer may deliver periodic progress updates. + * <li>The installer commits or abandons the session, resulting in the + * session being finished. + * </ul> */ public static abstract class SessionCallback { /** - * New session has been created. + * New session has been created. Details about the session can be + * obtained from {@link PackageInstaller#getSessionInfo(int)}. */ public abstract void onCreated(int sessionId); /** + * Session has been opened. A session is usually opened when the + * installer is actively writing data. + */ + public abstract void onOpened(int sessionId); + + /** * Progress for given session has been updated. * <p> * Note that this progress may not directly correspond to the value @@ -198,6 +240,11 @@ public class PackageInstaller { public abstract void onProgressChanged(int sessionId, float progress); /** + * Session has been closed. + */ + public abstract void onClosed(int sessionId); + + /** * Session has completely finished, either with success or failure. */ public abstract void onFinished(int sessionId, boolean success); @@ -207,8 +254,10 @@ public class PackageInstaller { private static class SessionCallbackDelegate extends IPackageInstallerCallback.Stub implements Handler.Callback { private static final int MSG_SESSION_CREATED = 1; - private static final int MSG_SESSION_PROGRESS_CHANGED = 2; - private static final int MSG_SESSION_FINISHED = 3; + private static final int MSG_SESSION_OPENED = 2; + private static final int MSG_SESSION_PROGRESS_CHANGED = 3; + private static final int MSG_SESSION_CLOSED = 4; + private static final int MSG_SESSION_FINISHED = 5; final SessionCallback mCallback; final Handler mHandler; @@ -224,9 +273,15 @@ public class PackageInstaller { case MSG_SESSION_CREATED: mCallback.onCreated(msg.arg1); return true; + case MSG_SESSION_OPENED: + mCallback.onOpened(msg.arg1); + return true; case MSG_SESSION_PROGRESS_CHANGED: mCallback.onProgressChanged(msg.arg1, (float) msg.obj); return true; + case MSG_SESSION_CLOSED: + mCallback.onClosed(msg.arg1); + return true; case MSG_SESSION_FINISHED: mCallback.onFinished(msg.arg1, msg.arg2 != 0); return true; @@ -240,12 +295,22 @@ public class PackageInstaller { } @Override + public void onSessionOpened(int sessionId) { + mHandler.obtainMessage(MSG_SESSION_OPENED, sessionId, 0).sendToTarget(); + } + + @Override public void onSessionProgressChanged(int sessionId, float progress) { mHandler.obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, 0, progress) .sendToTarget(); } @Override + public void onSessionClosed(int sessionId) { + mHandler.obtainMessage(MSG_SESSION_CLOSED, sessionId, 0).sendToTarget(); + } + + @Override public void onSessionFinished(int sessionId, boolean success) { mHandler.obtainMessage(MSG_SESSION_FINISHED, sessionId, success ? 1 : 0) .sendToTarget(); @@ -373,7 +438,7 @@ public class PackageInstaller { ExceptionUtils.maybeUnwrapIOException(e); throw e; } catch (RemoteException e) { - throw new IOException(e); + throw e.rethrowAsRuntimeException(); } } @@ -391,6 +456,40 @@ public class PackageInstaller { } /** + * List all APK names contained in this session. + * <p> + * This returns all names which have been previously written through + * {@link #openWrite(String, long, long)} as part of this session. + */ + public @NonNull String[] list() { + try { + return mSession.list(); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** + * Open a stream to read an APK file from the session. + * <p> + * This is only valid for names which have been previously written + * through {@link #openWrite(String, long, long)} as part of this + * session. For example, this stream may be used to calculate a + * {@link MessageDigest} of a written APK before committing. + */ + public @NonNull InputStream openRead(@NonNull String name) throws IOException { + try { + final ParcelFileDescriptor pfd = mSession.openRead(name); + return new ParcelFileDescriptor.AutoCloseInputStream(pfd); + } catch (RuntimeException e) { + ExceptionUtils.maybeUnwrapIOException(e); + throw e; + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** * 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. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index afac239..c3ac012 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -61,6 +61,12 @@ public class Process { public static final String SECONDARY_ZYGOTE_SOCKET = "zygote_secondary"; /** + * Defines the root UID. + * @hide + */ + public static final int ROOT_UID = 0; + + /** * Defines the UID/GID under which system code runs. */ public static final int SYSTEM_UID = 1000; diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java index dca9921..7db70ba 100644 --- a/core/java/com/android/internal/util/XmlUtils.java +++ b/core/java/com/android/internal/util/XmlUtils.java @@ -16,12 +16,18 @@ package com.android.internal.util; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Bitmap.CompressFormat; +import android.net.Uri; +import android.util.Base64; import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -1415,6 +1421,20 @@ public class XmlUtils { out.attribute(null, name, Long.toString(value)); } + public static float readFloatAttribute(XmlPullParser in, String name) throws IOException { + final String value = in.getAttributeValue(null, name); + try { + return Float.parseFloat(value); + } catch (NumberFormatException e) { + throw new ProtocolException("problem parsing " + name + "=" + value + " as long"); + } + } + + public static void writeFloatAttribute(XmlSerializer out, String name, float value) + throws IOException { + out.attribute(null, name, Float.toString(value)); + } + public static boolean readBooleanAttribute(XmlPullParser in, String name) { final String value = in.getAttributeValue(null, name); return Boolean.parseBoolean(value); @@ -1425,6 +1445,63 @@ public class XmlUtils { out.attribute(null, name, Boolean.toString(value)); } + public static Uri readUriAttribute(XmlPullParser in, String name) { + final String value = in.getAttributeValue(null, name); + return (value != null) ? Uri.parse(value) : null; + } + + public static void writeUriAttribute(XmlSerializer out, String name, Uri value) + throws IOException { + if (value != null) { + out.attribute(null, name, value.toString()); + } + } + + public static String readStringAttribute(XmlPullParser in, String name) { + return in.getAttributeValue(null, name); + } + + public static void writeStringAttribute(XmlSerializer out, String name, String value) + throws IOException { + if (value != null) { + out.attribute(null, name, value); + } + } + + public static byte[] readByteArrayAttribute(XmlPullParser in, String name) { + final String value = in.getAttributeValue(null, name); + if (value != null) { + return Base64.decode(value, Base64.DEFAULT); + } else { + return null; + } + } + + public static void writeByteArrayAttribute(XmlSerializer out, String name, byte[] value) + throws IOException { + if (value != null) { + out.attribute(null, name, Base64.encodeToString(value, Base64.DEFAULT)); + } + } + + public static Bitmap readBitmapAttribute(XmlPullParser in, String name) { + final byte[] value = readByteArrayAttribute(in, name); + if (value != null) { + return BitmapFactory.decodeByteArray(value, 0, value.length); + } else { + return null; + } + } + + public static void writeBitmapAttribute(XmlSerializer out, String name, Bitmap value) + throws IOException { + if (value != null) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + value.compress(CompressFormat.PNG, 90, os); + writeByteArrayAttribute(out, name, os.toByteArray()); + } + } + /** @hide */ public interface WriteMapCallback { /** |