From f5c894f7ceea7993c23fe19bfe6b59de6836367e Mon Sep 17 00:00:00 2001 From: Songchun Fan Date: Fri, 29 Nov 2019 15:43:58 -0800 Subject: [PATCH] [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 --- Android.bp | 2 +- core/java/android/content/Context.java | 2 +- .../os/incremental/IIncrementalManager.aidl | 97 +----- .../IIncrementalManagerNative.aidl | 104 ++++++ .../incremental/IIncrementalServiceProxy.aidl | 37 --- .../os/incremental/IncrementalManager.java | 82 ++--- .../os/incremental/IncrementalStorage.java | 17 +- .../IncrementalManagerService.java | 159 ++++++++++ .../IncrementalManagerShellCommand.java | 295 ++++++++++++++++++ 9 files changed, 628 insertions(+), 167 deletions(-) create mode 100644 core/java/android/os/incremental/IIncrementalManagerNative.aidl delete mode 100644 core/java/android/os/incremental/IIncrementalServiceProxy.aidl create mode 100644 services/core/java/com/android/server/incremental/IncrementalManagerService.java create mode 100644 services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java diff --git a/Android.bp b/Android.bp index 04b4e6edf2ec3..2e750f959ea73 100644 --- a/Android.bp +++ b/Android.bp @@ -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", diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 7b580c3bde793..881b42ae4af1c 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -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 diff --git a/core/java/android/os/incremental/IIncrementalManager.aidl b/core/java/android/os/incremental/IIncrementalManager.aidl index d6446d485af51..f84d7efe06b1f 100644 --- a/core/java/android/os/incremental/IIncrementalManager.aidl +++ b/core/java/android/os/incremental/IIncrementalManager.aidl @@ -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); } diff --git a/core/java/android/os/incremental/IIncrementalManagerNative.aidl b/core/java/android/os/incremental/IIncrementalManagerNative.aidl new file mode 100644 index 0000000000000..d9c7c6b5cc212 --- /dev/null +++ b/core/java/android/os/incremental/IIncrementalManagerNative.aidl @@ -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); +} diff --git a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl b/core/java/android/os/incremental/IIncrementalServiceProxy.aidl deleted file mode 100644 index ffff52e5aac90..0000000000000 --- a/core/java/android/os/incremental/IIncrementalServiceProxy.aidl +++ /dev/null @@ -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); -} diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index 5aabf86e17e6d..c30f5589a8353 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -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: * *
- * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_MANAGER);
+ * IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
  * IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
  * 
* * @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 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 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 { * * * - * @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. + *

+ * 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; } } diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index 2bf89ed7f7e80..275086832c515 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -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: * *

- * 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");
  * 
@@ -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); } diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerService.java b/services/core/java/com/android/server/incremental/IncrementalManagerService.java new file mode 100644 index 0000000000000..1b1a292936397 --- /dev/null +++ b/services/core/java/com/android/server/incremental/IncrementalManagerService.java @@ -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); + } +} diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java new file mode 100644 index 0000000000000..d35e806b2685c --- /dev/null +++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java @@ -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 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 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 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 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 getDataLoaderDynamicArgs() { + Map 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 parseFileArgs(PrintWriter pw) { + List 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; + } +}