Merge "DataLoader version of installation API."

This commit is contained in:
TreeHugger Robot
2019-12-17 04:50:32 +00:00
committed by Android (Google) Code Review
6 changed files with 466 additions and 85 deletions

View File

@@ -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);
}

View File

@@ -230,6 +230,15 @@ public class PackageInstaller {
/** {@hide} */
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
@@ -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
@@ -1219,27 +1271,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>

View File

@@ -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);

View File

@@ -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);
}
@@ -2223,6 +2351,132 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
return params.isStaged;
}
@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();
@@ -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);
}

View File

@@ -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 runInstall() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
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 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);
}

View File

@@ -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.