[incremental] Java service and shell command handler

Renaming:

IIncrementalManager.aidl -> IIncrementalManagerNative.aidl
IIncrementalServiceProxy.aidl -> IIncrementalManager.aidl

Adding implementation of IIncrementalManager.aidl. Also adding
implementation of the shell command handler.

Test: $adb abb incremental help

Change-Id: Ia18fde097f6170f4774552428a2927186ede8389
This commit is contained in:
Songchun Fan
2019-11-29 15:43:58 -08:00
parent 6cde9d9ada
commit f5c894f7ce
9 changed files with 628 additions and 167 deletions

View File

@@ -804,8 +804,8 @@ cc_library {
filegroup {
name: "incremental_aidl",
srcs: [
"core/java/android/os/incremental/IIncrementalManagerNative.aidl",
"core/java/android/os/incremental/IIncrementalManager.aidl",
"core/java/android/os/incremental/IIncrementalServiceProxy.aidl",
"core/java/android/os/incremental/IncrementalDataLoaderParamsParcel.aidl",
"core/java/android/os/incremental/IncrementalFileSystemControlParcel.aidl",
"core/java/android/os/incremental/NamedParcelFileDescriptor.aidl",

View File

@@ -4980,7 +4980,7 @@ public abstract class Context {
* {@link android.os.incremental.IncrementalManager}.
* @hide
*/
public static final String INCREMENTAL_SERVICE = "incremental";
public static final String INCREMENTAL_SERVICE = "incremental_service";
/**
* Determine whether the given permission is allowed for a particular

View File

@@ -16,89 +16,22 @@
package android.os.incremental;
import android.os.incremental.IncrementalFileSystemControlParcel;
import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.content.pm.IDataLoaderStatusListener;
/** @hide */
/**
* Binder service to receive calls from native Incremental Service and handle Java tasks such as
* looking up data loader service package names, binding and talking to the data loader service.
* @hide
*/
interface IIncrementalManager {
/**
* A set of flags for the |createMode| parameters when creating a new Incremental storage.
*/
const int CREATE_MODE_TEMPORARY_BIND = 1;
const int CREATE_MODE_PERMANENT_BIND = 2;
const int CREATE_MODE_CREATE = 4;
const int CREATE_MODE_OPEN_EXISTING = 8;
/**
* Opens or creates a storage given a target path and data loader params. Returns the storage ID.
*/
int openStorage(in @utf8InCpp String path);
int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
* Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
*/
const int BIND_TEMPORARY = 0;
const int BIND_PERMANENT = 1;
int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
/**
* Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
*/
int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
/**
* Creates a directory under a storage. The target directory is specified by its relative path under the storage.
*/
int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
* All the parent directories of the target directory will be created if they do not exist already.
*/
int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Creates a file under a storage, specifying its name, size and metadata.
*/
int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
/**
* Creates a file under a storage. Content of the file is from a range inside another file.
* Both files are specified by relative paths under storage.
*/
int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
/**
* Creates a hard link between two files in two storage instances.
* Source and dest specified by parent storage IDs and their relative paths under the storage.
* The source and dest storage instances should be in the same fs mount.
* Note: destStorageId can be the same as sourceStorageId.
*/
int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
/**
* Deletes a hard link in a storage, specified by the relative path of the link target under storage.
*/
int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Checks if a file's certain range is loaded. File is specified by relative file path under storage.
*/
boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
/**
* Reads the metadata of a file. File is specified by relative path under storage.
*/
byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Starts loading data for a storage.
*/
boolean startLoading(int storageId);
/**
* Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
*/
void deleteStorage(int storageId);
boolean prepareDataLoader(int mountId,
in IncrementalFileSystemControlParcel control,
in IncrementalDataLoaderParamsParcel params,
in IDataLoaderStatusListener listener);
boolean startDataLoader(int mountId);
void showHealthBlockedUI(int mountId);
void destroyDataLoader(int mountId);
void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
}

View File

@@ -0,0 +1,104 @@
/*
* 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.os.incremental.IncrementalDataLoaderParamsParcel;
/** @hide */
interface IIncrementalManagerNative {
/**
* A set of flags for the |createMode| parameters when creating a new Incremental storage.
*/
const int CREATE_MODE_TEMPORARY_BIND = 1;
const int CREATE_MODE_PERMANENT_BIND = 2;
const int CREATE_MODE_CREATE = 4;
const int CREATE_MODE_OPEN_EXISTING = 8;
/**
* Opens or creates a storage given a target path and data loader params. Returns the storage ID.
*/
int openStorage(in @utf8InCpp String path);
int createStorage(in @utf8InCpp String path, in IncrementalDataLoaderParamsParcel params, int createMode);
int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
/**
* Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
*/
const int BIND_TEMPORARY = 0;
const int BIND_PERMANENT = 1;
int makeBindMount(int storageId, in @utf8InCpp String pathUnderStorage, in @utf8InCpp String targetFullPath, int bindType);
/**
* Deletes an existing bind mount on a path under a storage. Returns 0 on success, and -errno on failure.
*/
int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
/**
* Creates a directory under a storage. The target directory is specified by its relative path under the storage.
*/
int makeDirectory(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Recursively creates a directory under a storage. The target directory is specified by its relative path under the storage.
* All the parent directories of the target directory will be created if they do not exist already.
*/
int makeDirectories(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Creates a file under a storage, specifying its name, size and metadata.
*/
int makeFile(int storageId, in @utf8InCpp String pathUnderStorage, long size, in byte[] metadata);
/**
* Creates a file under a storage. Content of the file is from a range inside another file.
* Both files are specified by relative paths under storage.
*/
int makeFileFromRange(int storageId, in @utf8InCpp String targetPathUnderStorage, in @utf8InCpp String sourcePathUnderStorage, long start, long end);
/**
* Creates a hard link between two files in two storage instances.
* Source and dest specified by parent storage IDs and their relative paths under the storage.
* The source and dest storage instances should be in the same fs mount.
* Note: destStorageId can be the same as sourceStorageId.
*/
int makeLink(int sourceStorageId, in @utf8InCpp String sourcePathUnderStorage, int destStorageId, in @utf8InCpp String destPathUnderStorage);
/**
* Deletes a hard link in a storage, specified by the relative path of the link target under storage.
*/
int unlink(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Checks if a file's certain range is loaded. File is specified by relative file path under storage.
*/
boolean isFileRangeLoaded(int storageId, in @utf8InCpp String pathUnderStorage, long start, long end);
/**
* Reads the metadata of a file. File is specified by relative path under storage.
*/
byte[] getFileMetadata(int storageId, in @utf8InCpp String pathUnderStorage);
/**
* Starts loading data for a storage.
*/
boolean startLoading(int storageId);
/**
* Deletes a storage given its ID. Deletes its bind mounts and unmount it. Stop its data loader.
*/
void deleteStorage(int storageId);
}

View File

@@ -1,37 +0,0 @@
/*
* 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.os.incremental.IncrementalFileSystemControlParcel;
import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.content.pm.IDataLoaderStatusListener;
/**
* Binder service to receive calls from native Incremental Service and handle Java tasks such as
* looking up data loader service package names, binding and talking to the data loader service.
* @hide
*/
interface IIncrementalServiceProxy {
boolean prepareDataLoader(int mountId,
in IncrementalFileSystemControlParcel control,
in IncrementalDataLoaderParamsParcel params,
in IDataLoaderStatusListener listener);
boolean startDataLoader(int mountId);
void showHealthBlockedUI(int mountId);
void destroyDataLoader(int mountId);
void newFileForDataLoader(int mountId, long inode, in byte[] metadata);
}

View File

@@ -36,31 +36,28 @@ import java.nio.file.Path;
import java.nio.file.Paths;
/**
* Provides operations to open or create an IncrementalStorage, using IIncrementalManager service.
* Example Usage:
* Provides operations to open or create an IncrementalStorage, using IIncrementalManagerNative
* service. Example Usage:
*
* <blockquote><pre>
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
* </pre></blockquote>
*
* @hide
*/
@SystemService(Context.INCREMENTAL_SERVICE)
public class IncrementalManager {
public final class IncrementalManager {
private static final String TAG = "IncrementalManager";
private final IIncrementalManager mService;
@GuardedBy("mStorages")
private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
public static final int CREATE_MODE_TEMPORARY_BIND =
IIncrementalManager.CREATE_MODE_TEMPORARY_BIND;
IIncrementalManagerNative.CREATE_MODE_TEMPORARY_BIND;
public static final int CREATE_MODE_PERMANENT_BIND =
IIncrementalManager.CREATE_MODE_PERMANENT_BIND;
IIncrementalManagerNative.CREATE_MODE_PERMANENT_BIND;
public static final int CREATE_MODE_CREATE =
IIncrementalManager.CREATE_MODE_CREATE;
IIncrementalManagerNative.CREATE_MODE_CREATE;
public static final int CREATE_MODE_OPEN_EXISTING =
IIncrementalManager.CREATE_MODE_OPEN_EXISTING;
IIncrementalManagerNative.CREATE_MODE_OPEN_EXISTING;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"CREATE_MODE_"}, value = {
@@ -72,8 +69,12 @@ public class IncrementalManager {
public @interface CreateMode {
}
public IncrementalManager(@NonNull IIncrementalManager is) {
mService = is;
private final @Nullable IIncrementalManagerNative mNativeService;
@GuardedBy("mStorages")
private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>();
public IncrementalManager(IIncrementalManagerNative nativeService) {
mNativeService = nativeService;
}
/**
@@ -82,6 +83,7 @@ public class IncrementalManager {
* @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) {
@@ -95,8 +97,8 @@ public class IncrementalManager {
*
* @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 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.
*/
@@ -105,11 +107,11 @@ public class IncrementalManager {
@NonNull IncrementalDataLoaderParams params, @CreateMode int createMode,
boolean autoStartDataLoader) {
try {
final int id = mService.createStorage(path, params.getData(), createMode);
final int id = mNativeService.createStorage(path, params.getData(), createMode);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -123,8 +125,8 @@ public class IncrementalManager {
}
/**
* Opens an existing Incremental File System mounted directory and returns an
* IncrementalStorage object.
* 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.
@@ -132,11 +134,11 @@ public class IncrementalManager {
@Nullable
public IncrementalStorage openStorage(@NonNull String path) {
try {
final int id = mService.openStorage(path);
final int id = mNativeService.openStorage(path);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -155,11 +157,12 @@ public class IncrementalManager {
public IncrementalStorage createStorage(@NonNull String path,
@NonNull IncrementalStorage linkedStorage, @CreateMode int createMode) {
try {
final int id = mService.createLinkedStorage(path, linkedStorage.getId(), createMode);
final int id = mNativeService.createLinkedStorage(
path, linkedStorage.getId(), createMode);
if (id < 0) {
return null;
}
final IncrementalStorage storage = new IncrementalStorage(mService, id);
final IncrementalStorage storage = new IncrementalStorage(mNativeService, id);
synchronized (mStorages) {
mStorages.put(id, storage);
}
@@ -175,6 +178,7 @@ public class IncrementalManager {
* @param file Target file to search storage for.
* @return Absolute path which is a bind-mount point of Incremental File System.
*/
@Nullable
private Path getStoragePathForFile(File file) {
File currentPath = new File(file.getParent());
while (currentPath.getParent() != null) {
@@ -198,18 +202,19 @@ public class IncrementalManager {
* </li>
* </ol>
*
* @param sourcePath Absolute path to the source. Should be the same type as the destPath
* (file or dir). Expected to already exist and is an Incremental path.
* @param destPath Absolute path to the destination.
* @throws IllegalArgumentException when 1) source does not exist,
* or 2) source and dest type mismatch (one is file and the other is dir),
* or 3) source path is not on Incremental File System,
* @throws IOException when 1) cannot find the root path of the Incremental storage of source,
* or 2) cannot retrieve the Incremental storage instance of the source,
* or 3) renaming a file, but dest is not on the same Incremental Storage,
* or 4) renaming a dir, dest dir does not exist but fails to be created.
*
* TODO(b/136132412): add unit tests
* @param sourcePath Absolute path to the source. Should be the same type as the destPath (file
* or dir). Expected to already exist and is an Incremental path.
* @param destPath Absolute path to the destination.
* @throws IllegalArgumentException when 1) source does not exist, or 2) source and dest type
* mismatch (one is file and the other is dir), or 3) source
* path is not on Incremental File System,
* @throws IOException when 1) cannot find the root path of the Incremental storage
* of source, or 2) cannot retrieve the Incremental storage
* instance of the source, or 3) renaming a file, but dest is
* not on the same Incremental Storage, or 4) renaming a dir,
* dest dir does not exist but fails to be created.
* <p>
* TODO(b/136132412): add unit tests
*/
public void rename(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
final File source = new File(sourcePath);
@@ -243,6 +248,7 @@ public class IncrementalManager {
if (storage == null) {
throw new IOException("Failed to retrieve storage from Incremental Service.");
}
if (source.isFile()) {
renameFile(storage, storagePath, source, dest);
} else {
@@ -304,11 +310,11 @@ public class IncrementalManager {
*/
public void closeStorage(@NonNull String path) {
try {
final int id = mService.openStorage(path);
final int id = mNativeService.openStorage(path);
if (id < 0) {
return;
}
mService.deleteStorage(id);
mNativeService.deleteStorage(id);
synchronized (mStorages) {
mStorages.remove(id);
}
@@ -321,7 +327,7 @@ public class IncrementalManager {
* Checks if path is mounted on Incremental File System.
*/
public static boolean isIncrementalPath(@NonNull String path) {
// TODO(b/136132412): implement native method
// TODO(b/136132412): add jni implementation
return false;
}
}

View File

@@ -24,11 +24,11 @@ import java.io.File;
import java.io.IOException;
/**
* Provides operations on an Incremental File System directory, using IncrementalService. Example
* usage:
* Provides operations on an Incremental File System directory, using IncrementalServiceNative.
* Example usage:
*
* <blockquote><pre>
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
* storage.makeDirectory("subdir");
* </pre></blockquote>
@@ -38,10 +38,10 @@ import java.io.IOException;
public final class IncrementalStorage {
private static final String TAG = "IncrementalStorage";
private final int mId;
private final IIncrementalManager mService;
private final IIncrementalManagerNative mService;
public IncrementalStorage(@NonNull IIncrementalManager is, int id) {
public IncrementalStorage(@NonNull IIncrementalManagerNative is, int id) {
mService = is;
mId = id;
}
@@ -72,7 +72,7 @@ public final class IncrementalStorage {
throws IOException {
try {
int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
IIncrementalManager.BIND_TEMPORARY);
IIncrementalManagerNative.BIND_TEMPORARY);
if (res < 0) {
throw new IOException("bind() failed with errno " + -res);
}
@@ -103,7 +103,7 @@ public final class IncrementalStorage {
throws IOException {
try {
int res = mService.makeBindMount(mId, sourcePathUnderStorage, targetPath,
IIncrementalManager.BIND_PERMANENT);
IIncrementalManagerNative.BIND_PERMANENT);
if (res < 0) {
throw new IOException("bind() permanent failed with errno " + -res);
}
@@ -274,7 +274,8 @@ public final class IncrementalStorage {
throw new IOException("moveDir() requires that destination dir already exists.");
}
try {
int res = mService.makeBindMount(mId, "", destPath, IIncrementalManager.BIND_PERMANENT);
int res = mService.makeBindMount(mId, "", destPath,
IIncrementalManagerNative.BIND_PERMANENT);
if (res < 0) {
throw new IOException("moveDir() failed at making bind mount, errno " + -res);
}

View File

@@ -0,0 +1,159 @@
/*
* 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 com.android.server.incremental;
import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.DataLoaderManager;
import android.content.pm.IDataLoader;
import android.content.pm.IDataLoaderStatusListener;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
import android.os.incremental.IIncrementalManager;
import android.os.incremental.IncrementalDataLoaderParamsParcel;
import android.os.incremental.IncrementalFileSystemControlParcel;
import android.util.Slog;
import java.io.FileDescriptor;
/**
* This service has the following purposes:
* 1) Starts the IIncrementalManager binder service.
* 1) Starts the native IIncrementalManagerService binder service.
* 2) Handles shell commands for "incremental" service.
* 3) Handles binder calls from the native IIncrementalManagerService binder service and pass
* them to a data loader binder service.
*/
public class IncrementalManagerService extends IIncrementalManager.Stub {
private static final String TAG = "IncrementalManagerService";
private static final String BINDER_SERVICE_NAME = "incremental";
// DataLoaderManagerService should have been started before us
private @NonNull DataLoaderManager mDataLoaderManager;
private long mNativeInstance;
private final @NonNull Context mContext;
/**
* Starts IIncrementalManager binder service and register to Service Manager.
* Starts the native IIncrementalManagerNative binder service.
*/
public static IncrementalManagerService start(Context context) {
IncrementalManagerService self = new IncrementalManagerService(context);
if (self.mNativeInstance == 0) {
return null;
}
return self;
}
private IncrementalManagerService(Context context) {
mContext = context;
mDataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
ServiceManager.addService(BINDER_SERVICE_NAME, this);
// Starts and register IIncrementalManagerNative service
// TODO(b/136132412): add jni implementation
}
/**
* Notifies native IIncrementalManager service that system is ready.
*/
public void systemReady() {
// TODO(b/136132412): add jni implementation
}
/**
* Finds data loader service provider and binds to it. This requires PackageManager.
*/
@Override
public boolean prepareDataLoader(int mountId, IncrementalFileSystemControlParcel control,
IncrementalDataLoaderParamsParcel params,
IDataLoaderStatusListener listener) {
Bundle dataLoaderParams = new Bundle();
dataLoaderParams.putCharSequence("packageName", params.packageName);
dataLoaderParams.putParcelable("control", control);
dataLoaderParams.putParcelable("params", params);
DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
if (dataLoaderManager == null) {
Slog.e(TAG, "Failed to find data loader manager service");
return false;
}
if (!dataLoaderManager.initializeDataLoader(mountId, dataLoaderParams, listener)) {
Slog.e(TAG, "Failed to initialize data loader");
return false;
}
return true;
}
@Override
public boolean startDataLoader(int mountId) {
IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
if (dataLoader == null) {
Slog.e(TAG, "Start failed to retrieve data loader for ID=" + mountId);
return false;
}
try {
// TODO: fix file list
dataLoader.start(null);
return true;
} catch (RemoteException ex) {
return false;
}
}
@Override
public void destroyDataLoader(int mountId) {
IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
if (dataLoader == null) {
Slog.e(TAG, "Destroy failed to retrieve data loader for ID=" + mountId);
return;
}
try {
dataLoader.destroy();
} catch (RemoteException ex) {
return;
}
}
// TODO: remove this
@Override
public void newFileForDataLoader(int mountId, long inode, byte[] metadata) {
IDataLoader dataLoader = mDataLoaderManager.getDataLoader(mountId);
if (dataLoader == null) {
Slog.e(TAG, "Failed to retrieve data loader for ID=" + mountId);
return;
}
try {
dataLoader.onFileCreated(inode, metadata);
} catch (RemoteException ex) {
}
}
@Override
public void showHealthBlockedUI(int mountId) {
// TODO(b/136132412): implement this
}
@Override
public void onShellCommand(@NonNull FileDescriptor in, @NonNull FileDescriptor out,
FileDescriptor err, @NonNull String[] args, ShellCallback callback,
@NonNull ResultReceiver resultReceiver) {
(new IncrementalManagerShellCommand(mContext)).exec(
this, in, out, err, args, callback, resultReceiver);
}
}

View File

@@ -0,0 +1,295 @@
/*
* 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 com.android.server.incremental;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.InstallationFile;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.ShellCommand;
import android.os.incremental.IncrementalDataLoaderParams;
import android.util.Slog;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Defines actions to handle adb commands like "adb abb incremental ...".
*/
public final class IncrementalManagerShellCommand extends ShellCommand {
private static final String TAG = "IncrementalShellCommand";
// Assuming the adb data loader is always installed on the device
private static final String LOADER_PACKAGE_NAME = "com.android.incremental.nativeadb";
private final @NonNull Context mContext;
private static final int ERROR_INVALID_ARGUMENTS = -1;
private static final int ERROR_DATA_LOADER_INIT = -2;
private static final int ERROR_COMMAND_EXECUTION = -3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({ERROR_INVALID_ARGUMENTS, ERROR_DATA_LOADER_INIT, ERROR_COMMAND_EXECUTION})
public @interface IncrementalShellCommandErrorCode {
}
IncrementalManagerShellCommand(@NonNull Context context) {
mContext = context;
}
@Override
public int onCommand(@Nullable String cmd) {
if (cmd == null) {
return handleDefaultCommands(null);
}
switch (cmd) {
case "install-start":
return runInstallStart();
case "install-finish":
return runInstallFinish();
default:
return handleDefaultCommands(cmd);
}
}
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
pw.println("Incremental Service Commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println(" install-start");
pw.println(" Opens an installation session");
pw.println(" install-finish SESSION_ID --file NAME:SIZE:INDEX --file NAME:SIZE:INDEX ...");
pw.println(" Commits an installation session specified by session ID for an APK ");
pw.println(" or a bundle of splits. Configures lib dirs or OBB files if specified.");
}
private int runInstallStart() {
final PrintWriter pw = getOutPrintWriter();
final PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
if (packageInstaller == null) {
pw.println("Failed to get PackageInstaller.");
return ERROR_COMMAND_EXECUTION;
}
final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs();
if (dataLoaderDynamicArgs == null) {
pw.println("File names and sizes don't match.");
return ERROR_DATA_LOADER_INIT;
}
final IncrementalDataLoaderParams params = new IncrementalDataLoaderParams(
"", LOADER_PACKAGE_NAME, dataLoaderDynamicArgs);
PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
sessionParams.installFlags |= PackageManager.INSTALL_ALL_USERS;
// Replace existing if same package is already installed
sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
sessionParams.setIncrementalParams(params);
try {
int sessionId = packageInstaller.createSession(sessionParams);
pw.println("Successfully opened session: sessionId = " + sessionId);
} catch (Exception ex) {
pw.println("Failed to create session.");
return ERROR_COMMAND_EXECUTION;
} finally {
try {
for (Map.Entry<String, ParcelFileDescriptor> nfd
: dataLoaderDynamicArgs.entrySet()) {
nfd.getValue().close();
}
} catch (IOException ignored) {
}
}
return 0;
}
private int runInstallFinish() {
final PrintWriter pw = getOutPrintWriter();
final int sessionId = parseInt(getNextArgRequired());
final List<InstallationFile> installationFiles = parseFileArgs(pw);
if (installationFiles == null) {
pw.println("Must specify at least one file to install.");
return ERROR_INVALID_ARGUMENTS;
}
final int numFiles = installationFiles.size();
if (numFiles == 0) {
pw.println("Must specify at least one file to install.");
return ERROR_INVALID_ARGUMENTS;
}
final PackageInstaller packageInstaller = mContext.getPackageManager()
.getPackageInstaller();
if (packageInstaller == null) {
pw.println("Failed to get PackageInstaller.");
return ERROR_COMMAND_EXECUTION;
}
final LocalIntentReceiver localReceiver = new LocalIntentReceiver();
boolean success = false;
PackageInstaller.Session session = null;
try {
session = packageInstaller.openSession(sessionId);
for (int i = 0; i < numFiles; i++) {
InstallationFile file = installationFiles.get(i);
session.addFile(file.getName(), file.getSize(), file.getMetadata());
}
session.commit(localReceiver.getIntentSender());
final Intent result = localReceiver.getResult();
final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
PackageInstaller.STATUS_FAILURE);
if (status == PackageInstaller.STATUS_SUCCESS) {
success = true;
pw.println("Success");
return 0;
} else {
pw.println("Failure ["
+ result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
return ERROR_COMMAND_EXECUTION;
}
} catch (Exception e) {
e.printStackTrace(pw);
return ERROR_COMMAND_EXECUTION;
} finally {
if (!success) {
try {
if (session != null) {
session.abandon();
}
} catch (Exception ignore) {
}
}
}
}
private static class LocalIntentReceiver {
private final LinkedBlockingQueue<Intent> mResult = new LinkedBlockingQueue<>();
private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
@Override
public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
IIntentReceiver finishedReceiver, String requiredPermission,
Bundle options) {
try {
mResult.offer(intent, 5, TimeUnit.SECONDS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
public IntentSender getIntentSender() {
return new IntentSender((IIntentSender) mLocalSender);
}
public Intent getResult() {
try {
return mResult.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/** Helpers. */
private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() {
Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
final FileDescriptor outFd = getOutFileDescriptor();
final FileDescriptor inFd = getInFileDescriptor();
try {
dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd));
dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd));
return dataLoaderDynamicArgs;
} catch (Exception ex) {
Slog.e(TAG, "Failed to dup FDs");
return null;
}
}
private long parseLong(String arg) {
long result = -1;
try {
result = Long.parseLong(arg);
} catch (NumberFormatException e) {
}
return result;
}
private int parseInt(String arg) {
int result = -1;
try {
result = Integer.parseInt(arg);
} catch (NumberFormatException e) {
}
return result;
}
private List<InstallationFile> parseFileArgs(PrintWriter pw) {
List<InstallationFile> fileList = new ArrayList<>();
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
case "--file": {
final String fileArgs = getNextArgRequired();
final String[] args = fileArgs.split(":");
if (args.length != 3) {
pw.println("Invalid file args: " + fileArgs);
return null;
}
final String name = args[0];
final long size = parseLong(args[1]);
if (size < 0) {
pw.println("Invalid file size in: " + fileArgs);
return null;
}
final long index = parseLong(args[2]);
if (index < 0) {
pw.println("Invalid file index in: " + fileArgs);
return null;
}
final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8);
fileList.add(new InstallationFile(name, size, metadata));
break;
}
default:
break;
}
}
return fileList;
}
}