Wait for APK to be fully downloaded for full APK digests.
Bug: 160605420
Test: atest ChecksumsTest
Change-Id: Ib9fd591c67290786268b6dcdc57c7db153612e01
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index be8b929..f351c7d 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -90,6 +90,14 @@
int unlink(int storageId, in @utf8InCpp String path);
/**
+ * Checks if a file is fully loaded. File is specified by its path.
+ * 0 - fully loaded
+ * >0 - certain pages missing
+ * <0 - -errcode
+ */
+ int isFileFullyLoaded(int storageId, in @utf8InCpp String path);
+
+ /**
* Returns overall loading progress of all the files on a storage, progress value between [0,1].
* Returns a negative value on error.
*/
diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java
index b8dbfbb..ed386f7 100644
--- a/core/java/android/os/incremental/IncrementalStorage.java
+++ b/core/java/android/os/incremental/IncrementalStorage.java
@@ -304,6 +304,25 @@
}
/**
+ * 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;
+ }
+ }
+
+ /**
* Returns the loading progress of a storage
*
* @return progress value between [0, 1].
diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java
index d8745ab..0338ed8 100644
--- a/services/core/java/com/android/server/pm/ApkChecksums.java
+++ b/services/core/java/com/android/server/pm/ApkChecksums.java
@@ -16,6 +16,7 @@
package com.android.server.pm;
+import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA256;
import static android.content.pm.PackageManager.PARTIAL_MERKLE_ROOT_1M_SHA512;
import static android.content.pm.PackageManager.WHOLE_MD5;
@@ -27,11 +28,20 @@
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
import static android.util.apk.ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.FileChecksum;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.incremental.IncrementalManager;
+import android.os.incremental.IncrementalStorage;
import android.util.ArrayMap;
+import android.util.Pair;
import android.util.Slog;
import android.util.apk.ApkSignatureSchemeV2Verifier;
import android.util.apk.ApkSignatureSchemeV3Verifier;
@@ -43,6 +53,7 @@
import android.util.apk.SignatureNotFoundException;
import android.util.apk.VerityBuilder;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.VerityUtils;
import java.io.BufferedInputStream;
@@ -72,32 +83,163 @@
static final String ALGO_SHA512 = "SHA512";
/**
- * Fetch or calculate checksums for the specific file.
+ * Check back in 1 second after we detected we needed to wait for the APK to be fully available.
+ */
+ private static final long PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS = 1000;
+
+ /**
+ * 24 hours timeout to wait till all files are loaded.
+ */
+ private static final long PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS = 1000 * 3600 * 24;
+
+ /**
+ * Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
*
- * @param split split name, null for base
- * @param file to fetch checksums for
+ * NOTE: All getters should return the same instance for every call.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static class Injector {
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ interface Producer<T> {
+ /** Produce an instance of type {@link T} */
+ T produce();
+ }
+
+ private final Producer<Context> mContext;
+ private final Producer<Handler> mHandlerProducer;
+ private final Producer<IncrementalManager> mIncrementalManagerProducer;
+
+ Injector(Producer<Context> context, Producer<Handler> handlerProducer,
+ Producer<IncrementalManager> incrementalManagerProducer) {
+ mContext = context;
+ mHandlerProducer = handlerProducer;
+ mIncrementalManagerProducer = incrementalManagerProducer;
+ }
+
+ public Context getContext() {
+ return mContext.produce();
+ }
+
+ public Handler getHandler() {
+ return mHandlerProducer.produce();
+ }
+
+ public IncrementalManager getIncrementalManager() {
+ return mIncrementalManagerProducer.produce();
+ }
+ }
+
+ /**
+ * Fetch or calculate checksums for the collection of files.
+ *
+ * @param filesToChecksum split name, null for base and File to fetch checksums for
* @param optional mask to fetch readily available checksums
* @param required mask to forcefully calculate if not available
* @param trustedInstallers array of certificate to trust, two specific cases:
* null - trust anybody,
* [] - trust nobody.
+ * @param statusReceiver to receive the resulting checksums
*/
- public static List<FileChecksum> getFileChecksums(String split, File file,
+ public static void getChecksums(List<Pair<String, File>> filesToChecksum,
@PackageManager.FileChecksumKind int optional,
@PackageManager.FileChecksumKind int required,
- @Nullable Certificate[] trustedInstallers) {
+ @Nullable Certificate[] trustedInstallers,
+ @NonNull IntentSender statusReceiver,
+ @NonNull Injector injector) {
+ List<Map<Integer, FileChecksum>> result = new ArrayList<>(filesToChecksum.size());
+ for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+ final String split = filesToChecksum.get(i).first;
+ final File file = filesToChecksum.get(i).second;
+ Map<Integer, FileChecksum> checksums = new ArrayMap<>();
+ result.add(checksums);
+
+ try {
+ getAvailableFileChecksums(split, file, optional | required, trustedInstallers,
+ checksums);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Preferred checksum calculation error", e);
+ }
+ }
+
+ long startTime = SystemClock.uptimeMillis();
+ processRequiredChecksums(filesToChecksum, result, required, statusReceiver, injector,
+ startTime);
+ }
+
+ private static void processRequiredChecksums(List<Pair<String, File>> filesToChecksum,
+ List<Map<Integer, FileChecksum>> result,
+ @PackageManager.FileChecksumKind int required,
+ @NonNull IntentSender statusReceiver,
+ @NonNull Injector injector,
+ long startTime) {
+ final boolean timeout =
+ SystemClock.uptimeMillis() - startTime >= PROCESS_REQUIRED_CHECKSUMS_TIMEOUT_MILLIS;
+ List<FileChecksum> allChecksums = new ArrayList<>();
+ for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
+ final String split = filesToChecksum.get(i).first;
+ final File file = filesToChecksum.get(i).second;
+ Map<Integer, FileChecksum> checksums = result.get(i);
+
+ try {
+ if (!timeout || required != 0) {
+ if (needToWait(file, required, checksums, injector)) {
+ // Not ready, come back later.
+ injector.getHandler().postDelayed(() -> {
+ processRequiredChecksums(filesToChecksum, result, required,
+ statusReceiver, injector, startTime);
+ }, PROCESS_REQUIRED_CHECKSUMS_DELAY_MILLIS);
+ return;
+ }
+
+ getRequiredFileChecksums(split, file, required, checksums);
+ }
+ allChecksums.addAll(checksums.values());
+ } catch (Throwable e) {
+ Slog.e(TAG, "Required checksum calculation error", e);
+ }
+ }
+
+ final Intent intent = new Intent();
+ intent.putExtra(EXTRA_CHECKSUMS,
+ allChecksums.toArray(new FileChecksum[allChecksums.size()]));
+
+ try {
+ statusReceiver.sendIntent(injector.getContext(), 1, intent, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.w(TAG, e);
+ }
+ }
+
+ /**
+ * Fetch readily available checksums - enforced by kernel or provided by Installer.
+ *
+ * @param split split name, null for base
+ * @param file to fetch checksums for
+ * @param kinds mask to fetch checksums
+ * @param trustedInstallers array of certificate to trust, two specific cases:
+ * null - trust anybody,
+ * [] - trust nobody.
+ * @param checksums resulting checksums
+ */
+ private static void getAvailableFileChecksums(String split, File file,
+ @PackageManager.FileChecksumKind int kinds,
+ @Nullable Certificate[] trustedInstallers,
+ Map<Integer, FileChecksum> checksums) {
final String filePath = file.getAbsolutePath();
- Map<Integer, FileChecksum> checksums = new ArrayMap<>();
- final int kinds = (optional | required);
- // System enforced: FSI or v2/v3/v4 signatures.
- if ((kinds & WHOLE_MERKLE_ROOT_4K_SHA256) != 0) {
+
+ // Always available: FSI or IncFs.
+ if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
// Hashes in fs-verity and IncFS are always verified.
FileChecksum checksum = extractHashFromFS(split, filePath);
if (checksum != null) {
checksums.put(checksum.getKind(), checksum);
}
}
- if ((kinds & (PARTIAL_MERKLE_ROOT_1M_SHA256 | PARTIAL_MERKLE_ROOT_1M_SHA512)) != 0) {
+
+ // System enforced: v2/v3.
+ if (isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums) || isRequired(
+ PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
Map<Integer, FileChecksum> v2v3checksums = extractHashFromV2V3Signature(
split, filePath, kinds);
if (v2v3checksums != null) {
@@ -106,11 +248,58 @@
}
// TODO(b/160605420): Installer provided.
- // TODO(b/160605420): Wait for Incremental to be fully loaded.
+ }
+
+ /**
+ * Whether the file is available for checksumming or we need to wait.
+ */
+ private static boolean needToWait(File file,
+ @PackageManager.FileChecksumKind int kinds,
+ Map<Integer, FileChecksum> checksums,
+ @NonNull Injector injector) throws IOException {
+ if (!isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)
+ && !isRequired(WHOLE_MD5, kinds, checksums)
+ && !isRequired(WHOLE_SHA1, kinds, checksums)
+ && !isRequired(WHOLE_SHA256, kinds, checksums)
+ && !isRequired(WHOLE_SHA512, kinds, checksums)
+ && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA256, kinds, checksums)
+ && !isRequired(PARTIAL_MERKLE_ROOT_1M_SHA512, kinds, checksums)) {
+ return false;
+ }
+
+ final String filePath = file.getAbsolutePath();
+ if (!IncrementalManager.isIncrementalPath(filePath)) {
+ return false;
+ }
+
+ IncrementalManager manager = injector.getIncrementalManager();
+ if (manager == null) {
+ throw new IllegalStateException("IncrementalManager is missing.");
+ }
+ IncrementalStorage storage = manager.openStorage(filePath);
+ if (storage == null) {
+ throw new IllegalStateException(
+ "IncrementalStorage is missing for a path on IncFs: " + filePath);
+ }
+
+ return !storage.isFileFullyLoaded(filePath);
+ }
+
+ /**
+ * Fetch or calculate checksums for the specific file.
+ *
+ * @param split split name, null for base
+ * @param file to fetch checksums for
+ * @param kinds mask to forcefully calculate if not available
+ * @param checksums resulting checksums
+ */
+ private static void getRequiredFileChecksums(String split, File file,
+ @PackageManager.FileChecksumKind int kinds,
+ Map<Integer, FileChecksum> checksums) {
+ final String filePath = file.getAbsolutePath();
// Manually calculating required checksums if not readily available.
- if ((required & WHOLE_MERKLE_ROOT_4K_SHA256) != 0 && !checksums.containsKey(
- WHOLE_MERKLE_ROOT_4K_SHA256)) {
+ if (isRequired(WHOLE_MERKLE_ROOT_4K_SHA256, kinds, checksums)) {
try {
byte[] generatedRootHash = VerityBuilder.generateFsVerityRootHash(
filePath, /*salt=*/null,
@@ -127,14 +316,23 @@
}
}
- calculateChecksumIfRequested(checksums, split, file, required, WHOLE_MD5);
- calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA1);
- calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA256);
- calculateChecksumIfRequested(checksums, split, file, required, WHOLE_SHA512);
+ calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_MD5);
+ calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA1);
+ calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA256);
+ calculateChecksumIfRequested(checksums, split, file, kinds, WHOLE_SHA512);
- calculatePartialChecksumsIfRequested(checksums, split, file, required);
+ calculatePartialChecksumsIfRequested(checksums, split, file, kinds);
+ }
- return new ArrayList<>(checksums.values());
+ private static boolean isRequired(@PackageManager.FileChecksumKind int kind,
+ @PackageManager.FileChecksumKind int kinds, Map<Integer, FileChecksum> checksums) {
+ if ((kinds & kind) == 0) {
+ return false;
+ }
+ if (checksums.containsKey(kind)) {
+ return false;
+ }
+ return true;
}
private static FileChecksum extractHashFromFS(String split, String filePath) {
@@ -170,7 +368,9 @@
PackageParser.SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V2,
false).contentDigests;
} catch (PackageParser.PackageParserException e) {
- Slog.e(TAG, "Signature verification error", e);
+ if (!(e.getCause() instanceof SignatureNotFoundException)) {
+ Slog.e(TAG, "Signature verification error", e);
+ }
}
if (contentDigests == null) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 344f9cf..ca97f31 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -40,7 +40,6 @@
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-import static android.content.pm.PackageManager.EXTRA_CHECKSUMS;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
@@ -169,7 +168,6 @@
import android.content.pm.DataLoaderType;
import android.content.pm.FallbackCategoryProvider;
import android.content.pm.FeatureInfo;
-import android.content.pm.FileChecksum;
import android.content.pm.IDexModuleRegisterCallback;
import android.content.pm.IPackageChangeObserver;
import android.content.pm.IPackageDataObserver;
@@ -933,6 +931,7 @@
private final Object mLock;
private final Installer mInstaller;
private final Object mInstallLock;
+ private final Handler mBackgroundHandler;
private final Executor mBackgroundExecutor;
// ----- producers -----
@@ -955,7 +954,7 @@
Injector(Context context, Object lock, Installer installer,
Object installLock, PackageAbiHelper abiHelper,
- Executor backgroundExecutor,
+ Handler backgroundHandler,
Producer<ComponentResolver> componentResolverProducer,
Producer<PermissionManagerServiceInternal> permissionManagerProducer,
Producer<UserManagerService> userManagerProducer,
@@ -977,7 +976,8 @@
mInstaller = installer;
mAbiHelper = abiHelper;
mInstallLock = installLock;
- mBackgroundExecutor = backgroundExecutor;
+ mBackgroundHandler = backgroundHandler;
+ mBackgroundExecutor = new HandlerExecutor(backgroundHandler);
mComponentResolverProducer = new Singleton<>(componentResolverProducer);
mPermissionManagerProducer = new Singleton<>(permissionManagerProducer);
mUserManagerProducer = new Singleton<>(userManagerProducer);
@@ -1092,6 +1092,10 @@
return mPlatformCompatProducer.get(this, mPackageManager);
}
+ public Handler getBackgroundHandler() {
+ return mBackgroundHandler;
+ }
+
public Executor getBackgroundExecutor() {
return mBackgroundExecutor;
}
@@ -2489,29 +2493,14 @@
final Certificate[] trustedCerts = (trustedInstallers != null) ? decodeCertificates(
trustedInstallers) : null;
- final Context context = mContext;
mInjector.getBackgroundExecutor().execute(() -> {
- final Intent intent = new Intent();
- List<FileChecksum> result = new ArrayList<>();
- for (int i = 0, size = filesToChecksum.size(); i < size; ++i) {
- final String split = filesToChecksum.get(i).first;
- final File file = filesToChecksum.get(i).second;
- try {
- result.addAll(ApkChecksums.getFileChecksums(split, file, optional, required,
- trustedCerts));
- } catch (Throwable e) {
- Slog.e(TAG, "Checksum calculation error", e);
- }
- }
- intent.putExtra(EXTRA_CHECKSUMS,
- result.toArray(new FileChecksum[result.size()]));
-
- try {
- statusReceiver.sendIntent(context, 1, intent, null, null);
- } catch (SendIntentException e) {
- Slog.w(TAG, e);
- }
+ ApkChecksums.Injector injector = new ApkChecksums.Injector(
+ () -> mContext,
+ () -> mInjector.getBackgroundHandler(),
+ () -> mContext.getSystemService(IncrementalManager.class));
+ ApkChecksums.getChecksums(filesToChecksum, optional, required, trustedCerts,
+ statusReceiver, injector);
});
}
@@ -2684,7 +2673,7 @@
Injector injector = new Injector(
context, lock, installer, installLock, new PackageAbiHelperImpl(),
- new HandlerExecutor(backgroundHandler),
+ backgroundHandler,
(i, pm) ->
new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
(i, pm) ->
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index dbe96e6..4858682 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -33,6 +33,9 @@
"name": "CtsContentTestCases",
"options": [
{
+ "include-filter": "android.content.pm.cts.ChecksumsTest"
+ },
+ {
"include-filter": "android.content.pm.cts.PackageManagerShellCommandTest"
},
{
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 41945a2..87ae4d7 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -237,6 +237,13 @@
return ok();
}
+binder::Status BinderIncrementalService::isFileFullyLoaded(int32_t storageId,
+ const std::string& path,
+ int32_t* _aidl_return) {
+ *_aidl_return = mImpl.isFileFullyLoaded(storageId, path);
+ return ok();
+}
+
binder::Status BinderIncrementalService::getLoadingProgress(int32_t storageId,
float* _aidl_return) {
*_aidl_return = mImpl.getLoadingProgress(storageId);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 8b40350..8478142 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -66,6 +66,8 @@
int32_t destStorageId, const std::string& destPath,
int32_t* _aidl_return) final;
binder::Status unlink(int32_t storageId, const std::string& path, int32_t* _aidl_return) final;
+ binder::Status isFileFullyLoaded(int32_t storageId, const std::string& path,
+ int32_t* _aidl_return) final;
binder::Status getLoadingProgress(int32_t storageId, float* _aidl_return) final;
binder::Status getMetadataByPath(int32_t storageId, const std::string& path,
std::vector<uint8_t>* _aidl_return) final;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 9836262e..447ee55 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1603,7 +1603,8 @@
const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
if (!writeFd.ok()) {
- LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
+ LOG(ERROR) << "Failed to open write fd for: " << targetLibPath
+ << " errno: " << writeFd.get();
return;
}
@@ -1673,6 +1674,37 @@
return mRunning;
}
+int IncrementalService::isFileFullyLoaded(StorageId storage, const std::string& path) const {
+ std::unique_lock l(mLock);
+ const auto ifs = getIfsLocked(storage);
+ if (!ifs) {
+ LOG(ERROR) << "isFileFullyLoaded failed, invalid storageId: " << storage;
+ return -EINVAL;
+ }
+ const auto storageInfo = ifs->storages.find(storage);
+ if (storageInfo == ifs->storages.end()) {
+ LOG(ERROR) << "isFileFullyLoaded failed, no storage: " << storage;
+ return -EINVAL;
+ }
+ l.unlock();
+ return isFileFullyLoadedFromPath(*ifs, path);
+}
+
+int IncrementalService::isFileFullyLoadedFromPath(const IncFsMount& ifs,
+ std::string_view filePath) const {
+ const auto [filledBlocks, totalBlocks] = mIncFs->countFilledBlocks(ifs.control, filePath);
+ if (filledBlocks < 0) {
+ LOG(ERROR) << "isFileFullyLoadedFromPath failed to get filled blocks count for: "
+ << filePath << " errno: " << filledBlocks;
+ return filledBlocks;
+ }
+ if (totalBlocks < filledBlocks) {
+ LOG(ERROR) << "isFileFullyLoadedFromPath failed to get total num of blocks";
+ return -EINVAL;
+ }
+ return totalBlocks - filledBlocks;
+}
+
float IncrementalService::getLoadingProgress(StorageId storage) const {
std::unique_lock l(mLock);
const auto ifs = getIfsLocked(storage);
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index cd6bfed..267458d 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -132,6 +132,7 @@
std::string_view newPath);
int unlink(StorageId storage, std::string_view path);
+ int isFileFullyLoaded(StorageId storage, const std::string& path) const;
float getLoadingProgress(StorageId storage) const;
RawMetadata getMetadata(StorageId storage, std::string_view path) const;
@@ -339,6 +340,7 @@
int makeDirs(const IncFsMount& ifs, StorageId storageId, std::string_view path, int mode);
binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
+ int isFileFullyLoadedFromPath(const IncFsMount& ifs, std::string_view filePath) const;
float getLoadingProgressFromPath(const IncFsMount& ifs, std::string_view path) const;
void registerAppOpsCallback(const std::string& packageName);
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index 1ed46c4..f6d89c5 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -195,8 +195,8 @@
ErrorCode unlink(const Control& control, std::string_view path) const final {
return incfs::unlink(control, path);
}
- base::unique_fd openForSpecialOps(const Control& control, FileId id) const final {
- return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
+ incfs::UniqueFd openForSpecialOps(const Control& control, FileId id) const final {
+ return incfs::openForSpecialOps(control, id);
}
ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 82a1704..6376d86 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -74,6 +74,7 @@
using Control = incfs::Control;
using FileId = incfs::FileId;
using ErrorCode = incfs::ErrorCode;
+ using UniqueFd = incfs::UniqueFd;
using WaitResult = incfs::WaitResult;
using ExistingMountCallback =
@@ -96,7 +97,7 @@
virtual ErrorCode link(const Control& control, std::string_view from,
std::string_view to) const = 0;
virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
- virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
+ virtual UniqueFd openForSpecialOps(const Control& control, FileId id) const = 0;
virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
virtual WaitResult waitForPendingReads(
const Control& control, std::chrono::milliseconds timeout,
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index d1000e56..a290a17 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -289,7 +289,7 @@
ErrorCode(const Control& control, std::string_view from,
std::string_view to));
MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
- MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
+ MOCK_CONST_METHOD2(openForSpecialOps, UniqueFd(const Control& control, FileId id));
MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
MOCK_CONST_METHOD3(waitForPendingReads,
WaitResult(const Control& control, std::chrono::milliseconds timeout,
@@ -304,6 +304,10 @@
ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(1, 2)));
}
+ void countFilledBlocksFullyLoaded() {
+ ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(10000, 10000)));
+ }
+
void countFilledBlocksFails() {
ON_CALL(*this, countFilledBlocks(_, _)).WillByDefault(Return(std::make_pair(-1, -1)));
}
@@ -1069,6 +1073,53 @@
ASSERT_EQ(res, 0);
}
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithNoFile) {
+ mIncFs->countFilledBlocksFails();
+ mFs->hasNoFile();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedFailsWithFailedRanges) {
+ mIncFs->countFilledBlocksFails();
+ mFs->hasFiles();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+ ASSERT_EQ(-1, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccessWithEmptyRanges) {
+ mIncFs->countFilledBlocksEmpty();
+ mFs->hasFiles();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+ ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
+TEST_F(IncrementalServiceTest, testIsFileFullyLoadedSuccess) {
+ mIncFs->countFilledBlocksFullyLoaded();
+ mFs->hasFiles();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(1);
+ ASSERT_EQ(0, mIncrementalService->isFileFullyLoaded(storageId, "base.apk"));
+}
+
TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccessWithNoFile) {
mIncFs->countFilledBlocksSuccess();
mFs->hasNoFile();