summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2014-07-31 22:24:57 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2014-07-25 23:00:08 +0000
commitceb2adca4905bc1f80545792d82bed5d877ed583 (patch)
tree86b5aa99228e89d26747470841f2570d6c6eef98 /core
parentf5f45bc224d69f478ce446d85720a514a3d12f7e (diff)
parent1cb2d0d4bba387665128c62c342e59103ea4be26 (diff)
downloadframeworks_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')
-rw-r--r--core/java/android/content/pm/IPackageInstallerCallback.aidl2
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl2
-rw-r--r--core/java/android/content/pm/InstallSessionInfo.java30
-rw-r--r--core/java/android/content/pm/InstallSessionParams.java33
-rw-r--r--core/java/android/content/pm/PackageInstaller.java109
-rw-r--r--core/java/android/os/Process.java6
-rw-r--r--core/java/com/android/internal/util/XmlUtils.java77
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 {
/**