blob: 916edfae679f32efba7352c03653991c58a7ad81 [file] [log] [blame]
/*
* Copyright (C) 2019 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 android.os.incremental;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.content.pm.DataLoaderParams;
import android.content.pm.IDataLoaderStatusListener;
import android.os.RemoteException;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
/**
* Provides operations to open or create an IncrementalStorage, using IIncrementalService
* service. Example Usage:
*
* <blockquote><pre>
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
* </pre></blockquote>
*
* @hide
*/
@SystemService(Context.INCREMENTAL_SERVICE)
public final class IncrementalManager {
private static final String TAG = "IncrementalManager";
private static final String ALLOWED_PROPERTY = "incremental.allowed";
public static final int CREATE_MODE_TEMPORARY_BIND =
IIncrementalService.CREATE_MODE_TEMPORARY_BIND;
public static final int CREATE_MODE_PERMANENT_BIND =
IIncrementalService.CREATE_MODE_PERMANENT_BIND;
public static final int CREATE_MODE_CREATE =
IIncrementalService.CREATE_MODE_CREATE;
public static final int CREATE_MODE_OPEN_EXISTING =
IIncrementalService.CREATE_MODE_OPEN_EXISTING;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CREATE_MODE_"}, value = {
CREATE_MODE_TEMPORARY_BIND,
CREATE_MODE_PERMANENT_BIND,
CREATE_MODE_CREATE,
CREATE_MODE_OPEN_EXISTING,
})
public @interface CreateMode {
}
private final @Nullable IIncrementalService mService;
@GuardedBy("mStorages")
private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
public IncrementalManager(IIncrementalService service) {
mService = service;
}
/**
* Returns a storage object given a storage ID.
*
* @param storageId The storage ID to identify the storage object.
* @return IncrementalStorage object corresponding to storage ID.
*/
// TODO(b/136132412): remove this
@Nullable
public IncrementalStorage getStorage(int storageId) {
synchronized (mStorages) {
return mStorages.get(storageId);
}
}
/**
* Opens or create an Incremental File System mounted directory and returns an
* IncrementalStorage object.
*
* @param path Absolute path to mount Incremental File System on.
* @param params IncrementalDataLoaderParams object to configure data loading.
* @param createMode Mode for opening an old Incremental File System mount or creating
* a new mount.
* @param autoStartDataLoader Set true to immediately start data loader after creating storage.
* @return IncrementalStorage object corresponding to the mounted directory.
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
@NonNull DataLoaderParams params, @Nullable IDataLoaderStatusListener listener,
@CreateMode int createMode,
boolean autoStartDataLoader) {
try {
final int id = mService.createStorage(path, params.getData(), listener, createMode);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
if (autoStartDataLoader) {
storage.startLoading();
}
return storage;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Opens an existing Incremental File System mounted directory and returns an IncrementalStorage
* object.
*
* @param path Absolute target path that Incremental File System has been mounted on.
* @return IncrementalStorage object corresponding to the mounted directory.
*/
@Nullable
public IncrementalStorage openStorage(@NonNull String path) {
try {
final int id = mService.openStorage(path);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
return storage;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Opens or creates an IncrementalStorage that is linked to another IncrementalStorage.
*
* @return IncrementalStorage object corresponding to the linked storage.
*/
@Nullable
public IncrementalStorage createStorage(@NonNull String path,
@NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
try {
final int id = mService.createLinkedStorage(
path, linkedStorage.getId(), createMode);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
return storage;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Set up an app's code path. The expected outcome of this method is:
* 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
* of {@code afterCodeFile}.
* 2) All the files under {@code beforeCodeFile} will show up under {@code afterCodeFile}.
*
* @param beforeCodeFile Path that is currently bind-mounted and have APKs under it.
* Should no longer have any APKs after this method is called.
* Example: /data/app/vmdl*tmp
* @param afterCodeFile Path that should will have APKs after this method is called. Its parent
* directory should be bind-mounted to a directory under /data/incremental.
* Example: /data/app/~~[randomStringA]/[packageName]-[randomStringB]
* @throws IllegalArgumentException
* @throws IOException
* TODO(b/147371381): add unit tests
*/
public void renameCodePath(File beforeCodeFile, File afterCodeFile)
throws IllegalArgumentException, IOException {
final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
if (apkStorage == null) {
throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
}
final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
final IncrementalStorage linkedApkStorage =
createStorage(targetStorageDir, apkStorage,
IncrementalManager.CREATE_MODE_CREATE
| IncrementalManager.CREATE_MODE_PERMANENT_BIND);
if (linkedApkStorage == null) {
throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
}
try {
final String afterCodePathName = afterCodeFile.getName();
linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
apkStorage.unBind(beforeCodeAbsolute.toString());
} catch (Exception e) {
linkedApkStorage.unBind(targetStorageDir);
throw e;
}
}
/**
* Recursively set up directories and link all the files from source storage to target storage.
*
* @param sourceStorage The storage that has all the files and directories underneath.
* @param sourceAbsolutePath The absolute path of the directory that holds all files and dirs.
* @param sourceRelativePath The relative path on the source directory, e.g., "" or "lib".
* @param targetStorage The target storage that will have the same files and directories.
* @param targetRelativePath The relative path to the directory on the target storage that
* should have all the files and dirs underneath,
* e.g., "packageName-random".
* @throws IOException When makeDirectory or makeLink fails on the Incremental File System.
*/
private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
String sourceRelativePath, IncrementalStorage targetStorage,
String targetRelativePath) throws IOException {
final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
final Path targetRelative = Paths.get(targetRelativePath);
Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
final Path relativeDir = sourceBase.relativize(dir);
targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
final Path relativeFile = sourceBase.relativize(file);
sourceStorage.makeLink(
file.toAbsolutePath().toString(), targetStorage,
targetRelative.resolve(relativeFile).toString());
return FileVisitResult.CONTINUE;
}
});
}
/**
* Closes a storage specified by the absolute path. If the path is not Incremental, do nothing.
* Unbinds the target dir and deletes the corresponding storage instance.
*/
public void closeStorage(@NonNull String path) {
try {
final int id = mService.openStorage(path);
if (id < 0) {
return;
}
mService.deleteStorage(id);
synchronized (mStorages) {
mStorages.remove(id);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Checks if Incremental feature is enabled on this device.
*/
public static boolean isFeatureEnabled() {
return nativeIsEnabled();
}
/**
* Checks if Incremental installations are allowed.
* A developer can disable Incremental installations by setting the property.
*/
public static boolean isAllowed() {
return isFeatureEnabled() && android.os.SystemProperties.getBoolean(ALLOWED_PROPERTY, true);
}
/**
* Checks if path is mounted on Incremental File System.
*/
public static boolean isIncrementalPath(@NonNull String path) {
return nativeIsIncrementalPath(path);
}
/**
* Returns raw signature for file if it's on Incremental File System.
* Unsafe, use only if you are sure what you are doing.
*/
public static @Nullable byte[] unsafeGetFileSignature(@NonNull String path) {
return nativeUnsafeGetFileSignature(path);
}
/* Native methods */
private static native boolean nativeIsEnabled();
private static native boolean nativeIsIncrementalPath(@NonNull String path);
private static native byte[] nativeUnsafeGetFileSignature(@NonNull String path);
}