605 lines
21 KiB
Java
605 lines
21 KiB
Java
![]() |
/*
|
||
|
* 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.NonNull;
|
||
|
import android.annotation.Nullable;
|
||
|
import android.content.pm.DataLoaderParams;
|
||
|
import android.content.pm.IDataLoaderStatusListener;
|
||
|
import android.os.PersistableBundle;
|
||
|
import android.os.RemoteException;
|
||
|
|
||
|
import java.io.File;
|
||
|
import java.io.IOException;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.Objects;
|
||
|
import java.util.UUID;
|
||
|
|
||
|
/**
|
||
|
* Provides operations on an Incremental File System directory, using IncrementalServiceNative.
|
||
|
* Example usage:
|
||
|
*
|
||
|
* <blockquote><pre>
|
||
|
* IncrementalManager manager = (IncrementalManager) getSystemService(Context.INCREMENTAL_SERVICE);
|
||
|
* IncrementalStorage storage = manager.openStorage("/path/to/incremental/dir");
|
||
|
* storage.makeDirectory("subdir");
|
||
|
* </pre></blockquote>
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
public final class IncrementalStorage {
|
||
|
private static final String TAG = "IncrementalStorage";
|
||
|
private final int mId;
|
||
|
private final IIncrementalService mService;
|
||
|
|
||
|
|
||
|
public IncrementalStorage(@NonNull IIncrementalService is, int id) {
|
||
|
mService = is;
|
||
|
mId = id;
|
||
|
}
|
||
|
|
||
|
public int getId() {
|
||
|
return mId;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Temporarily bind-mounts the current storage directory to a target directory. The bind-mount
|
||
|
* will NOT be preserved between device reboots.
|
||
|
*
|
||
|
* @param targetPath Absolute path to the target directory.
|
||
|
*/
|
||
|
public void bind(@NonNull String targetPath) throws IOException {
|
||
|
bind("", targetPath);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Temporarily bind-mounts a subdir under the current storage directory to a target directory.
|
||
|
* The bind-mount will NOT be preserved between device reboots.
|
||
|
*
|
||
|
* @param sourcePath Source path as a relative path under current storage
|
||
|
* directory.
|
||
|
* @param targetPath Absolute path to the target directory.
|
||
|
*/
|
||
|
public void bind(@NonNull String sourcePath, @NonNull String targetPath)
|
||
|
throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeBindMount(mId, sourcePath, targetPath,
|
||
|
IIncrementalService.BIND_TEMPORARY);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("bind() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Permanently bind-mounts the current storage directory to a target directory. The bind-mount
|
||
|
* WILL be preserved between device reboots.
|
||
|
*
|
||
|
* @param targetPath Absolute path to the target directory.
|
||
|
*/
|
||
|
public void bindPermanent(@NonNull String targetPath) throws IOException {
|
||
|
bindPermanent("", targetPath);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Permanently bind-mounts a subdir under the current storage directory to a target directory.
|
||
|
* The bind-mount WILL be preserved between device reboots.
|
||
|
*
|
||
|
* @param sourcePath Relative path under the current storage directory.
|
||
|
* @param targetPath Absolute path to the target directory.
|
||
|
*/
|
||
|
public void bindPermanent(@NonNull String sourcePath, @NonNull String targetPath)
|
||
|
throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeBindMount(mId, sourcePath, targetPath,
|
||
|
IIncrementalService.BIND_PERMANENT);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("bind() permanent failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unbinds a bind mount.
|
||
|
*
|
||
|
* @param targetPath Absolute path to the target directory.
|
||
|
*/
|
||
|
public void unBind(@NonNull String targetPath) throws IOException {
|
||
|
try {
|
||
|
int res = mService.deleteBindMount(mId, targetPath);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("unbind() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a sub-directory under the current storage directory.
|
||
|
*
|
||
|
* @param path Relative path of the sub-directory, e.g., "subdir"
|
||
|
*/
|
||
|
public void makeDirectory(@NonNull String path) throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeDirectory(mId, path);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("makeDirectory() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a sub-directory under the current storage directory. If its parent dirs do not exist,
|
||
|
* create the parent dirs as well.
|
||
|
*
|
||
|
* @param path Full path.
|
||
|
*/
|
||
|
public void makeDirectories(@NonNull String path) throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeDirectories(mId, path);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("makeDirectory() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a file under the current storage directory.
|
||
|
*
|
||
|
* @param path Relative path of the new file.
|
||
|
* @param size Size of the new file in bytes.
|
||
|
* @param mode File access permission mode.
|
||
|
* @param metadata Metadata bytes.
|
||
|
* @param v4signatureBytes Serialized V4SignatureProto.
|
||
|
* @param content Optionally set file content.
|
||
|
*/
|
||
|
public void makeFile(@NonNull String path, long size, int mode, @Nullable UUID id,
|
||
|
@Nullable byte[] metadata, @Nullable byte[] v4signatureBytes, @Nullable byte[] content)
|
||
|
throws IOException {
|
||
|
try {
|
||
|
if (id == null && metadata == null) {
|
||
|
throw new IOException("File ID and metadata cannot both be null");
|
||
|
}
|
||
|
validateV4Signature(v4signatureBytes);
|
||
|
final IncrementalNewFileParams params = new IncrementalNewFileParams();
|
||
|
params.size = size;
|
||
|
params.metadata = (metadata == null ? new byte[0] : metadata);
|
||
|
params.fileId = idToBytes(id);
|
||
|
params.signature = v4signatureBytes;
|
||
|
int res = mService.makeFile(mId, path, mode, params, content);
|
||
|
if (res != 0) {
|
||
|
throw new IOException("makeFile() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a file in Incremental storage. The content of the file is mapped from a range inside
|
||
|
* a source file in the same storage.
|
||
|
*
|
||
|
* @param destPath Target full path.
|
||
|
* @param sourcePath Source full path.
|
||
|
* @param rangeStart Starting offset (in bytes) in the source file.
|
||
|
* @param rangeEnd Ending offset (in bytes) in the source file.
|
||
|
*/
|
||
|
public void makeFileFromRange(@NonNull String destPath,
|
||
|
@NonNull String sourcePath, long rangeStart, long rangeEnd) throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeFileFromRange(mId, destPath, sourcePath,
|
||
|
rangeStart, rangeEnd);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("makeFileFromRange() failed, errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Creates a hard-link between two paths, which can be under different storages but in the same
|
||
|
* Incremental File System.
|
||
|
*
|
||
|
* @param sourcePath The absolute path of the source.
|
||
|
* @param destStorage The target storage of the link target.
|
||
|
* @param destPath The absolute path of the target.
|
||
|
*/
|
||
|
public void makeLink(@NonNull String sourcePath, IncrementalStorage destStorage,
|
||
|
@NonNull String destPath) throws IOException {
|
||
|
try {
|
||
|
int res = mService.makeLink(mId, sourcePath, destStorage.getId(),
|
||
|
destPath);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("makeLink() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deletes a hard-link under the current storage directory.
|
||
|
*
|
||
|
* @param path The absolute path of the target.
|
||
|
*/
|
||
|
public void unlink(@NonNull String path) throws IOException {
|
||
|
try {
|
||
|
int res = mService.unlink(mId, path);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("unlink() failed with errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rename an old file name to a new file name under the current storage directory.
|
||
|
*
|
||
|
* @param sourcepath Old file path as a full path to the storage directory.
|
||
|
* @param destpath New file path as a full path to the storage directory.
|
||
|
*/
|
||
|
public void moveFile(@NonNull String sourcepath,
|
||
|
@NonNull String destpath) throws IOException {
|
||
|
//TODO(zyy): implement using rename(2) when confirmed that IncFS supports it.
|
||
|
try {
|
||
|
int res = mService.makeLink(mId, sourcepath, mId, destpath);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("moveFile() failed at makeLink(), errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
try {
|
||
|
mService.unlink(mId, sourcepath);
|
||
|
} catch (RemoteException ignored) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Move a directory, which is bind-mounted to a given storage, to a new location. The bind mount
|
||
|
* will be persistent between reboots.
|
||
|
*
|
||
|
* @param sourcePath The old path of the directory as an absolute path.
|
||
|
* @param destPath The new path of the directory as an absolute path, expected to already
|
||
|
* exist.
|
||
|
*/
|
||
|
public void moveDir(@NonNull String sourcePath, @NonNull String destPath) throws IOException {
|
||
|
if (!new File(destPath).exists()) {
|
||
|
throw new IOException("moveDir() requires that destination dir already exists.");
|
||
|
}
|
||
|
try {
|
||
|
int res = mService.makeBindMount(mId, sourcePath, destPath,
|
||
|
IIncrementalService.BIND_PERMANENT);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("moveDir() failed at making bind mount, errno " + -res);
|
||
|
}
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
try {
|
||
|
mService.deleteBindMount(mId, sourcePath);
|
||
|
} catch (RemoteException ignored) {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks whether a file under the current storage directory is fully loaded.
|
||
|
*
|
||
|
* @param path The relative path of the file.
|
||
|
* @return True if the file is fully loaded.
|
||
|
*/
|
||
|
public boolean isFileFullyLoaded(@NonNull String path) throws IOException {
|
||
|
try {
|
||
|
int res = mService.isFileFullyLoaded(mId, path);
|
||
|
if (res < 0) {
|
||
|
throw new IOException("isFileFullyLoaded() failed, errno " + -res);
|
||
|
}
|
||
|
return res == 0;
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Checks if all files in the storage are fully loaded.
|
||
|
*/
|
||
|
public boolean isFullyLoaded() throws IOException {
|
||
|
try {
|
||
|
final int res = mService.isFullyLoaded(mId);
|
||
|
if (res < 0) {
|
||
|
throw new IOException(
|
||
|
"isFullyLoaded() failed at querying loading progress, errno " + -res);
|
||
|
}
|
||
|
return res == 0;
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the loading progress of a storage
|
||
|
*
|
||
|
* @return progress value between [0, 1].
|
||
|
*/
|
||
|
public float getLoadingProgress() throws IOException {
|
||
|
try {
|
||
|
final float res = mService.getLoadingProgress(mId);
|
||
|
if (res < 0) {
|
||
|
throw new IOException(
|
||
|
"getLoadingProgress() failed at querying loading progress, errno " + -res);
|
||
|
}
|
||
|
return res;
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the metadata object of an IncFs File.
|
||
|
*
|
||
|
* @param path The relative path of the file.
|
||
|
* @return Byte array that contains metadata bytes.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public byte[] getFileMetadata(@NonNull String path) {
|
||
|
try {
|
||
|
return mService.getMetadataByPath(mId, path);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the metadata object of an IncFs File.
|
||
|
*
|
||
|
* @param id The file id.
|
||
|
* @return Byte array that contains metadata bytes.
|
||
|
*/
|
||
|
@Nullable
|
||
|
public byte[] getFileMetadata(@NonNull UUID id) {
|
||
|
try {
|
||
|
final byte[] rawId = idToBytes(id);
|
||
|
return mService.getMetadataById(mId, rawId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes and starts the DataLoader.
|
||
|
* This makes sure all install-time parameters are applied.
|
||
|
* Does not affect persistent DataLoader params.
|
||
|
* @return True if start request was successfully queued.
|
||
|
*/
|
||
|
public boolean startLoading(
|
||
|
@NonNull DataLoaderParams dataLoaderParams,
|
||
|
@Nullable IDataLoaderStatusListener statusListener,
|
||
|
@Nullable StorageHealthCheckParams healthCheckParams,
|
||
|
@Nullable IStorageHealthListener healthListener,
|
||
|
@NonNull PerUidReadTimeouts[] perUidReadTimeouts) {
|
||
|
Objects.requireNonNull(perUidReadTimeouts);
|
||
|
try {
|
||
|
return mService.startLoading(mId, dataLoaderParams.getData(), statusListener,
|
||
|
healthCheckParams, healthListener, perUidReadTimeouts);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Marks the completion of installation.
|
||
|
*/
|
||
|
public void onInstallationComplete() {
|
||
|
try {
|
||
|
mService.onInstallationComplete(mId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
private static final int UUID_BYTE_SIZE = 16;
|
||
|
|
||
|
/**
|
||
|
* Converts UUID to a byte array usable for Incremental API calls
|
||
|
*
|
||
|
* @param id The id to convert
|
||
|
* @return Byte array that contains the same ID.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static byte[] idToBytes(@Nullable UUID id) {
|
||
|
if (id == null) {
|
||
|
return new byte[0];
|
||
|
}
|
||
|
final ByteBuffer buf = ByteBuffer.wrap(new byte[UUID_BYTE_SIZE]);
|
||
|
buf.putLong(id.getMostSignificantBits());
|
||
|
buf.putLong(id.getLeastSignificantBits());
|
||
|
return buf.array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Converts UUID from a byte array usable for Incremental API calls
|
||
|
*
|
||
|
* @param bytes The id in byte array format, 16 bytes long
|
||
|
* @return UUID constructed from the byte array.
|
||
|
*/
|
||
|
@NonNull
|
||
|
public static UUID bytesToId(byte[] bytes) throws IllegalArgumentException {
|
||
|
if (bytes.length != UUID_BYTE_SIZE) {
|
||
|
throw new IllegalArgumentException("Expected array of size " + UUID_BYTE_SIZE
|
||
|
+ ", got " + bytes.length);
|
||
|
}
|
||
|
final ByteBuffer buf = ByteBuffer.wrap(bytes);
|
||
|
long msb = buf.getLong();
|
||
|
long lsb = buf.getLong();
|
||
|
return new UUID(msb, lsb);
|
||
|
}
|
||
|
|
||
|
private static final int INCFS_MAX_HASH_SIZE = 32; // SHA256
|
||
|
private static final int INCFS_MAX_ADD_DATA_SIZE = 128;
|
||
|
|
||
|
/**
|
||
|
* Permanently disable readlogs collection.
|
||
|
*/
|
||
|
public void disallowReadLogs() {
|
||
|
try {
|
||
|
mService.disallowReadLogs(mId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Deserialize and validate v4 signature bytes.
|
||
|
*/
|
||
|
private static void validateV4Signature(@Nullable byte[] v4signatureBytes)
|
||
|
throws IOException {
|
||
|
if (v4signatureBytes == null || v4signatureBytes.length == 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
final V4Signature signature;
|
||
|
try {
|
||
|
signature = V4Signature.readFrom(v4signatureBytes);
|
||
|
} catch (IOException e) {
|
||
|
throw new IOException("Failed to read v4 signature:", e);
|
||
|
}
|
||
|
|
||
|
if (!signature.isVersionSupported()) {
|
||
|
throw new IOException("v4 signature version " + signature.version
|
||
|
+ " is not supported");
|
||
|
}
|
||
|
|
||
|
final V4Signature.HashingInfo hashingInfo = V4Signature.HashingInfo.fromByteArray(
|
||
|
signature.hashingInfo);
|
||
|
final V4Signature.SigningInfos signingInfos = V4Signature.SigningInfos.fromByteArray(
|
||
|
signature.signingInfos);
|
||
|
|
||
|
if (hashingInfo.hashAlgorithm != V4Signature.HASHING_ALGORITHM_SHA256) {
|
||
|
throw new IOException("Unsupported hashAlgorithm: " + hashingInfo.hashAlgorithm);
|
||
|
}
|
||
|
if (hashingInfo.log2BlockSize != V4Signature.LOG2_BLOCK_SIZE_4096_BYTES) {
|
||
|
throw new IOException("Unsupported log2BlockSize: " + hashingInfo.log2BlockSize);
|
||
|
}
|
||
|
if (hashingInfo.salt != null && hashingInfo.salt.length > 0) {
|
||
|
throw new IOException("Unsupported salt: " + Arrays.toString(hashingInfo.salt));
|
||
|
}
|
||
|
if (hashingInfo.rawRootHash.length != INCFS_MAX_HASH_SIZE) {
|
||
|
throw new IOException("rawRootHash has to be " + INCFS_MAX_HASH_SIZE + " bytes");
|
||
|
}
|
||
|
if (signingInfos.signingInfo.additionalData.length > INCFS_MAX_ADD_DATA_SIZE) {
|
||
|
throw new IOException(
|
||
|
"additionalData has to be at most " + INCFS_MAX_ADD_DATA_SIZE + " bytes");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Configure all the lib files inside Incremental Service, e.g., create lib dirs, create new lib
|
||
|
* files, extract original lib file data from zip and then write data to the lib files on the
|
||
|
* Incremental File System.
|
||
|
*
|
||
|
* @param apkFullPath Source APK to extract native libs from.
|
||
|
* @param libDirRelativePath Target dir to put lib files, e.g., "lib" or "lib/arm".
|
||
|
* @param abi Target ABI of the native lib files. Only extract native libs of this ABI.
|
||
|
* @param extractNativeLibs If true, extract native libraries; otherwise just setup directories
|
||
|
* without extracting.
|
||
|
* @return Success of not.
|
||
|
*/
|
||
|
public boolean configureNativeBinaries(String apkFullPath, String libDirRelativePath,
|
||
|
String abi, boolean extractNativeLibs) {
|
||
|
try {
|
||
|
return mService.configureNativeBinaries(mId, apkFullPath, libDirRelativePath, abi,
|
||
|
extractNativeLibs);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Waits for all native binary extraction operations to complete on the storage.
|
||
|
*
|
||
|
* @return Success of not.
|
||
|
*/
|
||
|
public boolean waitForNativeBinariesExtraction() {
|
||
|
try {
|
||
|
return mService.waitForNativeBinariesExtraction(mId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register to listen to loading progress of all the files on this storage.
|
||
|
* @param listener To report progress from Incremental Service to the caller.
|
||
|
*/
|
||
|
public boolean registerLoadingProgressListener(IStorageLoadingProgressListener listener) {
|
||
|
try {
|
||
|
return mService.registerLoadingProgressListener(mId, listener);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Unregister to stop listening to storage loading progress.
|
||
|
*/
|
||
|
public boolean unregisterLoadingProgressListener() {
|
||
|
try {
|
||
|
return mService.unregisterLoadingProgressListener(mId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the metrics of the current storage.
|
||
|
* {@see IIncrementalService} for metrics keys.
|
||
|
*/
|
||
|
public PersistableBundle getMetrics() {
|
||
|
try {
|
||
|
return mService.getMetrics(mId);
|
||
|
} catch (RemoteException e) {
|
||
|
e.rethrowFromSystemServer();
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
}
|