summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/IPackageInstallerSession.aidl4
-rw-r--r--core/java/android/content/pm/PackageInstaller.java98
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java22
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerSession.java356
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java68
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java1
6 files changed, 465 insertions, 84 deletions
diff --git a/core/java/android/content/pm/IPackageInstallerSession.aidl b/core/java/android/content/pm/IPackageInstallerSession.aidl
index 0b3c7654f4fb..e9546353d5f3 100644
--- a/core/java/android/content/pm/IPackageInstallerSession.aidl
+++ b/core/java/android/content/pm/IPackageInstallerSession.aidl
@@ -39,6 +39,9 @@ interface IPackageInstallerSession {
void transfer(in String packageName, in IntentSender statusReceiver);
void abandon();
+ void addFile(String name, long lengthBytes, in byte[] metadata);
+ void removeFile(String name);
+
boolean isMultiPackage();
int[] getChildSessionIds();
void addChildSessionId(in int sessionId);
@@ -46,5 +49,4 @@ interface IPackageInstallerSession {
int getParentSessionId();
boolean isStaged();
- void addFile(in String name, long size, in byte[] metadata);
}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 8f5143518cbe..898631e9a3b1 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -231,6 +231,15 @@ public class PackageInstaller {
public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
/**
+ * Streaming installation pending.
+ * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
+ *
+ * @see #EXTRA_SESSION_ID
+ * {@hide}
+ */
+ public static final int STATUS_PENDING_STREAMING = -2;
+
+ /**
* User action is currently required to proceed. You can launch the intent
* activity described by {@link Intent#EXTRA_INTENT} to involve the user and
* continue.
@@ -1059,13 +1068,56 @@ public class PackageInstaller {
}
}
+
+ /**
+ * Adds a file to session. On commit this file will be pulled from dataLoader.
+ *
+ * @param name arbitrary, unique name of your choosing to identify the
+ * APK being written. You can open a file again for
+ * additional writes (such as after a reboot) by using the
+ * same name. This name is only meaningful within the context
+ * of a single install session.
+ * @param lengthBytes total size of the file being written.
+ * The system may clear various caches as needed to allocate
+ * this space.
+ * @param metadata additional info use by dataLoader to pull data for the file.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ * {@hide}
+ */
+ public void addFile(@NonNull String name, long lengthBytes, @NonNull byte[] metadata) {
+ try {
+ mSession.addFile(name, lengthBytes, metadata);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Removes a file.
+ *
+ * @param name name of a file, e.g. split.
+ * @throws SecurityException if called after the session has been
+ * sealed or abandoned
+ * @throws IllegalStateException if called for non-callback session
+ * {@hide}
+ */
+ public void removeFile(@NonNull String name) {
+ try {
+ mSession.removeFile(name);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Attempt to commit everything staged in this session. This may require
* user intervention, and so it may not happen immediately. The final
* result of the commit will be reported through the given callback.
* <p>
- * Once this method is called, the session is sealed and no additional
- * mutations may be performed on the session. If the device reboots
+ * Once this method is called, the session is sealed and no additional mutations may be
+ * performed on the session. In case of device reboot or data loader transient failure
* before the session has been finalized, you may commit the session again.
* <p>
* If the installer is the device owner or the affiliated profile owner, there will be no
@@ -1220,27 +1272,6 @@ public class PackageInstaller {
}
/**
- * Configure files for an installation session.
- *
- * Currently only for Incremental installation session. Once this method is called,
- * the files and their paths, as specified in the parameters, will be created and properly
- * configured in the Incremental File System.
- *
- * TODO(b/136132412): update this and InstallationFile class with latest API design.
- *
- * @throws IllegalStateException if {@link SessionParams#incrementalParams} is null.
- *
- * @hide
- */
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- try {
- mSession.addFile(name, size, metadata);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Release this session object. You can open the session again if it
* hasn't been finalized.
*/
@@ -1429,6 +1460,9 @@ public class PackageInstaller {
public long requiredInstalledVersionCode = PackageManager.VERSION_CODE_HIGHEST;
/** {@hide} */
public IncrementalDataLoaderParams incrementalParams;
+ /** TODO(b/146080380): add a class name to make it fully compatible with ComponentName.
+ * {@hide} */
+ public String dataLoaderPackageName;
/**
* Construct parameters for a new package install session.
@@ -1468,6 +1502,7 @@ public class PackageInstaller {
incrementalParams = new IncrementalDataLoaderParams(
dataLoaderParamsParcel);
}
+ dataLoaderPackageName = source.readString();
}
/** {@hide} */
@@ -1492,6 +1527,7 @@ public class PackageInstaller {
ret.isStaged = isStaged;
ret.requiredInstalledVersionCode = requiredInstalledVersionCode;
ret.incrementalParams = incrementalParams;
+ ret.dataLoaderPackageName = dataLoaderPackageName;
return ret;
}
@@ -1831,6 +1867,20 @@ public class PackageInstaller {
this.incrementalParams = incrementalParams;
}
+ /**
+ * Set the data provider params for the session.
+ * This also switches installation into callback mode and disallow direct writes into
+ * staging folder.
+ * TODO(b/146080380): unify dataprovider params with Incremental.
+ *
+ * @param dataLoaderPackageName name of the dataLoader package
+ * {@hide}
+ */
+ @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
+ public void setDataLoaderPackageName(String dataLoaderPackageName) {
+ this.dataLoaderPackageName = dataLoaderPackageName;
+ }
+
/** {@hide} */
public void dump(IndentingPrintWriter pw) {
pw.printPair("mode", mode);
@@ -1851,6 +1901,7 @@ public class PackageInstaller {
pw.printPair("isMultiPackage", isMultiPackage);
pw.printPair("isStaged", isStaged);
pw.printPair("requiredInstalledVersionCode", requiredInstalledVersionCode);
+ pw.printPair("dataLoaderPackageName", dataLoaderPackageName);
pw.println();
}
@@ -1885,6 +1936,7 @@ public class PackageInstaller {
} else {
dest.writeParcelable(null, flags);
}
+ dest.writeString(dataLoaderPackageName);
}
public static final Parcelable.Creator<SessionParams>
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index dceca0a749e3..a54534b461f1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -637,7 +637,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
- stageDir, stageCid, false, false, false, null, SessionInfo.INVALID_ID,
+ stageDir, stageCid, null, false, false, false, null, SessionInfo.INVALID_ID,
false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");
synchronized (mSessions) {
@@ -1014,12 +1014,28 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
}
+ static void sendPendingStreaming(Context context, IntentSender target, int sessionId,
+ Throwable cause) {
+ final Intent intent = new Intent();
+ intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+ intent.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_STREAMING);
+ if (cause != null && !TextUtils.isEmpty(cause.getMessage())) {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE,
+ "Staging Image Not Ready [" + cause.getMessage() + "]");
+ } else {
+ intent.putExtra(PackageInstaller.EXTRA_STATUS_MESSAGE, "Staging Image Not Ready");
+ }
+ try {
+ target.sendIntent(context, 0, intent, null, null);
+ } catch (SendIntentException ignored) {
+ }
+ }
+
static void sendOnUserActionRequired(Context context, IntentSender target, int sessionId,
Intent intent) {
final Intent fillIn = new Intent();
fillIn.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
- fillIn.putExtra(PackageInstaller.EXTRA_STATUS,
- PackageInstaller.STATUS_PENDING_USER_ACTION);
+ fillIn.putExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_PENDING_USER_ACTION);
fillIn.putExtra(Intent.EXTRA_INTENT, intent);
try {
target.sendIntent(context, 0, fillIn, null, null);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index d6ca86af34c7..286d291836ec 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_BAD_SIGNATURE;
import static android.content.pm.PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
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_MEDIA_UNAVAILABLE;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SPLIT;
import static android.content.pm.PackageParser.APEX_FILE_EXTENSION;
import static android.content.pm.PackageParser.APK_FILE_EXTENSION;
@@ -30,11 +31,13 @@ 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.readByteArrayAttribute;
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.writeByteArrayAttribute;
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -123,6 +126,7 @@ import java.io.FileDescriptor;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -142,6 +146,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
/** XML constants used for persisting a session */
static final String TAG_SESSION = "session";
static final String TAG_CHILD_SESSION = "childSession";
+ static final String TAG_SESSION_FILE = "sessionFile";
private static final String TAG_GRANTED_RUNTIME_PERMISSION = "granted-runtime-permission";
private static final String TAG_WHITELISTED_RESTRICTED_PERMISSION =
"whitelisted-restricted-permission";
@@ -183,6 +188,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
private static final String ATTR_VOLUME_UUID = "volumeUuid";
private static final String ATTR_NAME = "name";
private static final String ATTR_INSTALL_REASON = "installRason";
+ private static final String ATTR_DATA_LOADER_PACKAGE_NAME = "dataLoaderPackageName";
+ private static final String ATTR_LENGTH_BYTES = "lengthBytes";
+ private static final String ATTR_METADATA = "metadata";
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
@@ -278,6 +286,29 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private int mParentSessionId;
+ static class FileInfo {
+ public final String name;
+ public final Long lengthBytes;
+ public final byte[] metadata;
+
+ public static FileInfo added(String name, Long lengthBytes, byte[] metadata) {
+ return new FileInfo(name, lengthBytes, metadata);
+ }
+
+ public static FileInfo removed(String name) {
+ return new FileInfo(name, -1L, null);
+ }
+
+ FileInfo(String name, Long lengthBytes, byte[] metadata) {
+ this.name = name;
+ this.lengthBytes = lengthBytes;
+ this.metadata = metadata;
+ }
+ }
+
+ @GuardedBy("mLock")
+ private ArrayList<FileInfo> mFiles = new ArrayList<>();
+
@GuardedBy("mLock")
private boolean mStagedSessionApplied;
@GuardedBy("mLock")
@@ -313,6 +344,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private boolean mVerityFound;
+ // TODO(b/146080380): merge file list with Callback installation.
private IncrementalFileStorages mIncrementalFileStorages;
private static final FileFilter sAddedFilter = new FileFilter() {
@@ -344,7 +376,9 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
IntentSender statusReceiver;
switch (msg.what) {
case MSG_SEAL:
- handleSeal((IntentSender) msg.obj);
+ statusReceiver = (IntentSender) msg.obj;
+
+ handleSeal(statusReceiver);
break;
case MSG_COMMIT:
handleCommit();
@@ -378,6 +412,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
};
+ private boolean isDataLoaderInstallation() {
+ return !TextUtils.isEmpty(params.dataLoaderPackageName);
+ }
+
/**
* @return {@code true} iff the installing is app an device owner or affiliated profile owner.
*/
@@ -435,7 +473,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
PackageSessionProvider sessionProvider, Looper looper, StagingManager stagingManager,
int sessionId, int userId, int installerUid, @NonNull InstallSource installSource,
SessionParams params, long createdMillis,
- File stageDir, String stageCid, boolean prepared, boolean committed, boolean sealed,
+ File stageDir, String stageCid, FileInfo[] files, boolean prepared,
+ boolean committed, boolean sealed,
@Nullable int[] childSessionIds, int parentSessionId, boolean isReady,
boolean isFailed, boolean isApplied, int stagedSessionErrorCode,
String stagedSessionErrorMessage) {
@@ -464,6 +503,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
this.mParentSessionId = parentSessionId;
+ if (files != null) {
+ for (FileInfo file : files) {
+ mFiles.add(file);
+ }
+ }
+
if (!params.isMultiPackage && (stageDir == null) == (stageCid == null)) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
@@ -592,15 +637,19 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ @GuardedBy("mLock")
+ private void setClientProgressLocked(float progress) {
+ // Always publish first staging movement
+ final boolean forcePublish = (mClientProgress == 0);
+ mClientProgress = progress;
+ computeProgressLocked(forcePublish);
+ }
+
@Override
public void setClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- // Always publish first staging movement
- final boolean forcePublish = (mClientProgress == 0);
- mClientProgress = progress;
- computeProgressLocked(forcePublish);
+ setClientProgressLocked(progress);
}
}
@@ -608,8 +657,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
public void addClientProgress(float progress) {
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
-
- setClientProgress(mClientProgress + progress);
+ setClientProgressLocked(mClientProgress + progress);
}
}
@@ -637,7 +685,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@GuardedBy("mLock")
private String[] getNamesLocked() {
- return stageDir.list();
+ if (!isDataLoaderInstallation()) {
+ return stageDir.list();
+ }
+ return mFiles.stream().map(fileInfo -> fileInfo.name).toArray(String[]::new);
}
private static File[] filterFiles(File parent, String[] names, FileFilter filter) {
@@ -659,6 +710,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void removeSplit(String splitName) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot remove splits in a callback installation session.");
+ }
if (TextUtils.isEmpty(params.appPackageName)) {
throw new IllegalStateException("Must specify package name to remove a split");
}
@@ -693,8 +748,31 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ private void assertCanWrite(boolean reverseMode) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot write regular files in a callback installation session.");
+ }
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("assertCanWrite");
+ }
+ if (reverseMode) {
+ switch (Binder.getCallingUid()) {
+ case android.os.Process.SHELL_UID:
+ case android.os.Process.ROOT_UID:
+ case android.os.Process.SYSTEM_UID:
+ break;
+ default:
+ throw new SecurityException(
+ "Reverse mode only supported from shell or system");
+ }
+ }
+ }
+
@Override
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
+ assertCanWrite(false);
try {
return doWriteInternal(name, offsetBytes, lengthBytes, null);
} catch (IOException e) {
@@ -705,6 +783,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public void write(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor fd) {
+ assertCanWrite(fd != null);
try {
doWriteInternal(name, offsetBytes, lengthBytes, fd);
} catch (IOException e) {
@@ -720,9 +799,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
final RevocableFileDescriptor fd;
final FileBridge bridge;
synchronized (mLock) {
- assertCallerIsOwnerOrRootLocked();
- assertPreparedAndNotSealedLocked("openWrite");
-
if (PackageInstaller.ENABLE_REVOCABLE_FD) {
fd = new RevocableFileDescriptor();
bridge = null;
@@ -765,16 +841,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (incomingFd != null) {
- switch (Binder.getCallingUid()) {
- case android.os.Process.SHELL_UID:
- case android.os.Process.ROOT_UID:
- case android.os.Process.SYSTEM_UID:
- break;
- default:
- throw new SecurityException(
- "Reverse mode only supported from shell or system");
- }
-
// In "reverse" mode, we're streaming data ourselves from the
// incoming FD, which means we never have to hand out our
// sensitive internal FD. We still rely on a "bridge" being
@@ -786,7 +852,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (params.sizeBytes > 0) {
final long delta = progress - last.value;
last.value = progress;
- addClientProgress((float) delta / (float) params.sizeBytes);
+ synchronized (mLock) {
+ setClientProgressLocked(mClientProgress
+ + (float) delta / (float) params.sizeBytes);
+ }
}
});
} finally {
@@ -821,6 +890,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
@Override
public ParcelFileDescriptor openRead(String name) {
+ if (isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot read regular files in a callback installation session.");
+ }
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotCommittedOrDestroyedLocked("openRead");
@@ -926,6 +999,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return;
}
}
+
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
@@ -988,6 +1062,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
}
+ /** {@hide} */
+ private class StreamingException extends Exception {
+ StreamingException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+
/**
* Sanity checks to make sure it's ok to commit the session.
*/
@@ -1039,12 +1121,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
wasSealed = mSealed;
- if (!mSealed) {
+ try {
+ if (!mSealed) {
+ sealLocked(childSessions);
+ }
+
try {
- sealAndValidateLocked(childSessions);
- } catch (PackageManagerException e) {
+ streamAndValidateLocked();
+ } catch (StreamingException e) {
+ // In case of streaming failure we don't want to fail or commit the session.
+ // Just return from this method and allow caller to commit again.
+ PackageInstallerService.sendPendingStreaming(mContext, mRemoteStatusReceiver,
+ sessionId, e);
return false;
}
+ } catch (PackageManagerException e) {
+ return false;
}
// Client staging is fully done at this point
@@ -1131,17 +1223,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
/**
- * Seal the session to prevent further modification and validate the contents of it.
+ * Convenience wrapper, see {@link #sealLocked(List<PackageInstallerSession>) seal} and
+ * {@link #streamAndValidateLocked()}.
+ */
+ @GuardedBy("mLock")
+ private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ throws PackageManagerException, StreamingException {
+ sealLocked(childSessions);
+ streamAndValidateLocked();
+ }
+
+ /**
+ * Seal the session to prevent further modification.
*
* <p>The session will be sealed after calling this method even if it failed.
*
- * @param childSessions the child sessions of a multipackage that will be checked for
- * consistency. Can be null if session is not multipackage.
* @throws PackageManagerException if the session was sealed but something went wrong. If the
* session was sealed this is the only possible exception.
*/
@GuardedBy("mLock")
- private void sealAndValidateLocked(List<PackageInstallerSession> childSessions)
+ private void sealLocked(List<PackageInstallerSession> childSessions)
throws PackageManagerException {
try {
assertNoWriteFileTransfersOpenLocked();
@@ -1152,7 +1253,26 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (childSessions != null) {
assertMultiPackageConsistencyLocked(childSessions);
}
+ } catch (PackageManagerException e) {
+ throw onSessionVerificationFailure(e);
+ } catch (Throwable e) {
+ // Convert all exceptions into package manager exceptions as only those are handled
+ // in the code above.
+ throw onSessionVerificationFailure(new PackageManagerException(e));
+ }
+ }
+ /**
+ * Prepare DataLoader and stream content for DataLoader sessions.
+ * Validate the contents of all session.
+ *
+ * @throws StreamingException if streaming failed.
+ * @throws PackageManagerException if validation failed.
+ */
+ @GuardedBy("mLock")
+ private void streamAndValidateLocked()
+ throws PackageManagerException, StreamingException {
+ try {
// Read transfers from the original owner stay open, but as the session's data cannot
// be modified anymore, there is no leak of information. For staged sessions, further
// validation is performed by the staging manager.
@@ -1161,6 +1281,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
+ prepareDataLoader();
+
if ((params.installFlags & PackageManager.INSTALL_APEX) != 0) {
validateApexInstallLocked();
} else {
@@ -1173,6 +1295,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
} catch (PackageManagerException e) {
throw onSessionVerificationFailure(e);
+ } catch (StreamingException e) {
+ throw e;
} catch (Throwable e) {
// Convert all exceptions into package manager exceptions as only those are handled
// in the code above.
@@ -1208,6 +1332,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
synchronized (mLock) {
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ Slog.e(TAG, "Streaming failed", e);
} catch (PackageManagerException e) {
Slog.e(TAG, "Package not valid", e);
}
@@ -1277,6 +1403,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
try {
sealAndValidateLocked(childSessions);
+ } catch (StreamingException e) {
+ throw new IllegalArgumentException("Streaming failed", e);
} catch (PackageManagerException e) {
throw new IllegalArgumentException("Package is not valid", e);
}
@@ -2224,6 +2352,132 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
@Override
+ public void addFile(String name, long lengthBytes, byte[] metadata) {
+ if (mIncrementalFileStorages != null) {
+ try {
+ mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
+ } catch (IOException ex) {
+ throw new IllegalStateException(
+ "Failed to add and configure Incremental File: " + name, ex);
+ }
+ }
+ if (!isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot add files to non-callback installation session.");
+ }
+ // Use installer provided name for now; we always rename later
+ if (!FileUtils.isValidExtFilename(name)) {
+ throw new IllegalArgumentException("Invalid name: " + name);
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("addFile");
+
+ mFiles.add(FileInfo.added(name, lengthBytes, metadata));
+ }
+ }
+
+ @Override
+ public void removeFile(String name) {
+ if (!isDataLoaderInstallation()) {
+ throw new IllegalStateException(
+ "Cannot add files to non-callback installation session.");
+ }
+ if (TextUtils.isEmpty(params.appPackageName)) {
+ throw new IllegalStateException("Must specify package name to remove a split");
+ }
+
+ synchronized (mLock) {
+ assertCallerIsOwnerOrRootLocked();
+ assertPreparedAndNotSealedLocked("removeFile");
+
+ mFiles.add(FileInfo.removed(getRemoveMarkerName(name)));
+ }
+ }
+
+ /**
+ * Makes sure files are present in staging location.
+ */
+ private void prepareDataLoader()
+ throws PackageManagerException, StreamingException {
+ if (!isDataLoaderInstallation()) {
+ return;
+ }
+
+ FilesystemConnector connector = new FilesystemConnector();
+
+ FileInfo[] addedFiles = mFiles.stream().filter(
+ file -> sAddedFilter.accept(new File(file.name))).toArray(FileInfo[]::new);
+ String[] removedFiles = mFiles.stream().filter(
+ file -> sRemovedFilter.accept(new File(file.name))).map(
+ file -> file.name.substring(0,
+ file.name.length() - REMOVE_MARKER_EXTENSION.length())).toArray(
+ String[]::new);
+
+ DataLoader dataLoader = new DataLoader();
+ try {
+ dataLoader.onCreate(connector);
+
+ if (!dataLoader.onPrepareImage(addedFiles, removedFiles)) {
+ throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE,
+ "Failed to prepare image.");
+ }
+ } catch (IOException e) {
+ throw new StreamingException(e);
+ } finally {
+ dataLoader.onDestroy();
+ }
+ }
+
+ // TODO(b/146080380): implement DataLoader using Incremental infrastructure.
+ class FilesystemConnector {
+ void writeData(FileInfo fileInfo, long offset, long lengthBytes,
+ ParcelFileDescriptor incomingFd) throws IOException {
+ doWriteInternal(fileInfo.name, offset, lengthBytes, incomingFd);
+ }
+ }
+
+ static class DataLoader {
+ private ParcelFileDescriptor mInFd = null;
+ private FilesystemConnector mConnector = null;
+
+ void onCreate(FilesystemConnector connector) throws IOException {
+ mConnector = connector;
+ }
+
+ void onDestroy() {
+ IoUtils.closeQuietly(mInFd);
+ }
+
+ private static final String STDIN_PATH = "-";
+ boolean onPrepareImage(FileInfo[] addedFiles, String[] removedFiles) throws IOException {
+ for (FileInfo fileInfo : addedFiles) {
+ String filePath = new String(fileInfo.metadata, StandardCharsets.UTF_8);
+ if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
+ if (mInFd == null) {
+ Slog.e(TAG, "Invalid stdin file descriptor.");
+ return false;
+ }
+ ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(mInFd.getFileDescriptor());
+ mConnector.writeData(fileInfo, 0, fileInfo.lengthBytes, inFd);
+ } else {
+ File localFile = new File(filePath);
+ ParcelFileDescriptor incomingFd = null;
+ try {
+ incomingFd = ParcelFileDescriptor.open(localFile,
+ ParcelFileDescriptor.MODE_READ_ONLY);
+ mConnector.writeData(fileInfo, 0, localFile.length(), incomingFd);
+ } finally {
+ IoUtils.closeQuietly(incomingFd);
+ }
+ }
+ }
+ return true;
+ }
+ }
+
+ @Override
public int[] getChildSessionIds() {
final int[] childSessionIds = mChildSessionIds.copyKeys();
if (childSessionIds != null) {
@@ -2295,20 +2549,6 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return mParentSessionId;
}
- @Override
- public void addFile(@NonNull String name, long size, @NonNull byte[] metadata) {
- if (mIncrementalFileStorages == null) {
- throw new IllegalStateException(
- "Cannot add Incremental File to a non-Incremental session.");
- }
- try {
- mIncrementalFileStorages.addFile(new InstallationFile(name, size, metadata));
- } catch (IOException ex) {
- throw new IllegalStateException(
- "Failed to add and configure Incremental File: " + name, ex);
- }
- }
-
private void dispatchSessionFinished(int returnCode, String msg, Bundle extras) {
final IntentSender statusReceiver;
final String packageName;
@@ -2321,7 +2561,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
}
if (statusReceiver != null) {
- // Execute observer.onPackageInstalled on different tread as we don't want callers
+ // Execute observer.onPackageInstalled on different thread as we don't want callers
// inside the system server have to worry about catching the callbacks while they are
// calling into the session
final SomeArgs args = SomeArgs.obtain();
@@ -2595,6 +2835,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeStringAttribute(out, ATTR_VOLUME_UUID, params.volumeUuid);
writeIntAttribute(out, ATTR_INSTALL_REASON, params.installReason);
+ writeStringAttribute(out, ATTR_DATA_LOADER_PACKAGE_NAME, params.dataLoaderPackageName);
+
writeGrantedRuntimePermissionsLocked(out, params.grantedRuntimePermissions);
writeWhitelistedRestrictedPermissionsLocked(out,
params.whitelistedRestrictedPermissions);
@@ -2624,6 +2866,13 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
writeIntAttribute(out, ATTR_SESSION_ID, childSessionId);
out.endTag(null, TAG_CHILD_SESSION);
}
+ for (FileInfo fileInfo : mFiles) {
+ out.startTag(null, TAG_SESSION_FILE);
+ writeStringAttribute(out, ATTR_NAME, fileInfo.name);
+ writeLongAttribute(out, ATTR_LENGTH_BYTES, fileInfo.lengthBytes);
+ writeByteArrayAttribute(out, ATTR_METADATA, fileInfo.metadata);
+ out.endTag(null, TAG_SESSION_FILE);
+ }
}
out.endTag(null, TAG_SESSION);
@@ -2697,6 +2946,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
params.volumeUuid = readStringAttribute(in, ATTR_VOLUME_UUID);
params.installReason = readIntAttribute(in, ATTR_INSTALL_REASON);
+ params.dataLoaderPackageName = readStringAttribute(in, ATTR_DATA_LOADER_PACKAGE_NAME);
+
final File appIconFile = buildAppIconFile(sessionId, sessionsDir);
if (appIconFile.exists()) {
params.appIcon = BitmapFactory.decodeFile(appIconFile.getAbsolutePath());
@@ -2723,6 +2974,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
List<String> grantedRuntimePermissions = new ArrayList<>();
List<String> whitelistedRestrictedPermissions = new ArrayList<>();
List<Integer> childSessionIds = new ArrayList<>();
+ List<FileInfo> files = new ArrayList<>();
int outerDepth = in.getDepth();
int type;
while ((type = in.next()) != XmlPullParser.END_DOCUMENT
@@ -2740,6 +2992,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
if (TAG_CHILD_SESSION.equals(in.getName())) {
childSessionIds.add(readIntAttribute(in, ATTR_SESSION_ID, SessionInfo.INVALID_ID));
}
+ if (TAG_SESSION_FILE.equals(in.getName())) {
+ files.add(new FileInfo(readStringAttribute(in, ATTR_NAME),
+ readLongAttribute(in, ATTR_LENGTH_BYTES, -1),
+ readByteArrayAttribute(in, ATTR_METADATA)));
+ }
}
if (grantedRuntimePermissions.size() > 0) {
@@ -2758,11 +3015,16 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
childSessionIdsArray = EMPTY_CHILD_SESSION_ARRAY;
}
+ FileInfo[] fileInfosArray = null;
+ if (!files.isEmpty()) {
+ fileInfosArray = files.stream().toArray(FileInfo[]::new);
+ }
+
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
installOriginatingPackageName, installerPackageName, false);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
- installSource, params, createdMillis, stageDir, stageCid,
+ installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
prepared, committed, sealed, childSessionIdsArray, parentSessionId,
isReady, isFailed, isApplied, stagedSessionErrorCode, stagedSessionErrorMessage);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index fff404f3ede6..dfffbd6cabef 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -114,6 +114,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -135,6 +136,8 @@ class PackageManagerShellCommand extends ShellCommand {
private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
private static final int DEFAULT_WAIT_MS = 60 * 1000;
+ private static final String PM_SHELL_DATALOADER = "com.android.pm.dataloader";
+
final IPackageManager mInterface;
final IPermissionManager mPermissionManager;
final private WeakHashMap<String, Resources> mResourceCache =
@@ -175,6 +178,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runQueryIntentReceivers();
case "install":
return runInstall();
+ case "install-streaming":
+ return runStreamingInstall();
case "install-abandon":
case "install-destroy":
return runInstallAbandon();
@@ -1152,9 +1157,21 @@ class PackageManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runStreamingInstall() throws RemoteException {
+ final InstallParams params = makeInstallParams();
+ if (TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName)) {
+ params.sessionParams.setDataLoaderPackageName(PM_SHELL_DATALOADER);
+ }
+ return doRunInstall(params);
+ }
+
private int runInstall() throws RemoteException {
+ return doRunInstall(makeInstallParams());
+ }
+
+ private int doRunInstall(final InstallParams params) throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
- final InstallParams params = makeInstallParams();
+ final boolean streaming = !TextUtils.isEmpty(params.sessionParams.dataLoaderPackageName);
ArrayList<String> inPaths = getRemainingArgs();
if (inPaths.isEmpty()) {
@@ -1181,17 +1198,30 @@ class PackageManagerShellCommand extends ShellCommand {
return 1;
}
- setParamsSize(params, inPaths);
+ if (!streaming) {
+ setParamsSize(params, inPaths);
+ }
+
final int sessionId = doCreateSession(params.sessionParams,
params.installerPackageName, params.userId);
boolean abandonSession = true;
try {
for (String inPath : inPaths) {
- String splitName = hasSplits ? (new File(inPath)).getName()
- : "base." + (isApex ? "apex" : "apk");
- if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
- false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
- return 1;
+ if (streaming) {
+ String name = new File(inPath).getName();
+ byte[] metadata = inPath.getBytes(StandardCharsets.UTF_8);
+ if (doAddFile(sessionId, name, params.sessionParams.sizeBytes, metadata,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
+ } else {
+ String splitName = hasSplits ? new File(inPath).getName()
+ : "base." + (isApex ? "apex" : "apk");
+
+ if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, splitName,
+ false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
+ return 1;
+ }
}
}
if (doCommitSession(sessionId, false /*logSuccess*/)
@@ -2927,11 +2957,32 @@ class PackageManagerShellCommand extends ShellCommand {
return sessionId;
}
+ private int doAddFile(int sessionId, String name, long sizeBytes, byte[] metadata,
+ boolean logSuccess) throws RemoteException {
+ PackageInstaller.Session session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+ try {
+ session.addFile(name, sizeBytes, metadata);
+
+ if (logSuccess) {
+ getOutPrintWriter().println("Success");
+ }
+
+ return 0;
+ } finally {
+ IoUtils.closeQuietly(session);
+ }
+ }
+
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
boolean logSuccess) throws RemoteException {
PackageInstaller.Session session = null;
try {
+ session = new PackageInstaller.Session(
+ mInterface.getPackageInstaller().openSession(sessionId));
+
final PrintWriter pw = getOutPrintWriter();
+
final ParcelFileDescriptor fd;
if (STDIN_PATH.equals(inPath)) {
fd = ParcelFileDescriptor.dup(getInFileDescriptor());
@@ -2953,8 +3004,6 @@ class PackageManagerShellCommand extends ShellCommand {
return 1;
}
- session = new PackageInstaller.Session(
- mInterface.getPackageInstaller().openSession(sessionId));
session.write(splitName, 0, sizeBytes, fd);
if (logSuccess) {
@@ -3000,7 +3049,6 @@ class PackageManagerShellCommand extends ShellCommand {
try {
session = new PackageInstaller.Session(
mInterface.getPackageInstaller().openSession(sessionId));
-
for (String splitName : splitNames) {
session.removeSplit(splitName);
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index b751308a4bb4..c478ec472e61 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -173,6 +173,7 @@ public class PackageInstallerSessionTest {
/* createdMillis */ 0L,
/* stageDir */ mTmpDir,
/* stageCid */ null,
+ /* files */ null,
/* prepared */ true,
/* committed */ true,
/* sealed */ false, // Setting to true would trigger some PM logic.