Merge changes from topic 'TransferInstallSession-oc-mr1-dev' into oc-mr1-dev
* changes:
Allow to read all params used to create session.
Allow to transfer+seal a install session
diff --git a/api/current.txt b/api/current.txt
index dc92604..3c9cd8e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10506,6 +10506,7 @@
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -10523,10 +10524,16 @@
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index da9f9eb..6c1983d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -11180,12 +11180,14 @@
method public void abandon();
method public void close();
method public void commit(android.content.IntentSender);
+ method public void commitTransferred(android.content.IntentSender);
method public void fsync(java.io.OutputStream) throws java.io.IOException;
method public java.lang.String[] getNames() throws java.io.IOException;
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 removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -11200,13 +11202,26 @@
public static class PackageInstaller.SessionInfo implements android.os.Parcelable {
method public android.content.Intent createDetailsIntent();
method public int describeContents();
+ method public boolean getAllocateAggressive();
+ method public boolean getAllowDowngrade();
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public boolean getDontKillApp();
+ method public java.lang.String[] getGrantedRuntimePermissions();
+ method public boolean getInstallAsFullApp(boolean);
+ method public boolean getInstallAsInstantApp(boolean);
+ method public boolean getInstallAsVirtualPreload();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index 3e27470..839964f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -10544,6 +10544,7 @@
method public java.io.OutputStream openWrite(java.lang.String, long, long) throws java.io.IOException;
method public void removeSplit(java.lang.String) throws java.io.IOException;
method public void setStagingProgress(float);
+ method public void transfer(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
}
public static abstract class PackageInstaller.SessionCallback {
@@ -10561,10 +10562,16 @@
method public android.graphics.Bitmap getAppIcon();
method public java.lang.CharSequence getAppLabel();
method public java.lang.String getAppPackageName();
+ method public int getInstallLocation();
method public int getInstallReason();
method public java.lang.String getInstallerPackageName();
+ method public int getMode();
+ method public int getOriginatingUid();
+ method public android.net.Uri getOriginatingUri();
method public float getProgress();
+ method public android.net.Uri getReferrerUri();
method public int getSessionId();
+ method public long getSize();
method public boolean isActive();
method public boolean isSealed();
method public void writeToParcel(android.os.Parcel, int);
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 2a3fac3..0b16852 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -32,6 +32,7 @@
void removeSplit(String splitName);
void close();
- void commit(in IntentSender statusReceiver);
+ void commit(in IntentSender statusReceiver, boolean forTransferred);
+ void transfer(in String packageName);
void abandon();
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c3ebf55..f4fdcaa 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -38,6 +38,7 @@
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
+import android.os.ParcelableException;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.system.ErrnoException;
@@ -793,7 +794,7 @@
* @throws IOException if trouble opening the file for writing, such as
* lack of disk space or unavailable media.
* @throws SecurityException if called after the session has been
- * committed or abandoned.
+ * sealed or abandoned
*/
public @NonNull OutputStream openWrite(@NonNull String name, long offsetBytes,
long lengthBytes) throws IOException {
@@ -918,7 +919,68 @@
*/
public void commit(@NonNull IntentSender statusReceiver) {
try {
- mSession.commit(statusReceiver);
+ mSession.commit(statusReceiver, false);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Attempt to commit a session that has been {@link #transfer(String) transferred}.
+ *
+ * <p>If the device reboots before the session has been finalized, you may commit the
+ * session again.
+ *
+ * <p>The caller of this method is responsible to ensure the safety of the session. As the
+ * session was created by another - usually less trusted - app, it is paramount that before
+ * committing <u>all</u> public and system {@link SessionInfo properties of the session}
+ * and <u>all</u> {@link #openRead(String) APKs} are verified by the caller. It might happen
+ * that new properties are added to the session with a new API revision. In this case the
+ * callers need to be updated.
+ *
+ * @param statusReceiver Callbacks called when the state of the session changes.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES)
+ public void commitTransferred(@NonNull IntentSender statusReceiver) {
+ try {
+ mSession.commit(statusReceiver, true);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Transfer the session to a new owner.
+ * <p>
+ * Only sessions that update the installing app can be transferred.
+ * <p>
+ * After the transfer to a package with a different uid all method calls on the session
+ * will cause {@link SecurityException}s.
+ * <p>
+ * Once this method is called, the session is sealed and no additional mutations beside
+ * committing it may be performed on the session.
+ *
+ * @param packageName The package of the new owner. Needs to hold the INSTALL_PACKAGES
+ * permission.
+ *
+ * @throws PackageManager.NameNotFoundException if the new owner could not be found.
+ * @throws SecurityException if called after the session has been committed or abandoned.
+ * @throws SecurityException if the session does not update the original installer
+ * @throws SecurityException if streams opened through
+ * {@link #openWrite(String, long, long) are still open.
+ */
+ public void transfer(@NonNull String packageName)
+ throws PackageManager.NameNotFoundException {
+ Preconditions.checkNotNull(packageName);
+
+ try {
+ mSession.transfer(packageName);
+ } catch (ParcelableException e) {
+ e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1041,6 +1103,26 @@
}
/**
+ * Check if there are hidden options set.
+ *
+ * <p>Hidden options are those options that cannot be verified via public or system-api
+ * methods on {@link SessionInfo}.
+ *
+ * @return {@code true} if any hidden option is set.
+ *
+ * @hide
+ */
+ public boolean areHiddenOptionsSet() {
+ return (installFlags & (PackageManager.INSTALL_ALLOW_DOWNGRADE
+ | PackageManager.INSTALL_DONT_KILL_APP
+ | PackageManager.INSTALL_INSTANT_APP
+ | PackageManager.INSTALL_FULL_APP
+ | PackageManager.INSTALL_VIRTUAL_PRELOAD
+ | PackageManager.INSTALL_ALLOCATE_AGGRESSIVE)) != installFlags
+ || abiOverride != null || volumeUuid != null;
+ }
+
+ /**
* Provide value of {@link PackageInfo#installLocation}, which may be used
* to determine where the app will be staged. Defaults to
* {@link PackageInfo#INSTALL_LOCATION_INTERNAL_ONLY}.
@@ -1300,6 +1382,19 @@
public CharSequence appLabel;
/** {@hide} */
+ public int installLocation;
+ /** {@hide} */
+ public Uri originatingUri;
+ /** {@hide} */
+ public int originatingUid;
+ /** {@hide} */
+ public Uri referrerUri;
+ /** {@hide} */
+ public String[] grantedRuntimePermissions;
+ /** {@hide} */
+ public int installFlags;
+
+ /** {@hide} */
public SessionInfo() {
}
@@ -1318,6 +1413,13 @@
appPackageName = source.readString();
appIcon = source.readParcelable(null);
appLabel = source.readString();
+
+ installLocation = source.readInt();
+ originatingUri = source.readParcelable(null);
+ originatingUid = source.readInt();
+ referrerUri = source.readParcelable(null);
+ grantedRuntimePermissions = source.readStringArray();
+ installFlags = source.readInt();
}
/**
@@ -1441,6 +1543,130 @@
return intent;
}
+ /**
+ * Get the mode of the session as set in the constructor of the {@link SessionParams}.
+ *
+ * @return One of {@link SessionParams#MODE_FULL_INSTALL}
+ * or {@link SessionParams#MODE_INHERIT_EXISTING}
+ */
+ public int getMode() {
+ return mode;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setInstallLocation(int)}.
+ */
+ public int getInstallLocation() {
+ return installLocation;
+ }
+
+ /**
+ * Get the value as set in {@link SessionParams#setSize(long)}.
+ *
+ * <p>The value is a hint and does not have to match the actual size.
+ */
+ public long getSize() {
+ return sizeBytes;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUri(Uri)}.
+ */
+ public @Nullable Uri getOriginatingUri() {
+ return originatingUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setOriginatingUid(int)}.
+ */
+ public int getOriginatingUid() {
+ return originatingUid;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setReferrerUri(Uri)}
+ */
+ public @Nullable Uri getReferrerUri() {
+ return referrerUri;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setGrantedRuntimePermissions(String[])}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @Nullable String[] getGrantedRuntimePermissions() {
+ return grantedRuntimePermissions;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllowDowngrade(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllowDowngrade() {
+ return (installFlags & PackageManager.INSTALL_ALLOW_DOWNGRADE) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setDontKillApp(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getDontKillApp() {
+ return (installFlags & PackageManager.INSTALL_DONT_KILL_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code true},
+ * return true. If it was called with {@code false} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsFullApp
+ */
+ @SystemApi
+ public boolean getInstallAsInstantApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
+ }
+
+ /**
+ * If {@link SessionParams#setInstallAsInstantApp(boolean)} was called with {@code false},
+ * return true. If it was called with {@code true} or if it was not called return false.
+ *
+ * @hide
+ *
+ * @see #getInstallAsInstantApp
+ */
+ @SystemApi
+ public boolean getInstallAsFullApp(boolean isInstantApp) {
+ return (installFlags & PackageManager.INSTALL_FULL_APP) != 0;
+ }
+
+ /**
+ * Get if {@link SessionParams#setInstallAsVirtualPreload()} was called.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getInstallAsVirtualPreload() {
+ return (installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0;
+ }
+
+ /**
+ * Get the value set in {@link SessionParams#setAllocateAggressive(boolean)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean getAllocateAggressive() {
+ return (installFlags & PackageManager.INSTALL_ALLOCATE_AGGRESSIVE) != 0;
+ }
+
+
/** {@hide} */
@Deprecated
public @Nullable Intent getDetailsIntent() {
@@ -1467,6 +1693,13 @@
dest.writeString(appPackageName);
dest.writeParcelable(appIcon, flags);
dest.writeString(appLabel != null ? appLabel.toString() : null);
+
+ dest.writeInt(installLocation);
+ dest.writeParcelable(originatingUri, flags);
+ dest.writeInt(originatingUid);
+ dest.writeParcelable(referrerUri, flags);
+ dest.writeStringArray(grantedRuntimePermissions);
+ dest.writeInt(installFlags);
}
public static final Parcelable.Creator<SessionInfo>
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index bab7011..c3b93b4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -16,17 +16,6 @@
package com.android.server.pm;
-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.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;
@@ -54,8 +43,6 @@
import android.content.pm.ParceledListSlice;
import android.content.pm.VersionedPackage;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -84,9 +71,6 @@
import android.util.SparseIntArray;
import android.util.Xml;
-import java.io.CharArrayWriter;
-import libcore.io.IoUtils;
-
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
@@ -97,10 +81,13 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.IoThread;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
+import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -125,32 +112,6 @@
/** XML constants used in {@link #mSessionsFile} */
private static final String TAG_SESSIONS = "sessions";
- private static final String TAG_SESSION = "session";
- private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
- 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_INSTALLER_UID = "installerUid";
- private static final String ATTR_CREATED_MILLIS = "createdMillis";
- private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
- private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
- private static final String ATTR_PREPARED = "prepared";
- 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";
- @Deprecated
- 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_ORIGINATING_UID = "originatingUid";
- private static final String ATTR_REFERRER_URI = "referrerUri";
- private static final String ATTR_ABI_OVERRIDE = "abiOverride";
- private static final String ATTR_VOLUME_UUID = "volumeUuid";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_INSTALL_REASON = "installRason";
/** Automatically destroy sessions older than this */
private static final long MAX_AGE_MILLIS = 3 * DateUtils.DAY_IN_MILLIS;
@@ -357,8 +318,10 @@
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);
+ if (PackageInstallerSession.TAG_SESSION.equals(tag)) {
+ final PackageInstallerSession session = PackageInstallerSession.
+ readFromXml(in, mInternalCallback, mContext, mPm,
+ mInstallThread.getLooper(), mSessionsDir);
final long age = System.currentTimeMillis() - session.createdMillis;
final boolean valid;
@@ -397,53 +360,10 @@
session.dump(pw);
mHistoricalSessions.add(writer.toString());
+ int installerUid = session.getInstallerUid();
// Increment the number of sessions by this installerUid.
- mHistoricalSessionsByInstaller.put(
- session.installerUid,
- mHistoricalSessionsByInstaller.get(session.installerUid) + 1);
- }
-
- private PackageInstallerSession readSessionLocked(XmlPullParser in) throws IOException,
- XmlPullParserException {
- 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 int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, mPm.getPackageUid(
- installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
- final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
- final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
- final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
- final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
- final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
- final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
-
- final SessionParams params = new SessionParams(
- SessionParams.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.originatingUid =
- readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
- params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
- params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
- params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
- params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
- params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
-
- final File appIconFile = buildAppIconFile(sessionId);
- if (appIconFile.exists()) {
- params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- return new PackageInstallerSession(mInternalCallback, mContext, mPm,
- mInstallThread.getLooper(), sessionId, userId, installerPackageName, installerUid,
- params, createdMillis, stageDir, stageCid, prepared, sealed);
+ mHistoricalSessionsByInstaller.put(installerUid,
+ mHistoricalSessionsByInstaller.get(installerUid) + 1);
}
private void writeSessionsLocked() {
@@ -460,7 +380,7 @@
final int size = mSessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- writeSessionLocked(out, session);
+ session.write(out, mSessionsDir);
}
out.endTag(null, TAG_SESSIONS);
out.endDocument();
@@ -473,106 +393,6 @@
}
}
- private void writeSessionLocked(XmlSerializer out, PackageInstallerSession session)
- throws IOException {
- final SessionParams params = session.params;
-
- 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);
- writeIntAttribute(out, ATTR_INSTALLER_UID, session.installerUid);
- writeLongAttribute(out, ATTR_CREATED_MILLIS, session.createdMillis);
- if (session.stageDir != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
- session.stageDir.getAbsolutePath());
- }
- if (session.stageCid != null) {
- writeStringAttribute(out, ATTR_SESSION_STAGE_CID, session.stageCid);
- }
- writeBooleanAttribute(out, ATTR_PREPARED, session.isPrepared());
- writeBooleanAttribute(out, ATTR_SEALED, session.isSealed());
-
- 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);
- writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
- writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
- writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
- writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
- writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
- writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
- writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
-
- // Persist app icon if changed since last written
- final File appIconFile = buildAppIconFile(session.sessionId);
- if (params.appIcon == null && appIconFile.exists()) {
- appIconFile.delete();
- } else if (params.appIcon != null
- && appIconFile.lastModified() != params.appIconLastModified) {
- if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
- FileOutputStream os = null;
- try {
- os = new FileOutputStream(appIconFile);
- params.appIcon.compress(CompressFormat.PNG, 90, os);
- } catch (IOException e) {
- Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
- } finally {
- IoUtils.closeQuietly(os);
- }
-
- params.appIconLastModified = appIconFile.lastModified();
- }
-
- writeGrantedRuntimePermissions(out, params.grantedRuntimePermissions);
-
- out.endTag(null, TAG_SESSION);
- }
-
- private static void writeGrantedRuntimePermissions(XmlSerializer out,
- String[] grantedRuntimePermissions) throws IOException {
- if (grantedRuntimePermissions != null) {
- for (String permission : grantedRuntimePermissions) {
- out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- writeStringAttribute(out, ATTR_NAME, permission);
- out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
- }
- }
- }
-
- private static String[] readGrantedRuntimePermissions(XmlPullParser in)
- throws IOException, XmlPullParserException {
- List<String> permissions = null;
-
- final int outerDepth = in.getDepth();
- int type;
- while ((type = in.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
- if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
- String permission = readStringAttribute(in, ATTR_NAME);
- if (permissions == null) {
- permissions = new ArrayList<>();
- }
- permissions.add(permission);
- }
- }
-
- if (permissions == null) {
- return null;
- }
-
- String[] permissionsArray = new String[permissions.size()];
- permissions.toArray(permissionsArray);
- return permissionsArray;
- }
-
private File buildAppIconFile(int sessionId) {
return new File(mSessionsDir, "app_icon." + sessionId + ".png");
}
@@ -885,9 +705,11 @@
synchronized (mSessions) {
for (int i = 0; i < mSessions.size(); i++) {
final PackageInstallerSession session = mSessions.valueAt(i);
- if (Objects.equals(session.installerPackageName, installerPackageName)
+
+ SessionInfo info = session.generateInfo(false);
+ if (Objects.equals(info.getInstallerPackageName(), installerPackageName)
&& session.userId == userId) {
- result.add(session.generateInfo(false));
+ result.add(info);
}
}
}
@@ -962,7 +784,7 @@
final int size = sessions.size();
for (int i = 0; i < size; i++) {
final PackageInstallerSession session = sessions.valueAt(i);
- if (session.installerUid == installerUid) {
+ if (session.getInstallerUid() == installerUid) {
count++;
}
}
@@ -974,7 +796,7 @@
if (callingUid == Process.ROOT_UID) {
return true;
} else {
- return (session != null) && (callingUid == session.installerUid);
+ return (session != null) && (callingUid == session.getInstallerUid());
}
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5823771..0fd696f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -25,9 +25,22 @@
import static android.system.OsConstants.O_RDONLY;
import static android.system.OsConstants.O_WRONLY;
+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.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 com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
import static com.android.server.pm.PackageInstallerService.prepareStageDir;
+import android.Manifest;
+import android.annotation.NonNull;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
@@ -45,6 +58,8 @@
import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.Signature;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.os.Binder;
import android.os.Bundle;
import android.os.FileBridge;
@@ -53,6 +68,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteException;
import android.os.RevocableFileDescriptor;
@@ -80,9 +96,14 @@
import libcore.io.IoUtils;
import libcore.io.Libcore;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileFilter;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.security.cert.Certificate;
import java.util.ArrayList;
@@ -97,6 +118,34 @@
private static final int MSG_COMMIT = 0;
+ /** XML constants used for persisting a session */
+ static final String TAG_SESSION = "session";
+ private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
+ 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_INSTALLER_UID = "installerUid";
+ private static final String ATTR_CREATED_MILLIS = "createdMillis";
+ private static final String ATTR_SESSION_STAGE_DIR = "sessionStageDir";
+ private static final String ATTR_SESSION_STAGE_CID = "sessionStageCid";
+ private static final String ATTR_PREPARED = "prepared";
+ 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";
+ @Deprecated
+ 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_ORIGINATING_UID = "originatingUid";
+ private static final String ATTR_REFERRER_URI = "referrerUri";
+ private static final String ATTR_ABI_OVERRIDE = "abiOverride";
+ private static final String ATTR_VOLUME_UUID = "volumeUuid";
+ private static final String ATTR_NAME = "name";
+ private static final String ATTR_INSTALL_REASON = "installRason";
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -104,12 +153,9 @@
private final Context mContext;
private final PackageManagerService mPm;
private final Handler mHandler;
- private final boolean mIsInstallerDeviceOwner;
final int sessionId;
final int userId;
- final String installerPackageName;
- final int installerUid;
final SessionParams params;
final long createdMillis;
final int defaultContainerGid;
@@ -122,6 +168,17 @@
private final Object mLock = new Object();
+ /** Uid of the creator of this session. */
+ private final int mOriginalInstallerUid;
+
+ /** Package of the owner of the installer session */
+ @GuardedBy("mLock")
+ private String mInstallerPackageName;
+
+ /** Uid of the owner of the installer session */
+ @GuardedBy("mLock")
+ private int mInstallerUid;
+
@GuardedBy("mLock")
private float mClientProgress = 0;
@GuardedBy("mLock")
@@ -132,18 +189,25 @@
@GuardedBy("mLock")
private float mReportedProgress = -1;
+ /** State of the session. */
@GuardedBy("mLock")
private boolean mPrepared = false;
@GuardedBy("mLock")
private boolean mSealed = false;
@GuardedBy("mLock")
- private boolean mPermissionsAccepted = false;
+ private boolean mCommitted = false;
@GuardedBy("mLock")
private boolean mRelinquished = false;
@GuardedBy("mLock")
private boolean mDestroyed = false;
+ /** Permissions have been accepted by the user (see {@link #setPermissionsResult}) */
+ @GuardedBy("mLock")
+ private boolean mPermissionsManuallyAccepted = false;
+
+ @GuardedBy("mLock")
private int mFinalStatus;
+ @GuardedBy("mLock")
private String mFinalMessage;
@GuardedBy("mLock")
@@ -155,9 +219,13 @@
private IPackageInstallObserver2 mRemoteObserver;
/** Fields derived from commit parsing */
+ @GuardedBy("mLock")
private String mPackageName;
+ @GuardedBy("mLock")
private int mVersionCode;
+ @GuardedBy("mLock")
private Signature[] mSignatures;
+ @GuardedBy("mLock")
private Certificate[][] mCertificates;
/**
@@ -205,32 +273,61 @@
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
- // Cache package manager data without the lock held
- final PackageInfo pkgInfo = mPm.getPackageInfo(
- params.appPackageName, PackageManager.GET_SIGNATURES
- | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
- final ApplicationInfo appInfo = mPm.getApplicationInfo(
- params.appPackageName, 0, userId);
-
synchronized (mLock) {
if (msg.obj != null) {
mRemoteObserver = (IPackageInstallObserver2) msg.obj;
}
-
try {
- commitLocked(pkgInfo, appInfo);
+ commitLocked();
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
dispatchSessionFinished(e.error, completeMsg, null);
}
-
- return true;
}
+
+ return true;
}
};
+ /**
+ * @return {@code true} iff the installing is app an device owner?
+ */
+ private boolean isInstallerDeviceOwnerLocked() {
+ DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
+ Context.DEVICE_POLICY_SERVICE);
+
+ return (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
+ mInstallerPackageName);
+ }
+
+ /**
+ * Checks if the permissions still need to be confirmed.
+ *
+ * <p>This is dependant on the identity of the installer, hence this cannot be cached if the
+ * installer might still {@link #transfer(String) change}.
+ *
+ * @return {@code true} iff we need to ask to confirm the permissions?
+ */
+ private boolean needToAskForPermissionsLocked() {
+ if (mPermissionsManuallyAccepted) {
+ return false;
+ }
+
+ final boolean isPermissionGranted =
+ (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES,
+ mInstallerUid) == PackageManager.PERMISSION_GRANTED);
+ final boolean isInstallerRoot = (mInstallerUid == Process.ROOT_UID);
+ final boolean forcePermissionPrompt =
+ (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
+
+ // Device owners are allowed to silently install packages, so the permission check is
+ // waived if the installer is the device owner.
+ return forcePermissionPrompt || !(isPermissionGranted || isInstallerRoot
+ || isInstallerDeviceOwnerLocked());
+ }
+
public PackageInstallerSession(PackageInstallerService.InternalCallback callback,
Context context, PackageManagerService pm, Looper looper, int sessionId, int userId,
String installerPackageName, int installerUid, SessionParams params, long createdMillis,
@@ -242,8 +339,9 @@
this.sessionId = sessionId;
this.userId = userId;
- this.installerPackageName = installerPackageName;
- this.installerUid = installerUid;
+ mOriginalInstallerUid = installerUid;
+ mInstallerPackageName = installerPackageName;
+ mInstallerUid = installerUid;
this.params = params;
this.createdMillis = createdMillis;
this.stageDir = stageDir;
@@ -257,26 +355,6 @@
mPrepared = prepared;
mSealed = sealed;
- // Device owners are allowed to silently install packages, so the permission check is
- // waived if the installer is the device owner.
- DevicePolicyManager dpm = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- final boolean isPermissionGranted =
- (mPm.checkUidPermission(android.Manifest.permission.INSTALL_PACKAGES, installerUid)
- == PackageManager.PERMISSION_GRANTED);
- final boolean isInstallerRoot = (installerUid == Process.ROOT_UID);
- final boolean forcePermissionPrompt =
- (params.installFlags & PackageManager.INSTALL_FORCE_PERMISSION_PROMPT) != 0;
- mIsInstallerDeviceOwner = (dpm != null) && dpm.isDeviceOwnerAppOnCallingUser(
- installerPackageName);
- if ((isPermissionGranted
- || isInstallerRoot
- || mIsInstallerDeviceOwner)
- && !forcePermissionPrompt) {
- mPermissionsAccepted = true;
- } else {
- mPermissionsAccepted = false;
- }
final long identity = Binder.clearCallingIdentity();
try {
final int uid = mPm.getPackageUid(PackageManagerService.DEFAULT_CONTAINER_PACKAGE,
@@ -295,7 +373,7 @@
final SessionInfo info = new SessionInfo();
synchronized (mLock) {
info.sessionId = sessionId;
- info.installerPackageName = installerPackageName;
+ info.installerPackageName = mInstallerPackageName;
info.resolvedBaseCodePath = (mResolvedBaseFile != null) ?
mResolvedBaseFile.getAbsolutePath() : null;
info.progress = mProgress;
@@ -310,6 +388,13 @@
info.appIcon = params.appIcon;
}
info.appLabel = params.appLabel;
+
+ info.installLocation = params.installLocation;
+ info.originatingUri = params.originatingUri;
+ info.originatingUid = params.originatingUid;
+ info.referrerUri = params.referrerUri;
+ info.grantedRuntimePermissions = params.grantedRuntimePermissions;
+ info.installFlags = params.installFlags;
}
return info;
}
@@ -326,14 +411,26 @@
}
}
- private void assertPreparedAndNotSealed(String cookie) {
- synchronized (mLock) {
- if (!mPrepared) {
- throw new IllegalStateException(cookie + " before prepared");
- }
- if (mSealed) {
- throw new SecurityException(cookie + " not allowed after commit");
- }
+ private void assertPreparedAndNotSealedLocked(String cookie) {
+ assertPreparedAndNotCommittedOrDestroyedLocked(cookie);
+ if (mSealed) {
+ throw new SecurityException(cookie + " not allowed after sealing");
+ }
+ }
+
+ private void assertPreparedAndNotCommittedOrDestroyedLocked(String cookie) {
+ assertPreparedAndNotDestroyedLocked(cookie);
+ if (mCommitted) {
+ throw new SecurityException(cookie + " not allowed after commit");
+ }
+ }
+
+ private void assertPreparedAndNotDestroyedLocked(String cookie) {
+ if (!mPrepared) {
+ throw new IllegalStateException(cookie + " before prepared");
+ }
+ if (mDestroyed) {
+ throw new SecurityException(cookie + " not allowed after destruction");
}
}
@@ -342,27 +439,27 @@
* might point at an ASEC mount point, which is why we delay path resolution
* until someone actively works with the session.
*/
- private File resolveStageDir() throws IOException {
- synchronized (mLock) {
- if (mResolvedStageDir == null) {
- if (stageDir != null) {
- mResolvedStageDir = stageDir;
+ private File resolveStageDirLocked() throws IOException {
+ if (mResolvedStageDir == null) {
+ if (stageDir != null) {
+ mResolvedStageDir = stageDir;
+ } else {
+ final String path = PackageHelper.getSdDir(stageCid);
+ if (path != null) {
+ mResolvedStageDir = new File(path);
} else {
- final String path = PackageHelper.getSdDir(stageCid);
- if (path != null) {
- mResolvedStageDir = new File(path);
- } else {
- throw new IOException("Failed to resolve path to container " + stageCid);
- }
+ throw new IOException("Failed to resolve path to container " + stageCid);
}
}
- return mResolvedStageDir;
}
+ return mResolvedStageDir;
}
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
// Always publish first staging movement
final boolean forcePublish = (mClientProgress == 0);
mClientProgress = progress;
@@ -373,6 +470,8 @@
@Override
public void addClientProgress(float progress) {
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
setClientProgress(mClientProgress + progress);
}
}
@@ -390,11 +489,15 @@
@Override
public String[] getNames() {
- assertPreparedAndNotSealed("getNames");
- try {
- return resolveStageDir().list();
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("getNames");
+
+ try {
+ return resolveStageDirLocked().list();
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
@@ -403,20 +506,26 @@
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
- try {
- createRemoveSplitMarker(splitName);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("removeSplit");
+
+ try {
+ createRemoveSplitMarkerLocked(splitName);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private void createRemoveSplitMarker(String splitName) throws IOException {
+ private void createRemoveSplitMarkerLocked(String splitName) throws IOException {
try {
final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
if (!FileUtils.isValidExtFilename(markerName)) {
throw new IllegalArgumentException("Invalid marker: " + markerName);
}
- final File target = new File(resolveStageDir(), markerName);
+ final File target = new File(resolveStageDirLocked(), markerName);
target.createNewFile();
Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
} catch (ErrnoException e) {
@@ -440,8 +549,10 @@
// will block any attempted install transitions.
final RevocableFileDescriptor fd;
final FileBridge bridge;
+ final File stageDir;
synchronized (mLock) {
- assertPreparedAndNotSealed("openWrite");
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("openWrite");
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
@@ -452,6 +563,8 @@
bridge = new FileBridge();
mBridges.add(bridge);
}
+
+ stageDir = resolveStageDirLocked();
}
try {
@@ -462,7 +575,7 @@
final File target;
final long identity = Binder.clearCallingIdentity();
try {
- target = new File(resolveStageDir(), name);
+ target = new File(stageDir, name);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -500,55 +613,108 @@
@Override
public ParcelFileDescriptor openRead(String name) {
- try {
- return openReadInternal(name);
- } catch (IOException e) {
- throw ExceptionUtils.wrap(e);
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
+ try {
+ return openReadInternalLocked(name);
+ } catch (IOException e) {
+ throw ExceptionUtils.wrap(e);
+ }
}
}
- private ParcelFileDescriptor openReadInternal(String name) throws IOException {
- assertPreparedAndNotSealed("openRead");
-
+ private ParcelFileDescriptor openReadInternalLocked(String name) throws IOException {
try {
if (!FileUtils.isValidExtFilename(name)) {
throw new IllegalArgumentException("Invalid name: " + name);
}
- final File target = new File(resolveStageDir(), name);
-
+ final File target = new File(resolveStageDirLocked(), name);
final FileDescriptor targetFd = Libcore.os.open(target.getAbsolutePath(), O_RDONLY, 0);
return new ParcelFileDescriptor(targetFd);
-
} catch (ErrnoException e) {
throw e.rethrowAsIOException();
}
}
+ /**
+ * Check if the caller is the owner of this session. Otherwise throw a
+ * {@link SecurityException}.
+ */
+ private void assertCallerIsOwnerOrRootLocked() {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID && callingUid != mInstallerUid) {
+ throw new SecurityException("Session does not belong to uid " + callingUid);
+ }
+ }
+
+ /**
+ * If anybody is reading or writing data of the session, throw an {@link SecurityException}.
+ */
+ private void assertNoWriteFileTransfersOpenLocked() {
+ // Verify that all writers are hands-off
+ for (RevocableFileDescriptor fd : mFds) {
+ if (!fd.isRevoked()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ for (FileBridge bridge : mBridges) {
+ if (!bridge.isClosed()) {
+ throw new SecurityException("Files still open");
+ }
+ }
+ }
+
@Override
- public void commit(IntentSender statusReceiver) {
+ public void commit(IntentSender statusReceiver, boolean forTransfer) {
Preconditions.checkNotNull(statusReceiver);
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
final boolean wasSealed;
synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotDestroyedLocked("commit");
+
+ if (forTransfer) {
+ mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, null);
+
+ if (mInstallerUid == mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has not been transferred");
+ }
+ } else {
+ if (mInstallerUid != mOriginalInstallerUid) {
+ throw new IllegalArgumentException("Session has been transferred");
+ }
+ }
+
wasSealed = mSealed;
if (!mSealed) {
- // Verify that all writers are hands-off
- for (RevocableFileDescriptor fd : mFds) {
- if (!fd.isRevoked()) {
- throw new SecurityException("Files still open");
- }
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ // Do now throw an exception here to stay compatible with O and older
+ destroyInternal();
+ dispatchSessionFinished(e.error, ExceptionUtils.getCompleteMessage(e), null);
+ return;
}
- for (FileBridge bridge : mBridges) {
- if (!bridge.isClosed()) {
- throw new SecurityException("Files still open");
- }
- }
- mSealed = true;
}
// Client staging is fully done at this point
mClientProgress = 1f;
computeProgressLocked(true);
+
+ // This ongoing commit should keep session active, even though client
+ // will probably close their end.
+ mActiveCount.incrementAndGet();
+
+ mCommitted = true;
+ final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(
+ mContext, statusReceiver, sessionId, isInstallerDeviceOwnerLocked(), userId);
+ mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
if (!wasSealed) {
@@ -557,17 +723,82 @@
// the session lock, since otherwise it's a lock inversion.
mCallback.onSessionSealedBlocking(this);
}
-
- // This ongoing commit should keep session active, even though client
- // will probably close their end.
- mActiveCount.incrementAndGet();
-
- final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
- statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
- mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
- private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
+ /**
+ * Seal the session to prevent further modification and validate the contents of it.
+ *
+ * <p>The session will be sealed after calling this method even if it failed.
+ *
+ * @param pkgInfo The package info for {@link #params}.packagename
+ */
+ private void sealAndValidateLocked(PackageInfo pkgInfo)
+ throws PackageManagerException {
+ assertNoWriteFileTransfersOpenLocked();
+
+ mSealed = true;
+
+ // Verify that stage looks sane with respect to existing application.
+ // This currently only ensures packageName, versionCode, and certificate
+ // consistency.
+ validateInstallLocked(pkgInfo);
+
+ // Read transfers from the original owner stay open, but as the session's data
+ // cannot be modified anymore, there is no leak of information.
+ }
+
+ @Override
+ public void transfer(String packageName) {
+ Preconditions.checkNotNull(packageName);
+
+ ApplicationInfo newOwnerAppInfo = mPm.getApplicationInfo(packageName, 0, userId);
+ if (newOwnerAppInfo == null) {
+ throw new ParcelableException(new PackageManager.NameNotFoundException(packageName));
+ }
+
+ if (PackageManager.PERMISSION_GRANTED != mPm.checkUidPermission(
+ Manifest.permission.INSTALL_PACKAGES, newOwnerAppInfo.uid)) {
+ throw new SecurityException("Destination package " + packageName + " does not have "
+ + "the " + Manifest.permission.INSTALL_PACKAGES + " permission");
+ }
+
+ // Cache package manager data without the lock held
+ final PackageInfo installedPkgInfo = mPm.getPackageInfo(
+ params.appPackageName, PackageManager.GET_SIGNATURES
+ | PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+
+ // Only install flags that can be verified by the app the session is transferred to are
+ // allowed. The parameters can be read via PackageInstaller.SessionInfo.
+ if (!params.areHiddenOptionsSet()) {
+ throw new SecurityException("Can only transfer sessions that use public options");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("transfer");
+
+ try {
+ sealAndValidateLocked(installedPkgInfo);
+ } catch (PackageManagerException e) {
+ throw new IllegalArgumentException("Package is not valid", e);
+ }
+
+ if (!mPackageName.equals(mInstallerPackageName)) {
+ throw new SecurityException("Can only transfer sessions that update the original "
+ + "installer");
+ }
+
+ mInstallerPackageName = packageName;
+ mInstallerUid = newOwnerAppInfo.uid;
+ }
+
+ // Persist the fact that we've sealed ourselves to prevent
+ // mutations of any hard links we create. We do this without holding
+ // the session lock, since otherwise it's a lock inversion.
+ mCallback.onSessionSealedBlocking(this);
+ }
+
+ private void commitLocked()
throws PackageManagerException {
if (mDestroyed) {
throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, "Session destroyed");
@@ -577,22 +808,17 @@
}
try {
- resolveStageDir();
+ resolveStageDirLocked();
} catch (IOException e) {
throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
"Failed to resolve stage location", e);
}
- // Verify that stage looks sane with respect to existing application.
- // This currently only ensures packageName, versionCode, and certificate
- // consistency.
- validateInstallLocked(pkgInfo, appInfo);
-
Preconditions.checkNotNull(mPackageName);
Preconditions.checkNotNull(mSignatures);
Preconditions.checkNotNull(mResolvedBaseFile);
- if (!mPermissionsAccepted) {
+ if (needToAskForPermissionsLocked()) {
// User needs to accept permissions; give installer an intent they
// can use to involve user.
final Intent intent = new Intent(PackageInstaller.ACTION_CONFIRM_PERMISSIONS);
@@ -622,7 +848,7 @@
if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
try {
final List<File> fromFiles = mResolvedInheritedFiles;
- final File toDir = resolveStageDir();
+ final File toDir = resolveStageDirLocked();
if (LOGD) Slog.d(TAG, "Inherited files: " + mResolvedInheritedFiles);
if (!mResolvedInheritedFiles.isEmpty() && mInheritedFilesBase == null) {
@@ -683,7 +909,7 @@
mRelinquished = true;
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
- installerPackageName, installerUid, user, mCertificates);
+ mInstallerPackageName, mInstallerUid, user, mCertificates);
}
/**
@@ -698,8 +924,7 @@
* Note that upgrade compatibility is still performed by
* {@link PackageManagerService}.
*/
- private void validateInstallLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
- throws PackageManagerException {
+ private void validateInstallLocked(PackageInfo pkgInfo) throws PackageManagerException {
mPackageName = null;
mVersionCode = -1;
mSignatures = null;
@@ -752,7 +977,7 @@
mCertificates = apk.certificates;
}
- assertApkConsistent(String.valueOf(addedFile), apk);
+ assertApkConsistentLocked(String.valueOf(addedFile), apk);
// Take this opportunity to enforce uniform naming
final String targetName;
@@ -807,13 +1032,14 @@
} else {
// Partial installs must be consistent with existing install
- if (appInfo == null) {
+ if (pkgInfo == null || pkgInfo.applicationInfo == null) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
"Missing existing base package for " + mPackageName);
}
final PackageLite existing;
final ApkLite existingBase;
+ ApplicationInfo appInfo = pkgInfo.applicationInfo;
try {
existing = PackageParser.parsePackageLite(new File(appInfo.getCodePath()), 0);
existingBase = PackageParser.parseApkLite(new File(appInfo.getBaseCodePath()),
@@ -822,7 +1048,7 @@
throw PackageManagerException.from(e);
}
- assertApkConsistent("Existing base", existingBase);
+ assertApkConsistentLocked("Existing base", existingBase);
// Inherit base if not overridden
if (mResolvedBaseFile == null) {
@@ -878,7 +1104,7 @@
}
}
- private void assertApkConsistent(String tag, ApkLite apk)
+ private void assertApkConsistentLocked(String tag, ApkLite apk)
throws PackageManagerException {
if (!mPackageName.equals(apk.packageName)) {
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
@@ -959,6 +1185,15 @@
return true;
}
+ /**
+ * @return the uid of the owner this session
+ */
+ public int getInstallerUid() {
+ synchronized (mLock) {
+ return mInstallerUid;
+ }
+ }
+
private static String getRelativePath(File file, File base) throws IOException {
final String pathStr = file.getAbsolutePath();
final String baseStr = base.getAbsolutePath();
@@ -1106,9 +1341,9 @@
if (accepted) {
// Mark and kick off another install pass
synchronized (mLock) {
- mPermissionsAccepted = true;
+ mPermissionsManuallyAccepted = true;
+ mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
- mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
} else {
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
@@ -1120,7 +1355,9 @@
mCallback.onSessionActiveChanged(this, true);
}
+ boolean wasPrepared;
synchronized (mLock) {
+ wasPrepared = mPrepared;
if (!mPrepared) {
if (stageDir != null) {
prepareStageDir(stageDir);
@@ -1141,13 +1378,20 @@
}
mPrepared = true;
- mCallback.onSessionPrepared(this);
}
}
+
+ if (!wasPrepared) {
+ mCallback.onSessionPrepared(this);
+ }
}
@Override
public void close() {
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ }
+
if (mActiveCount.decrementAndGet() == 0) {
mCallback.onSessionActiveChanged(this, false);
}
@@ -1155,21 +1399,33 @@
@Override
public void abandon() {
- if (mRelinquished) {
- Slog.d(TAG, "Ignoring abandon after commit relinquished control");
- return;
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+
+ if (mRelinquished) {
+ Slog.d(TAG, "Ignoring abandon after commit relinquished control");
+ return;
+ }
+ destroyInternal();
}
- destroyInternal();
+
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "Session was abandoned", null);
}
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
- mFinalStatus = returnCode;
- mFinalMessage = msg;
+ IPackageInstallObserver2 observer;
+ String packageName;
+ synchronized (mLock) {
+ mFinalStatus = returnCode;
+ mFinalMessage = msg;
- if (mRemoteObserver != null) {
+ observer = mRemoteObserver;
+ packageName = mPackageName;
+ }
+
+ if (observer != null) {
try {
- mRemoteObserver.onPackageInstalled(mPackageName, returnCode, msg, extras);
+ observer.onPackageInstalled(packageName, returnCode, msg, extras);
} catch (RemoteException ignored) {
}
}
@@ -1220,8 +1476,9 @@
pw.increaseIndent();
pw.printPair("userId", userId);
- pw.printPair("installerPackageName", installerPackageName);
- pw.printPair("installerUid", installerUid);
+ pw.printPair("mOriginalInstallerUid", mOriginalInstallerUid);
+ pw.printPair("mInstallerPackageName", mInstallerPackageName);
+ pw.printPair("mInstallerUid", mInstallerUid);
pw.printPair("createdMillis", createdMillis);
pw.printPair("stageDir", stageDir);
pw.printPair("stageCid", stageCid);
@@ -1232,7 +1489,7 @@
pw.printPair("mClientProgress", mClientProgress);
pw.printPair("mProgress", mProgress);
pw.printPair("mSealed", mSealed);
- pw.printPair("mPermissionsAccepted", mPermissionsAccepted);
+ pw.printPair("mPermissionsManuallyAccepted", mPermissionsManuallyAccepted);
pw.printPair("mRelinquished", mRelinquished);
pw.printPair("mDestroyed", mDestroyed);
pw.printPair("mFds", mFds.size());
@@ -1243,4 +1500,170 @@
pw.decreaseIndent();
}
+
+ private static void writeGrantedRuntimePermissionsLocked(XmlSerializer out,
+ String[] grantedRuntimePermissions) throws IOException {
+ if (grantedRuntimePermissions != null) {
+ for (String permission : grantedRuntimePermissions) {
+ out.startTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
+ writeStringAttribute(out, ATTR_NAME, permission);
+ out.endTag(null, TAG_GRANTED_RUNTIME_PERMISSION);
+ }
+ }
+ }
+
+ private static File buildAppIconFile(int sessionId, @NonNull File sessionsDir) {
+ return new File(sessionsDir, "app_icon." + sessionId + ".png");
+ }
+
+ /**
+ * Write this session to a {@link XmlSerializer}.
+ *
+ * @param out Where to write the session to
+ * @param sessionsDir The directory containing the sessions
+ */
+ void write(@NonNull XmlSerializer out, @NonNull File sessionsDir) throws IOException {
+ synchronized (mLock) {
+ out.startTag(null, TAG_SESSION);
+
+ writeIntAttribute(out, ATTR_SESSION_ID, sessionId);
+ writeIntAttribute(out, ATTR_USER_ID, userId);
+ writeStringAttribute(out, ATTR_INSTALLER_PACKAGE_NAME,
+ mInstallerPackageName);
+ writeIntAttribute(out, ATTR_INSTALLER_UID, mInstallerUid);
+ writeLongAttribute(out, ATTR_CREATED_MILLIS, createdMillis);
+ if (stageDir != null) {
+ writeStringAttribute(out, ATTR_SESSION_STAGE_DIR,
+ stageDir.getAbsolutePath());
+ }
+ if (stageCid != null) {
+ writeStringAttribute(out, ATTR_SESSION_STAGE_CID, stageCid);
+ }
+ writeBooleanAttribute(out, ATTR_PREPARED, isPrepared());
+ writeBooleanAttribute(out, ATTR_SEALED, isSealed());
+
+ 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);
+ writeStringAttribute(out, ATTR_APP_LABEL, params.appLabel);
+ writeUriAttribute(out, ATTR_ORIGINATING_URI, params.originatingUri);
+ writeIntAttribute(out, ATTR_ORIGINATING_UID, params.originatingUid);
+ writeUriAttribute(out, ATTR_REFERRER_URI, params.referrerUri);
+ writeStringAttribute(out, ATTR_ABI_OVERRIDE, params.abiOverride);
+ writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
+ writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
+
+ // Persist app icon if changed since last written
+ File appIconFile = buildAppIconFile(sessionId, sessionsDir);
+ if (params.appIcon == null && appIconFile.exists()) {
+ appIconFile.delete();
+ } else if (params.appIcon != null
+ && appIconFile.lastModified() != params.appIconLastModified) {
+ if (LOGD) Slog.w(TAG, "Writing changed icon " + appIconFile);
+ FileOutputStream os = null;
+ try {
+ os = new FileOutputStream(appIconFile);
+ params.appIcon.compress(Bitmap.CompressFormat.PNG, 90, os);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to write icon " + appIconFile + ": " + e.getMessage());
+ } finally {
+ IoUtils.closeQuietly(os);
+ }
+
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
+ writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
+ }
+
+ out.endTag(null, TAG_SESSION);
+ }
+
+ private static String[] readGrantedRuntimePermissions(XmlPullParser in)
+ throws IOException, XmlPullParserException {
+ List<String> permissions = null;
+
+ final int outerDepth = in.getDepth();
+ int type;
+ while ((type = in.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || in.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (TAG_GRANTED_RUNTIME_PERMISSION.equals(in.getName())) {
+ String permission = readStringAttribute(in, ATTR_NAME);
+ if (permissions == null) {
+ permissions = new ArrayList<>();
+ }
+ permissions.add(permission);
+ }
+ }
+
+ if (permissions == null) {
+ return null;
+ }
+
+ String[] permissionsArray = new String[permissions.size()];
+ permissions.toArray(permissionsArray);
+ return permissionsArray;
+ }
+
+ /**
+ * Read new session from a {@link XmlPullParser xml description} and create it.
+ *
+ * @param in The source of the description
+ * @param callback Callback the session uses to notify about changes of it's state
+ * @param context Context to be used by the session
+ * @param pm PackageManager to use by the session
+ * @param installerThread Thread to be used for callbacks of this session
+ * @param sessionsDir The directory the sessions are stored in
+ *
+ * @return The newly created session
+ */
+ public static PackageInstallerSession readFromXml(@NonNull XmlPullParser in,
+ @NonNull PackageInstallerService.InternalCallback callback, @NonNull Context context,
+ @NonNull PackageManagerService pm, Looper installerThread, @NonNull File sessionsDir)
+ throws IOException, XmlPullParserException {
+ 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 int installerUid = readIntAttribute(in, ATTR_INSTALLER_UID, pm.getPackageUid(
+ installerPackageName, PackageManager.MATCH_UNINSTALLED_PACKAGES, userId));
+ final long createdMillis = readLongAttribute(in, ATTR_CREATED_MILLIS);
+ final String stageDirRaw = readStringAttribute(in, ATTR_SESSION_STAGE_DIR);
+ final File stageDir = (stageDirRaw != null) ? new File(stageDirRaw) : null;
+ final String stageCid = readStringAttribute(in, ATTR_SESSION_STAGE_CID);
+ final boolean prepared = readBooleanAttribute(in, ATTR_PREPARED, true);
+ final boolean sealed = readBooleanAttribute(in, ATTR_SEALED);
+
+ final SessionParams params = new SessionParams(
+ SessionParams.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.originatingUid =
+ readIntAttribute(in, ATTR_ORIGINATING_UID, SessionParams.UID_UNKNOWN);
+ params.referrerUri = readUriAttribute(in, ATTR_REFERRER_URI);
+ params.abiOverride = readStringAttribute(in, ATTR_ABI_OVERRIDE);
+ params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
+ params.grantedRuntimePermissions = readGrantedRuntimePermissions(in);
+ params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
+
+ final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
+ if (appIconFile.exists()) {
+ params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
+ params.appIconLastModified = appIconFile.lastModified();
+ }
+
+ return new PackageInstallerSession(callback, context, pm,
+ installerThread, sessionId, userId, installerPackageName, installerUid,
+ params, createdMillis, stageDir, stageCid, prepared, sealed);
+ }
}