[incremental/pm] register progress listener
Incremental Serivce periodically polls loading progress and sends to
Package Manager Service. Package Manager provides APIs for other
interested parties to listen to the loading progress.
BUG: 165841827
Test: unit test
Change-Id: I44b9e17c2240b9efe53bc09fc728b6671f1f7dfe
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index cf9324c..0c56d46 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -32,11 +32,16 @@
import android.content.pm.PackageManager.ResolveInfoFlags;
import android.content.pm.parsing.component.ParsedMainComponent;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.Looper;
import android.os.PersistableBundle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
+import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.pm.PackageList;
import com.android.server.pm.PackageSetting;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -46,6 +51,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -986,4 +992,72 @@
* Returns {@code true} if the package is suspending any packages for the user.
*/
public abstract boolean isSuspendingAnyPackages(String suspendingPackage, int userId);
+
+ /**
+ * Register to listen for loading progress of an installed package.
+ * @param packageName The name of the installed package
+ * @param callback To loading reporting progress
+ * @param userId The user under which to check.
+ * @return Whether the registration was successful. It can fail if the package has not been
+ * installed yet.
+ */
+ public abstract boolean registerInstalledLoadingProgressCallback(@NonNull String packageName,
+ @NonNull InstalledLoadingProgressCallback callback, int userId);
+
+ /**
+ * Unregister to stop listening to loading progress of an installed package
+ * @param packageName The name of the installed package
+ * @param callback To unregister
+ * @return True if the callback is removed from registered callback list. False is the callback
+ * does not exist on the registered callback list, which can happen if the callback has
+ * already been unregistered.
+ */
+ public abstract boolean unregisterInstalledLoadingProgressCallback(@NonNull String packageName,
+ @NonNull InstalledLoadingProgressCallback callback);
+
+ /**
+ * Callback to listen for loading progress of a package installed on Incremental File System.
+ */
+ public abstract static class InstalledLoadingProgressCallback {
+ final LoadingProgressCallbackBinder mBinder = new LoadingProgressCallbackBinder();
+ final Executor mExecutor;
+ /**
+ * Default constructor that should always be called on subclass instantiation
+ * @param handler To dispatch callback events through. If null, the main thread
+ * handler will be used.
+ */
+ public InstalledLoadingProgressCallback(@Nullable Handler handler) {
+ if (handler == null) {
+ handler = new Handler(Looper.getMainLooper());
+ }
+ mExecutor = new HandlerExecutor(handler);
+ }
+
+ /**
+ * Binder used by Package Manager Service to register as a callback
+ * @return the binder object of IPackageLoadingProgressCallback
+ */
+ public final @NonNull IBinder getBinder() {
+ return mBinder;
+ }
+
+ /**
+ * Report loading progress of an installed package.
+ *
+ * @param progress Loading progress between [0, 1] for the registered package.
+ */
+ public abstract void onLoadingProgressChanged(float progress);
+
+ private class LoadingProgressCallbackBinder extends
+ android.content.pm.IPackageLoadingProgressCallback.Stub {
+ @Override
+ public void onPackageLoadingProgressChanged(float progress) {
+ mExecutor.execute(PooledLambda.obtainRunnable(
+ InstalledLoadingProgressCallback::onLoadingProgressChanged,
+ InstalledLoadingProgressCallback.this,
+ progress).recycleOnUse());
+ }
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5850dc0..3e4e88c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -176,6 +176,7 @@
import android.content.pm.IPackageDeleteObserver2;
import android.content.pm.IPackageInstallObserver2;
import android.content.pm.IPackageInstaller;
+import android.content.pm.IPackageLoadingProgressCallback;
import android.content.pm.IPackageManager;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IPackageMoveObserver;
@@ -15993,7 +15994,7 @@
String codePath = codeFile.getAbsolutePath();
if (mIncrementalManager != null && isIncrementalPath(codePath)) {
- mIncrementalManager.closeStorage(codePath);
+ mIncrementalManager.onPackageRemoved(codePath);
}
removeCodePathLI(codeFile);
@@ -17125,10 +17126,11 @@
& PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
final AndroidPackage pkg = reconciledPkg.pkgSetting.pkg;
final String packageName = pkg.getPackageName();
+ final String codePath = pkg.getPath();
final boolean onIncremental = mIncrementalManager != null
- && isIncrementalPath(pkg.getPath());
+ && isIncrementalPath(codePath);
if (onIncremental) {
- IncrementalStorage storage = mIncrementalManager.openStorage(pkg.getPath());
+ IncrementalStorage storage = mIncrementalManager.openStorage(codePath);
if (storage == null) {
throw new IllegalArgumentException(
"Install: null storage for incremental package " + packageName);
@@ -25359,6 +25361,56 @@
public boolean isSuspendingAnyPackages(String suspendingPackage, int userId) {
return PackageManagerService.this.isSuspendingAnyPackages(suspendingPackage, userId);
}
+
+ @Override
+ public boolean registerInstalledLoadingProgressCallback(String packageName,
+ PackageManagerInternal.InstalledLoadingProgressCallback callback, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ mPermissionManager.enforceCrossUserPermission(
+ callingUid, userId, true, false,
+ "registerLoadingProgressCallback");
+ final PackageSetting ps;
+ synchronized (mLock) {
+ ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(TAG, "Failed registering loading progress callback. Package "
+ + packageName + " is not installed");
+ return false;
+ }
+ if (shouldFilterApplicationLocked(ps, callingUid, userId)) {
+ Slog.w(TAG, "Failed registering loading progress callback. Package "
+ + packageName + " is not visible to the calling app");
+ return false;
+ }
+ // TODO(b/165841827): return false if package is fully loaded
+ }
+ if (mIncrementalManager == null) {
+ Slog.w(TAG,
+ "Failed registering loading progress callback. Incremental is not enabled");
+ return false;
+ }
+ return mIncrementalManager.registerCallback(ps.getPathString(),
+ (IPackageLoadingProgressCallback) callback.getBinder());
+ }
+
+ @Override
+ public boolean unregisterInstalledLoadingProgressCallback(String packageName,
+ PackageManagerInternal.InstalledLoadingProgressCallback callback) {
+ final PackageSetting ps;
+ synchronized (mLock) {
+ ps = mSettings.mPackages.get(packageName);
+ if (ps == null) {
+ Slog.w(TAG, "Failed unregistering loading progress callback. Package "
+ + packageName + " is not installed");
+ return false;
+ }
+ }
+ if (mIncrementalManager == null) {
+ return false;
+ }
+ return mIncrementalManager.unregisterCallback(ps.getPathString(),
+ (IPackageLoadingProgressCallback) callback.getBinder());
+ }
}
@GuardedBy("mLock")
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 87ae4d7..bf3a896 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -301,6 +301,20 @@
return ok();
}
+binder::Status BinderIncrementalService::registerLoadingProgressListener(
+ int32_t storageId,
+ const ::android::sp<::android::os::incremental::IStorageLoadingProgressListener>&
+ progressListener,
+ bool* _aidl_return) {
+ *_aidl_return = mImpl.registerLoadingProgressListener(storageId, progressListener);
+ return ok();
+}
+binder::Status BinderIncrementalService::unregisterLoadingProgressListener(int32_t storageId,
+ bool* _aidl_return) {
+ *_aidl_return = mImpl.unregisterLoadingProgressListener(storageId);
+ return ok();
+}
+
} // namespace android::os::incremental
jlong Incremental_IncrementalService_Start(JNIEnv* env) {
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 8478142..1238498 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -81,6 +81,12 @@
const std::string& abi, bool extractNativeLibs,
bool* _aidl_return) final;
binder::Status waitForNativeBinariesExtraction(int storageId, bool* _aidl_return) final;
+ binder::Status registerLoadingProgressListener(
+ int32_t storageId,
+ const ::android::sp<::android::os::incremental::IStorageLoadingProgressListener>&
+ progressListener,
+ bool* _aidl_return) final;
+ binder::Status unregisterLoadingProgressListener(int32_t storageId, bool* _aidl_return) final;
private:
android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 447ee55..10a508b 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -275,6 +275,7 @@
mJni(sm.getJni()),
mLooper(sm.getLooper()),
mTimedQueue(sm.getTimedQueue()),
+ mProgressUpdateJobQueue(sm.getProgressUpdateJobQueue()),
mFs(sm.getFs()),
mIncrementalDir(rootDir) {
CHECK(mVold) << "Vold service is unavailable";
@@ -283,6 +284,7 @@
CHECK(mJni) << "JNI is unavailable";
CHECK(mLooper) << "Looper is unavailable";
CHECK(mTimedQueue) << "TimedQueue is unavailable";
+ CHECK(mProgressUpdateJobQueue) << "mProgressUpdateJobQueue is unavailable";
CHECK(mFs) << "Fs is unavailable";
mJobQueue.reserve(16);
@@ -308,6 +310,7 @@
mJobProcessor.join();
mCmdLooperThread.join();
mTimedQueue->stop();
+ mProgressUpdateJobQueue->stop();
// Ensure that mounts are destroyed while the service is still valid.
mBindsByPath.clear();
mMounts.clear();
@@ -1744,6 +1747,35 @@
return (float)filledBlocks / (float)totalBlocks;
}
+bool IncrementalService::updateLoadingProgress(
+ StorageId storage, const StorageLoadingProgressListener& progressListener) {
+ const auto progress = getLoadingProgress(storage);
+ if (progress < 0) {
+ // Failed to get progress from incfs, abort.
+ return false;
+ }
+ progressListener->onStorageLoadingProgressChanged(storage, progress);
+ if (progress > 1 - 0.001f) {
+ // Stop updating progress once it is fully loaded
+ return true;
+ }
+ static constexpr auto kProgressUpdateInterval = 1000ms;
+ addTimedJob(*mProgressUpdateJobQueue, storage, kProgressUpdateInterval /* repeat after 1s */,
+ [storage, progressListener, this]() {
+ updateLoadingProgress(storage, progressListener);
+ });
+ return true;
+}
+
+bool IncrementalService::registerLoadingProgressListener(
+ StorageId storage, const StorageLoadingProgressListener& progressListener) {
+ return updateLoadingProgress(storage, progressListener);
+}
+
+bool IncrementalService::unregisterLoadingProgressListener(StorageId storage) {
+ return removeTimedJobs(*mProgressUpdateJobQueue, storage);
+}
+
bool IncrementalService::perfLoggingEnabled() {
static const bool enabled = base::GetBoolProperty("incremental.perflogging", false);
return enabled;
@@ -1826,18 +1858,21 @@
}
}
-void IncrementalService::addTimedJob(MountId id, Milliseconds after, Job what) {
+bool IncrementalService::addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after,
+ Job what) {
if (id == kInvalidStorageId) {
- return;
+ return false;
}
- mTimedQueue->addJob(id, after, std::move(what));
+ timedQueue.addJob(id, after, std::move(what));
+ return true;
}
-void IncrementalService::removeTimedJobs(MountId id) {
+bool IncrementalService::removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id) {
if (id == kInvalidStorageId) {
- return;
+ return false;
}
- mTimedQueue->removeJobs(id);
+ timedQueue.removeJobs(id);
+ return true;
}
IncrementalService::DataLoaderStub::DataLoaderStub(IncrementalService& service, MountId id,
@@ -1879,7 +1914,7 @@
mHealthPath.clear();
unregisterFromPendingReads();
resetHealthControl();
- mService.removeTimedJobs(mId);
+ mService.removeTimedJobs(*mService.mTimedQueue, mId);
}
requestDestroy();
@@ -2169,7 +2204,8 @@
}
LOG(DEBUG) << id() << ": updateHealthStatus in " << double(checkBackAfter.count()) / 1000.0
<< "secs";
- mService.addTimedJob(id(), checkBackAfter, [this]() { updateHealthStatus(); });
+ mService.addTimedJob(*mService.mTimedQueue, id(), checkBackAfter,
+ [this]() { updateHealthStatus(); });
}
// With kTolerance we are expecting these to execute before the next update.
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 267458d..a49e0f3 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -22,6 +22,7 @@
#include <android/content/pm/IDataLoaderStatusListener.h>
#include <android/os/incremental/BnIncrementalServiceConnector.h>
#include <android/os/incremental/BnStorageHealthListener.h>
+#include <android/os/incremental/BnStorageLoadingProgressListener.h>
#include <android/os/incremental/StorageHealthCheckParams.h>
#include <binder/IAppOpsCallback.h>
#include <utils/String16.h>
@@ -65,6 +66,8 @@
using StorageHealthCheckParams = ::android::os::incremental::StorageHealthCheckParams;
using IStorageHealthListener = ::android::os::incremental::IStorageHealthListener;
using StorageHealthListener = ::android::sp<IStorageHealthListener>;
+using IStorageLoadingProgressListener = ::android::os::incremental::IStorageLoadingProgressListener;
+using StorageLoadingProgressListener = ::android::sp<IStorageLoadingProgressListener>;
class IncrementalService final {
public:
@@ -134,6 +137,9 @@
int isFileFullyLoaded(StorageId storage, const std::string& path) const;
float getLoadingProgress(StorageId storage) const;
+ bool registerLoadingProgressListener(StorageId storage,
+ const StorageLoadingProgressListener& progressListener);
+ bool unregisterLoadingProgressListener(StorageId storage);
RawMetadata getMetadata(StorageId storage, std::string_view path) const;
RawMetadata getMetadata(StorageId storage, FileId node) const;
@@ -354,8 +360,10 @@
void runCmdLooper();
- void addTimedJob(MountId id, Milliseconds after, Job what);
- void removeTimedJobs(MountId id);
+ bool addTimedJob(TimedQueueWrapper& timedQueue, MountId id, Milliseconds after, Job what);
+ bool removeTimedJobs(TimedQueueWrapper& timedQueue, MountId id);
+ bool updateLoadingProgress(int32_t storageId,
+ const StorageLoadingProgressListener& progressListener);
private:
const std::unique_ptr<VoldServiceWrapper> mVold;
@@ -365,6 +373,7 @@
const std::unique_ptr<JniWrapper> mJni;
const std::unique_ptr<LooperWrapper> mLooper;
const std::unique_ptr<TimedQueueWrapper> mTimedQueue;
+ const std::unique_ptr<TimedQueueWrapper> mProgressUpdateJobQueue;
const std::unique_ptr<FsWrapper> mFs;
const std::string mIncrementalDir;
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index f6d89c5..144c466 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -357,6 +357,10 @@
return std::make_unique<RealTimedQueueWrapper>(mJvm);
}
+std::unique_ptr<TimedQueueWrapper> RealServiceManager::getProgressUpdateJobQueue() {
+ return std::make_unique<RealTimedQueueWrapper>(mJvm);
+}
+
std::unique_ptr<FsWrapper> RealServiceManager::getFs() {
return std::make_unique<RealFsWrapper>();
}
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 6376d86..4815caf 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -154,6 +154,7 @@
virtual std::unique_ptr<JniWrapper> getJni() = 0;
virtual std::unique_ptr<LooperWrapper> getLooper() = 0;
virtual std::unique_ptr<TimedQueueWrapper> getTimedQueue() = 0;
+ virtual std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() = 0;
virtual std::unique_ptr<FsWrapper> getFs() = 0;
};
@@ -170,6 +171,7 @@
std::unique_ptr<JniWrapper> getJni() final;
std::unique_ptr<LooperWrapper> getLooper() final;
std::unique_ptr<TimedQueueWrapper> getTimedQueue() final;
+ std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() final;
std::unique_ptr<FsWrapper> getFs() final;
private:
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index a290a17..aec9fa1 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -504,6 +504,14 @@
int32_t mStatus = -1;
};
+class MockStorageLoadingProgressListener : public IStorageLoadingProgressListener {
+public:
+ MockStorageLoadingProgressListener() = default;
+ MOCK_METHOD2(onStorageLoadingProgressChanged,
+ binder::Status(int32_t storageId, float progress));
+ MOCK_METHOD0(onAsBinder, IBinder*());
+};
+
class MockServiceManager : public ServiceManagerWrapper {
public:
MockServiceManager(std::unique_ptr<MockVoldService> vold,
@@ -513,6 +521,7 @@
std::unique_ptr<MockJniWrapper> jni,
std::unique_ptr<MockLooperWrapper> looper,
std::unique_ptr<MockTimedQueueWrapper> timedQueue,
+ std::unique_ptr<MockTimedQueueWrapper> progressUpdateJobQueue,
std::unique_ptr<MockFsWrapper> fs)
: mVold(std::move(vold)),
mDataLoaderManager(std::move(dataLoaderManager)),
@@ -521,6 +530,7 @@
mJni(std::move(jni)),
mLooper(std::move(looper)),
mTimedQueue(std::move(timedQueue)),
+ mProgressUpdateJobQueue(std::move(progressUpdateJobQueue)),
mFs(std::move(fs)) {}
std::unique_ptr<VoldServiceWrapper> getVoldService() final { return std::move(mVold); }
std::unique_ptr<DataLoaderManagerWrapper> getDataLoaderManager() final {
@@ -533,6 +543,9 @@
std::unique_ptr<JniWrapper> getJni() final { return std::move(mJni); }
std::unique_ptr<LooperWrapper> getLooper() final { return std::move(mLooper); }
std::unique_ptr<TimedQueueWrapper> getTimedQueue() final { return std::move(mTimedQueue); }
+ std::unique_ptr<TimedQueueWrapper> getProgressUpdateJobQueue() final {
+ return std::move(mProgressUpdateJobQueue);
+ }
std::unique_ptr<FsWrapper> getFs() final { return std::move(mFs); }
private:
@@ -543,6 +556,7 @@
std::unique_ptr<MockJniWrapper> mJni;
std::unique_ptr<MockLooperWrapper> mLooper;
std::unique_ptr<MockTimedQueueWrapper> mTimedQueue;
+ std::unique_ptr<MockTimedQueueWrapper> mProgressUpdateJobQueue;
std::unique_ptr<MockFsWrapper> mFs;
};
@@ -567,19 +581,19 @@
mLooper = looper.get();
auto timedQueue = std::make_unique<NiceMock<MockTimedQueueWrapper>>();
mTimedQueue = timedQueue.get();
+ auto progressUpdateJobQueue = std::make_unique<NiceMock<MockTimedQueueWrapper>>();
+ mProgressUpdateJobQueue = progressUpdateJobQueue.get();
auto fs = std::make_unique<NiceMock<MockFsWrapper>>();
mFs = fs.get();
- mIncrementalService =
- std::make_unique<IncrementalService>(MockServiceManager(std::move(vold),
- std::move(
- dataloaderManager),
- std::move(incFs),
- std::move(appOps),
- std::move(jni),
- std::move(looper),
- std::move(timedQueue),
- std::move(fs)),
- mRootDir.path);
+ mIncrementalService = std::make_unique<
+ IncrementalService>(MockServiceManager(std::move(vold),
+ std::move(dataloaderManager),
+ std::move(incFs), std::move(appOps),
+ std::move(jni), std::move(looper),
+ std::move(timedQueue),
+ std::move(progressUpdateJobQueue),
+ std::move(fs)),
+ mRootDir.path);
mDataLoaderParcel.packageName = "com.test";
mDataLoaderParcel.arguments = "uri";
mDataLoaderManager->unbindFromDataLoaderSuccess();
@@ -624,6 +638,7 @@
NiceMock<MockJniWrapper>* mJni = nullptr;
NiceMock<MockLooperWrapper>* mLooper = nullptr;
NiceMock<MockTimedQueueWrapper>* mTimedQueue = nullptr;
+ NiceMock<MockTimedQueueWrapper>* mProgressUpdateJobQueue = nullptr;
NiceMock<MockFsWrapper>* mFs = nullptr;
NiceMock<MockDataLoader>* mDataLoader = nullptr;
std::unique_ptr<IncrementalService> mIncrementalService;
@@ -1166,4 +1181,44 @@
EXPECT_CALL(*mIncFs, countFilledBlocks(_, _)).Times(3);
ASSERT_EQ(0.5, mIncrementalService->getLoadingProgress(storageId));
}
+
+TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerSuccess) {
+ mIncFs->countFilledBlocksSuccess();
+ mFs->hasFiles();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ sp<NiceMock<MockStorageLoadingProgressListener>> listener{
+ new NiceMock<MockStorageLoadingProgressListener>};
+ NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
+ EXPECT_CALL(*listenerMock, onStorageLoadingProgressChanged(_, _)).Times(2);
+ EXPECT_CALL(*mProgressUpdateJobQueue, addJob(_, _, _)).Times(2);
+ mIncrementalService->registerLoadingProgressListener(storageId, listener);
+ // Timed callback present.
+ ASSERT_EQ(storageId, mProgressUpdateJobQueue->mId);
+ ASSERT_EQ(mProgressUpdateJobQueue->mAfter, 1000ms);
+ auto timedCallback = mProgressUpdateJobQueue->mWhat;
+ timedCallback();
+ ASSERT_EQ(storageId, mProgressUpdateJobQueue->mId);
+ ASSERT_EQ(mProgressUpdateJobQueue->mAfter, 1000ms);
+ mIncrementalService->unregisterLoadingProgressListener(storageId);
+ ASSERT_EQ(mProgressUpdateJobQueue->mAfter, Milliseconds{});
+}
+
+TEST_F(IncrementalServiceTest, testRegisterLoadingProgressListenerFailsToGetProgress) {
+ mIncFs->countFilledBlocksFails();
+ mFs->hasFiles();
+
+ TemporaryDir tempDir;
+ int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel),
+ IncrementalService::CreateOptions::CreateNew,
+ {}, {}, {});
+ sp<NiceMock<MockStorageLoadingProgressListener>> listener{
+ new NiceMock<MockStorageLoadingProgressListener>};
+ NiceMock<MockStorageLoadingProgressListener>* listenerMock = listener.get();
+ EXPECT_CALL(*listenerMock, onStorageLoadingProgressChanged(_, _)).Times(0);
+ mIncrementalService->registerLoadingProgressListener(storageId, listener);
+}
} // namespace android::os::incremental