Merge "remove splits" into nyc-dev

This commit is contained in:
Todd Kennedy
2016-03-09 23:43:19 +00:00
committed by Android (Google) Code Review
4 changed files with 197 additions and 20 deletions

View File

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

View File

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

View File

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

View File

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