blob: b071e197aacc6cfeb39abb704c68530c16d2bf87 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.deployer;
import com.android.tools.deployer.model.Apk;
import com.android.tools.deployer.model.ApkEntry;
import com.android.tools.deployer.model.FileDiff;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Class that performs a configurable overlay diff between a newly built set of APKs and the current
* state of an existing overlay. The diff generates a set of files to extract from the APK and push
* to the device, and a set of files currently on device that should be deleted.
*
* <p>The diff throws an exception if the new set of APKs is missing a file that was contained in
* the base installation of the overlay, or if a changed file has an unsupported type. The set of
* supported file types is configurable.
*/
class OverlayDiffer {
public static class Result {
public final Collection<ApkEntry> filesToAdd;
public final Collection<String> filesToRemove;
private Result(Collection<ApkEntry> filesToAdd, Collection<String> filesToRemove) {
this.filesToAdd = filesToAdd;
this.filesToRemove = filesToRemove;
}
}
private final EnumSet<ChangeType> supportedChanges;
public OverlayDiffer(EnumSet<ChangeType> supportedChanges) {
this.supportedChanges = supportedChanges;
}
/**
* Compares a list of built APKs to a list of installed APKs to produce a collection of updates
* that will allow the overlay to reflect the delta between the new APKs and the installed APKs.
*
* @param newApks the APKs being deployed to the device
* @param overlayId the overlay info object containing the APKs that are currently installed on
* the device and the contents of the current overlay on the device
* @return a {@link Result} object describing the files that must be extracted from APKs and
* pushed to the overlay, as well as the files that must be deleted from the overlay.
* @throws DeployerException if a change cannot be supported by overlay update
*/
public Result diff(List<Apk> newApks, OverlayId overlayId) throws DeployerException {
return new Result(
getFilesToAdd(
newApks, overlayId.getInstalledApks(), overlayId.getOverlayContents()),
getFilesToRemove(newApks, overlayId.getOverlayContents()));
}
// Any files in the new APKs that aren't in the base APKs and are not already in the overlay
// must be added to the overlay.
private Collection<ApkEntry> getFilesToAdd(
List<Apk> newApks, List<Apk> installedApks, OverlayId.Contents overlayContents)
throws DeployerException {
ArrayList<ApkEntry> files = new ArrayList<>();
for (FileDiff diff : new ApkDiffer().diff(installedApks, newApks)) {
// Ensure that we currently have IWI support enabled for every change; otherwise, throw
// an exception to cause a fallback to delta install.
ChangeType type = ChangeType.getType(diff);
if (!supportedChanges.contains(type)) {
throw DeployerException.changeNotSupportedByIWI(type);
}
// IWI can't currently handle deleting files that are in the base APK.
if (diff.status == FileDiff.Status.DELETED) {
throw DeployerException.deleteInstalledFileNotSupported();
}
String qualifiedPath = diff.newFile.getQualifiedPath();
if (diff.newFile.getChecksum() != overlayContents.getFileChecksum(qualifiedPath)) {
files.add(diff.newFile);
}
}
return files;
}
// Any files in the overlay that are NOT in the new APK must be deleted from the overlay. This
// handles removing any "extra" files, such as the ones added by IWI swap.
private static Collection<String> getFilesToRemove(
List<Apk> newApks, OverlayId.Contents overlayContents) {
Set<String> files = new HashSet<>(overlayContents.allFiles());
newApks.stream()
.flatMap(apk -> apk.apkEntries.values().stream())
.map(ApkEntry::getQualifiedPath)
.forEach(files::remove);
return files;
}
}