Merge "DataLoader version of installation API."
This commit is contained in:
committed by
Android (Google) Code Review
commit
d71569fcc8
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user