Merge "remove splits" into nyc-dev
This commit is contained in:
@@ -29,6 +29,8 @@ interface IPackageInstallerSession {
|
||||
ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes);
|
||||
ParcelFileDescriptor openRead(String name);
|
||||
|
||||
void removeSplit(String splitName);
|
||||
|
||||
void close();
|
||||
void commit(in IntentSender statusReceiver);
|
||||
void abandon();
|
||||
|
||||
@@ -794,6 +794,27 @@ public class PackageInstaller {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a split.
|
||||
* <p>
|
||||
* Split removals occur prior to adding new APKs. If upgrading a feature
|
||||
* split, it is not expected nor desirable to remove the split prior to
|
||||
* upgrading.
|
||||
* <p>
|
||||
* When split removal is bundled with new APKs, the packageName must be
|
||||
* identical.
|
||||
*/
|
||||
public void removeSplit(@NonNull String splitName) throws IOException {
|
||||
try {
|
||||
mSession.removeSplit(splitName);
|
||||
} catch (RuntimeException e) {
|
||||
ExceptionUtils.maybeUnwrapIOException(e);
|
||||
throw e;
|
||||
} 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
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.content.IntentSender;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageInstallObserver2;
|
||||
import android.content.pm.IPackageInstallerSession;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
@@ -58,6 +59,7 @@ import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.system.OsConstants;
|
||||
import android.system.StructStat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArraySet;
|
||||
import android.util.ExceptionUtils;
|
||||
import android.util.MathUtils;
|
||||
@@ -77,6 +79,7 @@ import com.android.server.pm.PackageInstallerService.PackageInstallObserverAdapt
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -86,6 +89,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
private static final String TAG = "PackageInstaller";
|
||||
private static final boolean LOGD = true;
|
||||
private static final String REMOVE_SPLIT_MARKER_EXTENSION = ".removed";
|
||||
|
||||
private static final int MSG_COMMIT = 0;
|
||||
|
||||
@@ -171,6 +175,25 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
@GuardedBy("mLock")
|
||||
private File mInheritedFilesBase;
|
||||
|
||||
private static final FileFilter sAddedFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
// Installers can't stage directories, so it's fine to ignore
|
||||
// entries like "lost+found".
|
||||
if (file.isDirectory()) return false;
|
||||
if (file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private static final FileFilter sRemovedFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File file) {
|
||||
if (file.isDirectory()) return false;
|
||||
if (!file.getName().endsWith(REMOVE_SPLIT_MARKER_EXTENSION)) return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
@@ -345,6 +368,32 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeSplit(String splitName) {
|
||||
if (TextUtils.isEmpty(params.appPackageName)) {
|
||||
throw new IllegalStateException("Must specify package name to remove a split");
|
||||
}
|
||||
try {
|
||||
createRemoveSplitMarker(splitName);
|
||||
} catch (IOException e) {
|
||||
throw ExceptionUtils.wrap(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void createRemoveSplitMarker(String splitName) throws IOException {
|
||||
try {
|
||||
final String markerName = splitName + REMOVE_SPLIT_MARKER_EXTENSION;
|
||||
if (!FileUtils.isValidExtFilename(markerName)) {
|
||||
throw new IllegalArgumentException("Invalid marker: " + markerName);
|
||||
}
|
||||
final File target = new File(resolveStageDir(), markerName);
|
||||
target.createNewFile();
|
||||
Os.chmod(target.getAbsolutePath(), 0 /*mode*/);
|
||||
} catch (ErrnoException e) {
|
||||
throw e.rethrowAsIOException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openWrite(String name, long offsetBytes, long lengthBytes) {
|
||||
try {
|
||||
@@ -608,22 +657,28 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mResolvedStagedFiles.clear();
|
||||
mResolvedInheritedFiles.clear();
|
||||
|
||||
final File[] files = mResolvedStageDir.listFiles();
|
||||
if (ArrayUtils.isEmpty(files)) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
|
||||
final File[] removedFiles = mResolvedStageDir.listFiles(sRemovedFilter);
|
||||
final List<String> removeSplitList = new ArrayList<>();
|
||||
if (!ArrayUtils.isEmpty(removedFiles)) {
|
||||
for (File removedFile : removedFiles) {
|
||||
final String fileName = removedFile.getName();
|
||||
final String splitName = fileName.substring(
|
||||
0, fileName.length() - REMOVE_SPLIT_MARKER_EXTENSION.length());
|
||||
removeSplitList.add(splitName);
|
||||
}
|
||||
}
|
||||
|
||||
final File[] addedFiles = mResolvedStageDir.listFiles(sAddedFilter);
|
||||
if (ArrayUtils.isEmpty(addedFiles) && removeSplitList.size() == 0) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, "No packages staged");
|
||||
}
|
||||
// Verify that all staged packages are internally consistent
|
||||
final ArraySet<String> stagedSplits = new ArraySet<>();
|
||||
for (File file : files) {
|
||||
|
||||
// Installers can't stage directories, so it's fine to ignore
|
||||
// entries like "lost+found".
|
||||
if (file.isDirectory()) continue;
|
||||
|
||||
for (File addedFile : addedFiles) {
|
||||
final ApkLite apk;
|
||||
try {
|
||||
apk = PackageParser.parseApkLite(file, PackageParser.PARSE_COLLECT_CERTIFICATES);
|
||||
apk = PackageParser.parseApkLite(
|
||||
addedFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
|
||||
} catch (PackageParserException e) {
|
||||
throw PackageManagerException.from(e);
|
||||
}
|
||||
@@ -642,7 +697,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mSignatures = apk.signatures;
|
||||
}
|
||||
|
||||
assertApkConsistent(String.valueOf(file), apk);
|
||||
assertApkConsistent(String.valueOf(addedFile), apk);
|
||||
|
||||
// Take this opportunity to enforce uniform naming
|
||||
final String targetName;
|
||||
@@ -657,8 +712,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
}
|
||||
|
||||
final File targetFile = new File(mResolvedStageDir, targetName);
|
||||
if (!file.equals(targetFile)) {
|
||||
file.renameTo(targetFile);
|
||||
if (!addedFile.equals(targetFile)) {
|
||||
addedFile.renameTo(targetFile);
|
||||
}
|
||||
|
||||
// Base is coming from session
|
||||
@@ -669,6 +724,27 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
mResolvedStagedFiles.add(targetFile);
|
||||
}
|
||||
|
||||
if (removeSplitList.size() > 0) {
|
||||
// validate split names marked for removal
|
||||
final int flags = mSignatures == null ? PackageManager.GET_SIGNATURES : 0;
|
||||
final PackageInfo pkg = mPm.getPackageInfo(params.appPackageName, flags, userId);
|
||||
for (String splitName : removeSplitList) {
|
||||
if (!ArrayUtils.contains(pkg.splitNames, splitName)) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
|
||||
"Split not found: " + splitName);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure we've got appropriate package name, version code and signatures
|
||||
if (mPackageName == null) {
|
||||
mPackageName = pkg.packageName;
|
||||
mVersionCode = pkg.versionCode;
|
||||
}
|
||||
if (mSignatures == null) {
|
||||
mSignatures = pkg.signatures;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.mode == SessionParams.MODE_FULL_INSTALL) {
|
||||
// Full installs must include a base package
|
||||
if (!stagedSplits.contains(null)) {
|
||||
@@ -707,8 +783,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
for (int i = 0; i < existing.splitNames.length; i++) {
|
||||
final String splitName = existing.splitNames[i];
|
||||
final File splitFile = new File(existing.splitCodePaths[i]);
|
||||
|
||||
if (!stagedSplits.contains(splitName)) {
|
||||
final boolean splitRemoved = removeSplitList.contains(splitName);
|
||||
if (!stagedSplits.contains(splitName) && !splitRemoved) {
|
||||
mResolvedInheritedFiles.add(splitFile);
|
||||
}
|
||||
}
|
||||
@@ -748,6 +824,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag + " package "
|
||||
+ apk.packageName + " inconsistent with " + mPackageName);
|
||||
}
|
||||
if (params.appPackageName != null && !params.appPackageName.equals(apk.packageName)) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
|
||||
+ " specified package " + params.appPackageName
|
||||
+ " inconsistent with " + apk.packageName);
|
||||
}
|
||||
if (mVersionCode != apk.versionCode) {
|
||||
throw new PackageManagerException(INSTALL_FAILED_INVALID_APK, tag
|
||||
+ " version code " + apk.versionCode + " inconsistent with "
|
||||
|
||||
@@ -95,6 +95,8 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
return runInstallCommit();
|
||||
case "install-create":
|
||||
return runInstallCreate();
|
||||
case "install-remove":
|
||||
return runInstallRemove();
|
||||
case "install-write":
|
||||
return runInstallWrite();
|
||||
case "compile":
|
||||
@@ -136,11 +138,12 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
pw.println("Error: must either specify a package size or an APK file");
|
||||
return 1;
|
||||
}
|
||||
if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
|
||||
if (doWriteSplit(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
|
||||
false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
if (doCommitSession(sessionId, false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
|
||||
if (doCommitSession(sessionId, false /*logSuccess*/)
|
||||
!= PackageInstaller.STATUS_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
abandonSession = false;
|
||||
@@ -225,7 +228,20 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
final int sessionId = Integer.parseInt(getNextArg());
|
||||
final String splitName = getNextArg();
|
||||
final String path = getNextArg();
|
||||
return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
|
||||
return doWriteSplit(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
|
||||
}
|
||||
|
||||
private int runInstallRemove() throws RemoteException {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
|
||||
final int sessionId = Integer.parseInt(getNextArg());
|
||||
|
||||
final String splitName = getNextArg();
|
||||
if (splitName == null) {
|
||||
pw.println("Error: split name not specified");
|
||||
return 1;
|
||||
}
|
||||
return doRemoveSplit(sessionId, splitName, true /*logSuccess*/);
|
||||
}
|
||||
|
||||
private int runCompile() throws RemoteException {
|
||||
@@ -666,12 +682,18 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
}
|
||||
|
||||
String packageName = getNextArg();
|
||||
final String packageName = getNextArg();
|
||||
if (packageName == null) {
|
||||
pw.println("Error: package name not specified");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if a split is specified, just remove it and not the whole package
|
||||
final String splitName = getNextArg();
|
||||
if (splitName != null) {
|
||||
return runRemoveSplit(packageName, splitName);
|
||||
}
|
||||
|
||||
userId = translateUserId(userId, "runUninstall");
|
||||
if (userId == UserHandle.USER_ALL) {
|
||||
userId = UserHandle.USER_SYSTEM;
|
||||
@@ -709,6 +731,36 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private int runRemoveSplit(String packageName, String splitName) throws RemoteException {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
final SessionParams sessionParams = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
|
||||
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
|
||||
sessionParams.appPackageName = packageName;
|
||||
final int sessionId =
|
||||
doCreateSession(sessionParams, null /*installerPackageName*/, UserHandle.USER_ALL);
|
||||
boolean abandonSession = true;
|
||||
try {
|
||||
if (doRemoveSplit(sessionId, splitName, false /*logSuccess*/)
|
||||
!= PackageInstaller.STATUS_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
if (doCommitSession(sessionId, false /*logSuccess*/)
|
||||
!= PackageInstaller.STATUS_SUCCESS) {
|
||||
return 1;
|
||||
}
|
||||
abandonSession = false;
|
||||
pw.println("Success");
|
||||
return 0;
|
||||
} finally {
|
||||
if (abandonSession) {
|
||||
try {
|
||||
doAbandonSession(sessionId, false /*logSuccess*/);
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Intent parseIntentAndUser() throws URISyntaxException {
|
||||
mTargetUser = UserHandle.USER_CURRENT;
|
||||
Intent intent = Intent.parseCommandArgs(this, new Intent.CommandOptionHandler() {
|
||||
@@ -948,7 +1000,7 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
|
||||
private int doWriteSplit(int sessionId, String inPath, long sizeBytes, String splitName,
|
||||
boolean logSuccess) throws RemoteException {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
if ("-".equals(inPath)) {
|
||||
@@ -1004,6 +1056,27 @@ class PackageManagerShellCommand extends ShellCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private int doRemoveSplit(int sessionId, String splitName, boolean logSuccess)
|
||||
throws RemoteException {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
PackageInstaller.Session session = null;
|
||||
try {
|
||||
session = new PackageInstaller.Session(
|
||||
mInterface.getPackageInstaller().openSession(sessionId));
|
||||
session.removeSplit(splitName);
|
||||
|
||||
if (logSuccess) {
|
||||
pw.println("Success");
|
||||
}
|
||||
return 0;
|
||||
} catch (IOException e) {
|
||||
pw.println("Error: failed to remove split; " + e.getMessage());
|
||||
return 1;
|
||||
} finally {
|
||||
IoUtils.closeQuietly(session);
|
||||
}
|
||||
}
|
||||
|
||||
private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
|
||||
final PrintWriter pw = getOutPrintWriter();
|
||||
PackageInstaller.Session session = null;
|
||||
|
||||
Reference in New Issue
Block a user