summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2014-07-30 16:45:01 -0700
committerJeff Sharkey <jsharkey@android.com>2014-07-31 15:17:03 -0700
commit1cb2d0d4bba387665128c62c342e59103ea4be26 (patch)
tree2659fda22f585adf01574c24f1f23dbb3caccef2
parent874bcd82c223ce58c9d76edcf619b3988c672307 (diff)
downloadframeworks_base-1cb2d0d4bba387665128c62c342e59103ea4be26.zip
frameworks_base-1cb2d0d4bba387665128c62c342e59103ea4be26.tar.gz
frameworks_base-1cb2d0d4bba387665128c62c342e59103ea4be26.tar.bz2
Persist install sessions, more lifecycle.
To resume install sessions across device boots, persist session details and read at boot. Drop sessions older than 3 days, since they're probably buggy installers. Add session callback lifecycle around open/close to give home apps details about active installs. Also give them a well-known intent to show session details. Extend Session to list staged APKs and open them read-only, giving installers a mechanism to verify delivered bits, for example using MessageDigest, before committing. Switch to generating random session IDs instead of sequential. Defensively resize app icons if too large. Reject runaway installers when they have too many active sessions. Bug: 16514389 Change-Id: I66c2266cb82fc72b1eb980a615566773f4290498
-rw-r--r--api/current.txt9
-rw-r--r--cmds/pm/src/com/android/commands/pm/Pm.java22
-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
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java424
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java202
11 files changed, 757 insertions, 159 deletions
diff --git a/api/current.txt b/api/current.txt
index c5027ae..ef3d494 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -8515,9 +8515,11 @@ package android.content.pm {
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public android.content.Intent getDetailsIntent();
method public java.lang.String getInstallerPackageName();
method public float getProgress();
method public int getSessionId();
+ method public boolean isOpen();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
}
@@ -8531,7 +8533,6 @@ package android.content.pm {
method public void setInstallLocation(int);
method public void setOriginatingUri(android.net.Uri);
method public void setReferrerUri(android.net.Uri);
- method public void setSignatures(android.content.pm.Signature[]);
method public void setSize(long);
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator CREATOR;
@@ -8647,6 +8648,8 @@ package android.content.pm {
method public android.content.pm.PackageInstaller.Session openSession(int);
method public void removeSessionCallback(android.content.pm.PackageInstaller.SessionCallback);
method public void uninstall(java.lang.String, android.content.pm.PackageInstaller.UninstallCallback);
+ field public static final java.lang.String ACTION_SESSION_DETAILS = "android.content.pm.action.SESSION_DETAILS";
+ field public static final java.lang.String EXTRA_SESSION_ID = "android.content.pm.extra.SESSION_ID";
}
public static abstract class PackageInstaller.CommitCallback {
@@ -8666,14 +8669,18 @@ package android.content.pm {
method public void close();
method public void commit(android.content.pm.PackageInstaller.CommitCallback);
method public void fsync(java.io.OutputStream) throws java.io.IOException;
+ method public java.lang.String[] list();
+ method public java.io.InputStream openRead(java.lang.String) throws java.io.IOException;
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void setProgress(float);
}
public static abstract class PackageInstaller.SessionCallback {
ctor public PackageInstaller.SessionCallback();
+ method public abstract void onClosed(int);
method public abstract void onCreated(int);
method public abstract void onFinished(int, boolean);
+ method public abstract void onOpened(int);
method public abstract void onProgressChanged(int, float);
}
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index bc16800..1f25dd0 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -59,7 +59,6 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.SizedInputStream;
import libcore.io.IoUtils;
-import libcore.io.Streams;
import java.io.File;
import java.io.FileDescriptor;
@@ -1068,6 +1067,8 @@ public final class Pm {
}
}
+ final InstallSessionInfo info = mInstaller.getSessionInfo(sessionId);
+
PackageInstaller.Session session = null;
InputStream in = null;
OutputStream out = null;
@@ -1081,16 +1082,21 @@ public final class Pm {
}
out = session.openWrite(splitName, 0, sizeBytes);
- final int n = Streams.copy(in, out);
- session.fsync(out);
+ int total = 0;
+ byte[] buffer = new byte[65536];
+ int c;
+ while ((c = in.read(buffer)) != -1) {
+ total += c;
+ out.write(buffer, 0, c);
- final InstallSessionInfo info = mInstaller.getSessionInfo(sessionId);
- if (info.sizeBytes > 0) {
- final float fraction = ((float) n / (float) info.sizeBytes);
- session.addProgress(fraction);
+ if (info.sizeBytes > 0) {
+ final float fraction = ((float) c / (float) info.sizeBytes);
+ session.addProgress(fraction);
+ }
}
+ session.fsync(out);
- System.out.println("Success: streamed " + n + " bytes");
+ System.out.println("Success: streamed " + total + " bytes");
} finally {
IoUtils.closeQuietly(out);
IoUtils.closeQuietly(in);
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 {
/**
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 6036bcf..1650768 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -19,7 +19,22 @@ package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_ALL_USERS;
import static android.content.pm.PackageManager.INSTALL_FROM_ADB;
import static android.content.pm.PackageManager.INSTALL_REPLACE_EXISTING;
-
+import static com.android.internal.util.XmlUtils.readBitmapAttribute;
+import static com.android.internal.util.XmlUtils.readBooleanAttribute;
+import static com.android.internal.util.XmlUtils.readIntAttribute;
+import static com.android.internal.util.XmlUtils.readLongAttribute;
+import static com.android.internal.util.XmlUtils.readStringAttribute;
+import static com.android.internal.util.XmlUtils.readUriAttribute;
+import static com.android.internal.util.XmlUtils.writeBitmapAttribute;
+import static com.android.internal.util.XmlUtils.writeBooleanAttribute;
+import static com.android.internal.util.XmlUtils.writeIntAttribute;
+import static com.android.internal.util.XmlUtils.writeLongAttribute;
+import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.internal.util.XmlUtils.writeUriAttribute;
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.START_TAG;
+
+import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.IPackageDeleteObserver;
@@ -29,9 +44,14 @@ import android.content.pm.IPackageInstallerSession;
import android.content.pm.InstallSessionInfo;
import android.content.pm.InstallSessionParams;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
import android.os.Binder;
+import android.os.Environment;
import android.os.FileUtils;
+import android.os.Handler;
import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
@@ -40,30 +60,70 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.system.ErrnoException;
import android.system.Os;
+import android.text.format.DateUtils;
import android.util.ArraySet;
+import android.util.AtomicFile;
import android.util.ExceptionUtils;
+import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
+import com.android.server.pm.PackageInstallerSession.Snapshot;
import com.google.android.collect.Sets;
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
+import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Random;
public class PackageInstallerService extends IPackageInstaller.Stub {
private static final String TAG = "PackageInstaller";
+ private static final boolean LOGD = true;
- // TODO: destroy sessions with old timestamps
// TODO: remove outstanding sessions when installer package goes away
// TODO: notify listeners in other users when package has been installed there
+ /** XML constants used in {@link #mSessionsFile} */
+ private static final String TAG_SESSIONS = "sessions";
+ private static final String TAG_SESSION = "session";
+ private static final String ATTR_SESSION_ID = "sessionId";
+ private static final String ATTR_USER_ID = "userId";
+ private static final String ATTR_INSTALLER_PACKAGE_NAME = "installerPackageName";
+ private static final String ATTR_CREATED_MILLIS = "createdMillis";
+ private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
+ private static final String ATTR_SEALED = "sealed";
+ private static final String ATTR_MODE = "mode";
+ private static final String ATTR_INSTALL_FLAGS = "installFlags";
+ private static final String ATTR_INSTALL_LOCATION = "installLocation";
+ private static final String ATTR_SIZE_BYTES = "sizeBytes";
+ private static final String ATTR_APP_PACKAGE_NAME = "appPackageName";
+ private static final String ATTR_APP_ICON = "appIcon";
+ private static final String ATTR_APP_LABEL = "appLabel";
+ private static final String ATTR_ORIGINATING_URI = "originatingUri";
+ private static final String ATTR_REFERRER_URI = "referrerUri";
+ private static final String ATTR_ABI_OVERRIDE = "abiOverride";
+
+ private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
+ private static final long MAX_ACTIVE_SESSIONS = 1024;
+
private final Context mContext;
private final PackageManagerService mPm;
private final AppOpsManager mAppOps;
@@ -71,10 +131,21 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
private final File mStagingDir;
private final HandlerThread mInstallThread;
- private final Callback mCallback = new Callback();
+ private final Callbacks mCallbacks;
+
+ /**
+ * File storing persisted {@link #mSessions}.
+ */
+ private final AtomicFile mSessionsFile;
+
+ private final InternalCallback mInternalCallback = new InternalCallback();
+
+ /**
+ * Used for generating session IDs. Since this is created at boot time,
+ * normal random might be predictable.
+ */
+ private final Random mRandom = new SecureRandom();
- @GuardedBy("mSessions")
- private int mNextSessionId;
@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();
@@ -82,8 +153,6 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
@GuardedBy("mSessions")
private final SparseArray<PackageInstallerSession> mHistoricalSessions = new SparseArray<>();
- private RemoteCallbackList<IPackageInstallerCallback> mCallbacks = new RemoteCallbackList<>();
-
private static final FilenameFilter sStageFilter = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
@@ -101,6 +170,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
mInstallThread = new HandlerThread(TAG);
mInstallThread.start();
+ mCallbacks = new Callbacks(mInstallThread.getLooper());
+
+ mSessionsFile = new AtomicFile(
+ new File(Environment.getSystemSecureDirectory(), "install_sessions.xml"));
+
synchronized (mSessions) {
readSessionsLocked();
@@ -133,13 +207,140 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
private void readSessionsLocked() {
- // TODO: implement persisting
+ if (LOGD) Slog.v(TAG, "readSessionsLocked()");
+
mSessions.clear();
- mNextSessionId = 1;
+
+ FileInputStream fis = null;
+ try {
+ fis = mSessionsFile.openRead();
+ final XmlPullParser in = Xml.newPullParser();
+ in.setInput(fis, null);
+
+ int type;
+ while ((type = in.next()) != END_DOCUMENT) {
+ if (type == START_TAG) {
+ final String tag = in.getName();
+ if (TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session = readSessionLocked(in);
+ final long age = System.currentTimeMillis() - session.createdMillis;
+
+ final boolean valid;
+ if (age >= MAX_AGE_MILLIS) {
+ Slog.w(TAG, "Abandoning old session first created at "
+ + session.createdMillis);
+ valid = false;
+ } else if (!session.sessionStageDir.exists()) {
+ Slog.w(TAG, "Abandoning session with missing stage "
+ + session.sessionStageDir);
+ valid = false;
+ } else {
+ valid = true;
+ }
+
+ if (valid) {
+ mSessions.put(session.sessionId, session);
+ } else {
+ // Since this is early during boot we don't send
+ // any observer events about the session, but we
+ // keep details around for dumpsys.
+ mHistoricalSessions.put(session.sessionId, session);
+ }
+ }
+ }
+ }
+ } catch (FileNotFoundException e) {
+ // Missing sessions are okay, probably first boot
+ } catch (IOException e) {
+ Log.wtf(TAG, "Failed reading install sessions", e);
+ } catch (XmlPullParserException e) {
+ Log.wtf(TAG, "Failed reading install sessions", e);
+ } finally {
+ IoUtils.closeQuietly(fis);
+ }
+ }
+
+ private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException {
+ final int sessionId = readIntAttribute(in, ATTR_SESSION_ID);
+ final int userId = readIntAttribute(in, ATTR_USER_ID);
+ final String installerPackageName = readStringAttribute(in, ATTR_INSTALLER_PACKAGE_NAME);
+ final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
+ final File sessionStageDir = new File(readStringAttribute(in, ATTR_SESSION_STAGE_DIR));
+ final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
+
+ final InstallSessionParams params = new InstallSessionParams(
+ InstallSessionParams.MODE_INVALID);
+ params.mode = readIntAttribute(in, ATTR_MODE);
+ params.installFlags = readIntAttribute(in, ATTR_INSTALL_FLAGS);
+ params.installLocation = readIntAttribute(in, ATTR_INSTALL_LOCATION);
+ params.sizeBytes = readLongAttribute(in, ATTR_SIZE_BYTES);
+ params.appPackageName = readStringAttribute(in, ATTR_APP_PACKAGE_NAME);
+ params.appIcon = readBitmapAttribute(in, ATTR_APP_ICON);
+ params.appLabel = readStringAttribute(in, ATTR_APP_LABEL);
+ params.originatingUri = readUriAttribute(in, ATTR_ORIGINATING_URI);
+ params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
+ params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+
+ return new PackageInstallerSession(mInternalCallback, mPm, mInstallThread.getLooper(),
+ sessionId, userId, installerPackageName, params, createdMillis, sessionStageDir,
+ sealed);
}
private void writeSessionsLocked() {
- // TODO: implement persisting
+ if (LOGD) Slog.v(TAG, "writeSessionsLocked()");
+
+ FileOutputStream fos = null;
+ try {
+ fos = mSessionsFile.startWrite();
+
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.startTag(null, TAG_SESSIONS);
+ final int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ writeSessionLocked(out, session);
+ }
+ out.endTag(null, TAG_SESSIONS);
+ out.endDocument();
+
+ mSessionsFile.finishWrite(fos);
+ } catch (IOException e) {
+ if (fos != null) {
+ mSessionsFile.failWrite(fos);
+ }
+ }
+ }
+
+ private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
+ throws IOException {
+ final InstallSessionParams params = session.params;
+ final Snapshot snapshot = session.snapshot();
+
+ out.startTag(null, TAG_SESSION);
+
+ writeIntAttribute(out, ATTR_SESSION_ID, session.sessionId);
+ writeIntAttribute(out, ATTR_USER_ID, session.userId);
+ writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
+ session.installerPackageName);
+ writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis);
+ writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
+ session.sessionStageDir.getAbsolutePath());
+ writeBooleanAttribute(out, ATTR_SEALED, snapshot.sealed);
+
+ writeIntAttribute(out, ATTR_MODE, params.mode);
+ writeIntAttribute(out, ATTR_INSTALL_FLAGS, params.installFlags);
+ writeIntAttribute(out, ATTR_INSTALL_LOCATION, params.installLocation);
+ writeLongAttribute(out, ATTR_SIZE_BYTES, params.sizeBytes);
+ writeStringAttribute(out, ATTR_APP_PACKAGE_NAME, params.appPackageName);
+ writeBitmapAttribute(out, ATTR_APP_ICON, params.appIcon);
+ writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
+ writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
+ writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
+ writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+
+ out.endTag(null, TAG_SESSION);
}
private void writeSessionsAsync() {
@@ -163,8 +364,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
throw new SecurityException("User restriction prevents installing");
}
- if ((callingUid == Process.SHELL_UID) || (callingUid == 0)) {
+ if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
+ installerPackageName = "com.android.shell";
+
params.installFlags |= INSTALL_FROM_ADB;
+
} else {
mAppOps.checkPackage(callingUid, installerPackageName);
@@ -181,6 +385,18 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
throw new IllegalArgumentException("Params must have valid mode set");
}
+ // Defensively resize giant app icons
+ if (params.appIcon != null) {
+ final ActivityManager am = (ActivityManager) mContext.getSystemService(
+ Context.ACTIVITY_SERVICE);
+ final int iconSize = am.getLauncherLargeIconSize();
+ if ((params.appIcon.getWidth() > iconSize * 2)
+ || (params.appIcon.getHeight() > iconSize * 2)) {
+ params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
+ true);
+ }
+ }
+
// Sanity check that install could fit
if (params.sizeBytes > 0) {
try {
@@ -193,18 +409,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
final int sessionId;
final PackageInstallerSession session;
synchronized (mSessions) {
+ // Sanity check that installer isn't going crazy
+ final int activeCount = getSessionCountLocked(callingUid);
+ if (activeCount >= MAX_ACTIVE_SESSIONS) {
+ throw new IllegalStateException("Too many active sessions for UID " + callingUid);
+ }
+
sessionId = allocateSessionIdLocked();
final long createdMillis = System.currentTimeMillis();
final File sessionStageDir = prepareSessionStageDir(sessionId);
- session = new PackageInstallerSession(mCallback, mPm, sessionId, userId,
- installerPackageName, callingUid, params, createdMillis, sessionStageDir,
- mInstallThread.getLooper());
+ session = new PackageInstallerSession(mInternalCallback, mPm,
+ mInstallThread.getLooper(), sessionId, userId, installerPackageName, params,
+ createdMillis, sessionStageDir, false);
mSessions.put(sessionId, session);
}
- notifySessionCreated(session.generateInfo());
+ mCallbacks.notifySessionCreated(session.sessionId, session.userId);
writeSessionsAsync();
return sessionId;
}
@@ -216,25 +438,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
if (session == null) {
throw new IllegalStateException("Missing session " + sessionId);
}
- if (Binder.getCallingUid() != session.installerUid) {
+ if (!isCallingUidOwner(session)) {
throw new SecurityException("Caller has no access to session " + sessionId);
}
+ if (session.openCount.getAndIncrement() == 0) {
+ mCallbacks.notifySessionOpened(sessionId, session.userId);
+ }
return session;
}
}
private int allocateSessionIdLocked() {
- if (mSessions.get(mNextSessionId) != null) {
- throw new IllegalStateException("Next session already allocated");
- }
- return mNextSessionId++;
+ int n = 0;
+ int sessionId;
+ do {
+ sessionId = mRandom.nextInt(Integer.MAX_VALUE);
+ if (mSessions.get(sessionId) == null) {
+ return sessionId;
+ }
+ } while (n++ < 32);
+
+ throw new IllegalStateException("Failed to allocate session ID");
}
private File prepareSessionStageDir(int sessionId) {
final File file = new File(mStagingDir, "vmdl" + sessionId + ".tmp");
if (file.exists()) {
- throw new IllegalStateException();
+ throw new IllegalStateException("Session dir already exists: " + file);
}
try {
@@ -246,7 +477,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
if (!SELinux.restorecon(file)) {
- throw new IllegalStateException("Failed to prepare session dir");
+ throw new IllegalStateException("Failed to restorecon session dir");
}
return file;
@@ -256,9 +487,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
public InstallSessionInfo getSessionInfo(int sessionId) {
synchronized (mSessions) {
final PackageInstallerSession session = mSessions.get(sessionId);
- final boolean isOwner = (session != null)
- && (session.installerUid == Binder.getCallingUid());
- if (!isOwner) {
+ if (!isCallingUidOwner(session)) {
enforceCallerCanReadSessions();
}
return session != null ? session.generateInfo() : null;
@@ -323,7 +552,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "registerCallback");
enforceCallerCanReadSessions();
- mCallbacks.register(callback, new UserHandle(userId));
+ mCallbacks.register(callback, userId);
}
@Override
@@ -331,9 +560,24 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
mCallbacks.unregister(callback);
}
- private int getSessionUserId(int sessionId) {
- synchronized (mSessions) {
- return UserHandle.getUserId(mSessions.get(sessionId).installerUid);
+ private int getSessionCountLocked(int installerUid) {
+ int count = 0;
+ final int size = mSessions.size();
+ for (int i = 0; i < size; i++) {
+ final PackageInstallerSession session = mSessions.valueAt(i);
+ if (session.installerUid == installerUid) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ private boolean isCallingUidOwner(PackageInstallerSession session) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid == Process.ROOT_UID) {
+ return true;
+ } else {
+ return (session != null) && (callingUid == session.installerUid);
}
}
@@ -352,53 +596,87 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
- private void notifySessionCreated(InstallSessionInfo info) {
- final int userId = getSessionUserId(info.sessionId);
- final int n = mCallbacks.beginBroadcast();
- for (int i = 0; i < n; i++) {
- final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
- final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
- // TODO: dispatch notifications for slave profiles
- if (userId == user.getIdentifier()) {
- try {
- callback.onSessionCreated(info.sessionId);
- } catch (RemoteException ignored) {
- }
- }
+ private static class Callbacks extends Handler {
+ private static final int MSG_SESSION_CREATED = 1;
+ 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;
+
+ private final RemoteCallbackList<IPackageInstallerCallback>
+ mCallbacks = new RemoteCallbackList<>();
+
+ public Callbacks(Looper looper) {
+ super(looper);
}
- mCallbacks.finishBroadcast();
- }
- private void notifySessionProgressChanged(int sessionId, float progress) {
- final int userId = getSessionUserId(sessionId);
- final int n = mCallbacks.beginBroadcast();
- for (int i = 0; i < n; i++) {
- final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
- final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
- if (userId == user.getIdentifier()) {
- try {
- callback.onSessionProgressChanged(sessionId, progress);
- } catch (RemoteException ignored) {
+ public void register(IPackageInstallerCallback callback, int userId) {
+ mCallbacks.register(callback, new UserHandle(userId));
+ }
+
+ public void unregister(IPackageInstallerCallback callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ final int userId = msg.arg2;
+ final int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
+ final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
+ // TODO: dispatch notifications for slave profiles
+ if (userId == user.getIdentifier()) {
+ try {
+ invokeCallback(callback, msg);
+ } catch (RemoteException ignored) {
+ }
}
}
+ mCallbacks.finishBroadcast();
}
- mCallbacks.finishBroadcast();
- }
- private void notifySessionFinished(int sessionId, boolean success) {
- final int userId = getSessionUserId(sessionId);
- final int n = mCallbacks.beginBroadcast();
- for (int i = 0; i < n; i++) {
- final IPackageInstallerCallback callback = mCallbacks.getBroadcastItem(i);
- final UserHandle user = (UserHandle) mCallbacks.getBroadcastCookie(i);
- if (userId == user.getIdentifier()) {
- try {
- callback.onSessionFinished(sessionId, success);
- } catch (RemoteException ignored) {
- }
+ private void invokeCallback(IPackageInstallerCallback callback, Message msg)
+ throws RemoteException {
+ final int sessionId = msg.arg1;
+ switch (msg.what) {
+ case MSG_SESSION_CREATED:
+ callback.onSessionCreated(sessionId);
+ break;
+ case MSG_SESSION_OPENED:
+ callback.onSessionOpened(sessionId);
+ break;
+ case MSG_SESSION_PROGRESS_CHANGED:
+ callback.onSessionProgressChanged(sessionId, (float) msg.obj);
+ break;
+ case MSG_SESSION_CLOSED:
+ callback.onSessionClosed(sessionId);
+ break;
+ case MSG_SESSION_FINISHED:
+ callback.onSessionFinished(sessionId, (boolean) msg.obj);
+ break;
}
}
- mCallbacks.finishBroadcast();
+
+ private void notifySessionCreated(int sessionId, int userId) {
+ obtainMessage(MSG_SESSION_CREATED, sessionId, userId).sendToTarget();
+ }
+
+ private void notifySessionOpened(int sessionId, int userId) {
+ obtainMessage(MSG_SESSION_OPENED, sessionId, userId).sendToTarget();
+ }
+
+ private void notifySessionProgressChanged(int sessionId, int userId, float progress) {
+ obtainMessage(MSG_SESSION_PROGRESS_CHANGED, sessionId, userId, progress).sendToTarget();
+ }
+
+ private void notifySessionClosed(int sessionId, int userId) {
+ obtainMessage(MSG_SESSION_CLOSED, sessionId, userId).sendToTarget();
+ }
+
+ public void notifySessionFinished(int sessionId, int userId, boolean success) {
+ obtainMessage(MSG_SESSION_FINISHED, sessionId, userId, success).sendToTarget();
+ }
}
void dump(IndentingPrintWriter pw) {
@@ -427,13 +705,17 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
}
}
- class Callback {
+ class InternalCallback {
public void onSessionProgressChanged(PackageInstallerSession session, float progress) {
- notifySessionProgressChanged(session.sessionId, progress);
+ mCallbacks.notifySessionProgressChanged(session.sessionId, session.userId, progress);
+ }
+
+ public void onSessionClosed(PackageInstallerSession session) {
+ mCallbacks.notifySessionClosed(session.sessionId, session.userId);
}
public void onSessionFinished(PackageInstallerSession session, boolean success) {
- notifySessionFinished(session.sessionId, success);
+ mCallbacks.notifySessionFinished(session.sessionId, session.userId, success);
synchronized (mSessions) {
mSessions.remove(session.sessionId);
mHistoricalSessions.put(session.sessionId, session);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 06e1d53..26019db 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -21,6 +21,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
import static android.system.OsConstants.O_CREAT;
+import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
import android.content.pm.ApplicationInfo;
@@ -40,7 +41,6 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.system.ErrnoException;
@@ -52,6 +52,7 @@ import android.util.ExceptionUtils;
import android.util.MathUtils;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
@@ -62,33 +63,64 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String TAG = "PackageInstaller";
private static final boolean LOGD = true;
+ private static final int MSG_COMMIT = 0;
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
// TODO: handle INSTALL_EXTERNAL, INSTALL_INTERNAL
// TODO: treat INHERIT_EXISTING as installExistingPackage()
- private final PackageInstallerService.Callback mCallback;
+ private final PackageInstallerService.InternalCallback mCallback;
private final PackageManagerService mPm;
private final Handler mHandler;
- public final int sessionId;
- public final int userId;
- public final String installerPackageName;
- /** UID not persisted */
- public final int installerUid;
- public final InstallSessionParams params;
- public final long createdMillis;
- public final File sessionStageDir;
+ final int sessionId;
+ final int userId;
+ final String installerPackageName;
+ final InstallSessionParams params;
+ final long createdMillis;
+ final File sessionStageDir;
- private static final int MSG_COMMIT = 0;
+ /** Note that UID is not persisted; it's always derived at runtime. */
+ final int installerUid;
+
+ AtomicInteger openCount = new AtomicInteger();
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private float mClientProgress = 0;
+ @GuardedBy("mLock")
+ private float mProgress = 0;
+ @GuardedBy("mLock")
+ private float mReportedProgress = -1;
+
+ @GuardedBy("mLock")
+ private boolean mSealed = false;
+ @GuardedBy("mLock")
+ private boolean mPermissionsConfirmed = false;
+ @GuardedBy("mLock")
+ private boolean mDestroyed = false;
+
+ @GuardedBy("mLock")
+ private ArrayList<FileBridge> mBridges = new ArrayList<>();
- private Handler.Callback mHandlerCallback = new Handler.Callback() {
+ @GuardedBy("mLock")
+ private IPackageInstallObserver2 mRemoteObserver;
+
+ /** Fields derived from commit parsing */
+ private String mPackageName;
+ private int mVersionCode;
+ private Signature[] mSignatures;
+
+ private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
synchronized (mLock) {
@@ -114,27 +146,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
};
- private final Object mLock = new Object();
-
- private float mClientProgress;
- private float mProgress = 0;
-
- private String mPackageName;
- private int mVersionCode;
- private Signature[] mSignatures;
-
- private boolean mMutationsAllowed;
- private boolean mPermissionsConfirmed;
- private boolean mInvalid;
-
- private ArrayList<FileBridge> mBridges = new ArrayList<>();
-
- private IPackageInstallObserver2 mRemoteObserver;
-
- public PackageInstallerSession(PackageInstallerService.Callback callback,
- PackageManagerService pm, int sessionId, int userId, String installerPackageName,
- int installerUid, InstallSessionParams params, long createdMillis, File sessionStageDir,
- Looper looper) {
+ public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
+ PackageManagerService pm, Looper looper, int sessionId, int userId,
+ String installerPackageName, InstallSessionParams params, long createdMillis,
+ File sessionStageDir, boolean sealed) {
mCallback = callback;
mPm = pm;
mHandler = new Handler(looper, mHandlerCallback);
@@ -142,24 +157,23 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
this.sessionId = sessionId;
this.userId = userId;
this.installerPackageName = installerPackageName;
- this.installerUid = installerUid;
this.params = params;
this.createdMillis = createdMillis;
this.sessionStageDir = sessionStageDir;
- // Check against any explicitly provided signatures
- mSignatures = params.signatures;
+ mSealed = sealed;
- // TODO: splice in flag when restoring persisted session
- mMutationsAllowed = true;
+ // Always derived at runtime
+ installerUid = mPm.getPackageUid(installerPackageName, userId);
- if (pm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES, installerPackageName)
- == PackageManager.PERMISSION_GRANTED) {
- mPermissionsConfirmed = true;
- }
- if (installerUid == Process.SHELL_UID || installerUid == 0) {
+ if (mPm.checkPermission(android.Manifest.permission.INSTALL_PACKAGES,
+ installerPackageName) == PackageManager.PERMISSION_GRANTED) {
mPermissionsConfirmed = true;
+ } else {
+ mPermissionsConfirmed = false;
}
+
+ computeProgressLocked();
}
public InstallSessionInfo generateInfo() {
@@ -168,6 +182,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
info.sessionId = sessionId;
info.installerPackageName = installerPackageName;
info.progress = mProgress;
+ info.open = openCount.get() > 0;
info.mode = params.mode;
info.sizeBytes = params.sizeBytes;
@@ -178,16 +193,48 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return info;
}
+ private void assertNotSealed(String cookie) {
+ synchronized (mLock) {
+ if (mSealed) {
+ throw new SecurityException(cookie + " not allowed after commit");
+ }
+ }
+ }
+
@Override
public void setClientProgress(float progress) {
- mClientProgress = progress;
- mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
- mCallback.onSessionProgressChanged(this, mProgress);
+ synchronized (mLock) {
+ mClientProgress = progress;
+ computeProgressLocked();
+ }
+ maybePublishProgress();
}
@Override
public void addClientProgress(float progress) {
- setClientProgress(mClientProgress + progress);
+ synchronized (mLock) {
+ mClientProgress += progress;
+ computeProgressLocked();
+ }
+ maybePublishProgress();
+ }
+
+ private void computeProgressLocked() {
+ mProgress = MathUtils.constrain(mClientProgress * 0.8f, 0f, 0.8f);
+ }
+
+ private void maybePublishProgress() {
+ // Only publish when meaningful change
+ if (Math.abs(mProgress - mReportedProgress) > 0.01) {
+ mReportedProgress = mProgress;
+ mCallback.onSessionProgressChanged(this, mProgress);
+ }
+ }
+
+ @Override
+ public String[] list() {
+ assertNotSealed("list");
+ return sessionStageDir.list();
}
@Override
@@ -208,9 +255,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
// will block any attempted install transitions.
final FileBridge bridge;
synchronized (mLock) {
- if (!mMutationsAllowed) {
- throw new IllegalStateException("Mutations not allowed");
- }
+ assertNotSealed("openWrite");
bridge = new FileBridge();
mBridges.add(bridge);
@@ -252,25 +297,51 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@Override
+ public ParcelFileDescriptor openRead(String name) {
+ try {
+ return openReadInternal(name);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
+ }
+
+ private ParcelFileDescriptor openReadInternal(String name) throws IOException {
+ assertNotSealed("openRead");
+
+ try {
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+ final File target = new File(sessionStageDir, name);
+
+ final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
+ return new ParcelFileDescriptor(targetFd);
+
+ } catch (ErrnoException e) {
+ throw e.rethrowAsIOException();
+ }
+ }
+
+ @Override
public void commit(IPackageInstallObserver2 observer) {
Preconditions.checkNotNull(observer);
mHandler.obtainMessage(MSG_COMMIT, observer).sendToTarget();
}
private void commitLocked() throws PackageManagerException {
- if (mInvalid) {
+ if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_ALREADY_EXISTS, "Invalid session");
}
// Verify that all writers are hands-off
- if (mMutationsAllowed) {
+ if (!mSealed) {
for (FileBridge bridge : mBridges) {
if (!bridge.isClosed()) {
throw new PackageManagerException(INSTALL_FAILED_PACKAGE_CHANGED,
"Files still open");
}
}
- mMutationsAllowed = false;
+ mSealed = true;
// TODO: persist disabled mutations before going forward, since
// beyond this point we may have hardlinks to the valid install
@@ -331,6 +402,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void validateInstallLocked() throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
+ mSignatures = null;
final File[] files = sessionStageDir.listFiles();
if (ArrayUtils.isEmpty(files)) {
@@ -461,7 +533,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void close() {
- // Currently ignored
+ if (openCount.decrementAndGet() == 0) {
+ mCallback.onSessionClosed(this);
+ }
}
@Override
@@ -475,7 +549,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private void destroyInternal() {
synchronized (mLock) {
- mInvalid = true;
+ mSealed = true;
+ mDestroyed = true;
}
FileUtils.deleteContents(sessionStageDir);
sessionStageDir.delete();
@@ -496,11 +571,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
pw.printPair("mClientProgress", mClientProgress);
pw.printPair("mProgress", mProgress);
- pw.printPair("mMutationsAllowed", mMutationsAllowed);
+ pw.printPair("mSealed", mSealed);
pw.printPair("mPermissionsConfirmed", mPermissionsConfirmed);
+ pw.printPair("mDestroyed", mDestroyed);
pw.printPair("mBridges", mBridges.size());
pw.println();
pw.decreaseIndent();
}
+
+ Snapshot snapshot() {
+ return new Snapshot(this);
+ }
+
+ static class Snapshot {
+ final float clientProgress;
+ final boolean sealed;
+
+ public Snapshot(PackageInstallerSession session) {
+ clientProgress = session.mClientProgress;
+ sealed = session.mSealed;
+ }
+ }
}