diff options
author | 2020-09-03 11:45:53 -0700 | |
---|---|---|
committer | 2020-09-15 17:39:41 -0700 | |
commit | a70985945b1d51d30778f1c047a7e408d5d9032d (patch) | |
tree | e5d3818082f61617d3b2d26ad367a77a8265456d | |
parent | dc8a48f161c59a140c8108217861138aaf64e180 (diff) |
[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
16 files changed, 513 insertions, 70 deletions
diff --git a/Android.bp b/Android.bp index eacf57ccd38c..c6f9362b4919 100644 --- a/Android.bp +++ b/Android.bp @@ -1029,6 +1029,7 @@ filegroup { name: "incremental_manager_aidl", srcs: [ "core/java/android/os/incremental/IIncrementalService.aidl", + "core/java/android/os/incremental/IStorageLoadingProgressListener.aidl", "core/java/android/os/incremental/IncrementalNewFileParams.aidl", "core/java/android/os/incremental/IStorageHealthListener.aidl", "core/java/android/os/incremental/StorageHealthCheckParams.aidl", diff --git a/core/java/android/content/pm/IPackageLoadingProgressCallback.aidl b/core/java/android/content/pm/IPackageLoadingProgressCallback.aidl new file mode 100644 index 000000000000..8adfb7a17fbd --- /dev/null +++ b/core/java/android/content/pm/IPackageLoadingProgressCallback.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 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.content.pm; + +/** + * Callbacks for Package Manager to report package loading progress to listeners. + * @hide + */ +oneway interface IPackageLoadingProgressCallback { + void onPackageLoadingProgressChanged(float progress); +}
\ No newline at end of file diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 1f8cee25be51..244cfe124381 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -31,6 +31,7 @@ import android.content.pm.IPackageInstaller; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.IPackageDataObserver; +import android.content.pm.IPackageLoadingProgressCallback; import android.content.pm.IPackageMoveObserver; import android.content.pm.IPackageStatsObserver; import android.content.pm.IntentFilterVerificationInfo; diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl index f351c7df8d5d..ad9a16231f78 100644 --- a/core/java/android/os/incremental/IIncrementalService.aidl +++ b/core/java/android/os/incremental/IIncrementalService.aidl @@ -19,6 +19,7 @@ package android.os.incremental; import android.content.pm.DataLoaderParamsParcel; import android.content.pm.IDataLoaderStatusListener; import android.os.incremental.IncrementalNewFileParams; +import android.os.incremental.IStorageLoadingProgressListener; import android.os.incremental.IStorageHealthListener; import android.os.incremental.StorageHealthCheckParams; @@ -133,4 +134,14 @@ interface IIncrementalService { * Waits until all native library extraction is done for the storage */ boolean waitForNativeBinariesExtraction(int storageId); + + /** + * Register to start listening for loading progress change for a storage. + */ + boolean registerLoadingProgressListener(int storageId, IStorageLoadingProgressListener listener); + + /** + * Stop listening for the loading progress change for a storage. + */ + boolean unregisterLoadingProgressListener(int storageId); } diff --git a/core/java/android/os/incremental/IStorageLoadingProgressListener.aidl b/core/java/android/os/incremental/IStorageLoadingProgressListener.aidl new file mode 100644 index 000000000000..efb51fd4c8bd --- /dev/null +++ b/core/java/android/os/incremental/IStorageLoadingProgressListener.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 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; + +/** + * Callbacks for Incremental Service to report storage loading progress to Package Manager Service. + * @hide + */ +oneway interface IStorageLoadingProgressListener { + void onStorageLoadingProgressChanged(int storageId, float progress); +} diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java index c7f50c951d70..768ef975bd99 100644 --- a/core/java/android/os/incremental/IncrementalManager.java +++ b/core/java/android/os/incremental/IncrementalManager.java @@ -23,6 +23,8 @@ import android.annotation.SystemService; import android.content.Context; import android.content.pm.DataLoaderParams; import android.content.pm.IDataLoaderStatusListener; +import android.content.pm.IPackageLoadingProgressCallback; +import android.os.RemoteCallbackList; import android.os.RemoteException; import android.util.SparseArray; @@ -76,28 +78,15 @@ public final class IncrementalManager { } private final @Nullable IIncrementalService mService; - @GuardedBy("mStorages") - private final SparseArray<IncrementalStorage> mStorages = new SparseArray<>(); + + private final LoadingProgressCallbacks mLoadingProgressCallbacks = + new LoadingProgressCallbacks(); public IncrementalManager(IIncrementalService service) { mService = service; } /** - * Returns a storage object given a storage ID. - * - * @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) { - return mStorages.get(storageId); - } - } - - /** * Opens or create an Incremental File System mounted directory and returns an * IncrementalStorage object. * @@ -123,9 +112,6 @@ public final class IncrementalManager { return null; } final IncrementalStorage storage = new IncrementalStorage(mService, id); - synchronized (mStorages) { - mStorages.put(id, storage); - } if (autoStartDataLoader) { storage.startLoading(); } @@ -150,9 +136,6 @@ public final class IncrementalManager { return null; } final IncrementalStorage storage = new IncrementalStorage(mService, id); - synchronized (mStorages) { - mStorages.put(id, storage); - } return storage; } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -173,11 +156,7 @@ public final class IncrementalManager { if (id < 0) { return null; } - final IncrementalStorage storage = new IncrementalStorage(mService, id); - synchronized (mStorages) { - mStorages.put(id, storage); - } - return storage; + return new IncrementalStorage(mService, id); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -263,25 +242,6 @@ public final class IncrementalManager { } /** - * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing. - * Unbinds the target dir and deletes the corresponding storage instance. - */ - public void closeStorage(@NonNull String path) { - try { - final int id = mService.openStorage(path); - if (id < 0) { - return; - } - mService.deleteStorage(id); - synchronized (mStorages) { - mStorages.remove(id); - } - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** * Checks if Incremental feature is enabled on this device. */ public static boolean isFeatureEnabled() { @@ -311,6 +271,149 @@ public final class IncrementalManager { return nativeUnsafeGetFileSignature(path); } + /** + * Closes a storage specified by the absolute path. If the path is not Incremental, do nothing. + * Unbinds the target dir and deletes the corresponding storage instance. + * Deletes the package name and associated storage id from maps. + */ + public void onPackageRemoved(@NonNull String codePath) { + try { + final IncrementalStorage storage = openStorage(codePath); + if (storage == null) { + return; + } + mLoadingProgressCallbacks.cleanUpCallbacks(storage); + mService.deleteStorage(storage.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Called when a new callback wants to listen to the loading progress of an installed package. + * Increment the count of callbacks associated to the corresponding storage. + * Only register storage listener if there hasn't been any existing callback on the storage yet. + * @param codePath Path of the installed package. This path is on an Incremental Storage. + * @param callback To report loading progress to. + * @return True if the package name and associated storage id are valid. False otherwise. + */ + public boolean registerCallback(@NonNull String codePath, + @NonNull IPackageLoadingProgressCallback callback) { + final IncrementalStorage storage = openStorage(codePath); + if (storage == null) { + // storage does not exist, package not installed + return false; + } + return mLoadingProgressCallbacks.registerCallback(storage, callback); + } + + /** + * Called when a callback wants to stop listen to the loading progress of an installed package. + * Decrease the count of the callbacks on the associated to the corresponding storage. + * If the count becomes zero, unregister the storage listener. + * @param codePath Path of the installed package + * @return True if the package name and associated storage id are valid. False otherwise. + */ + public boolean unregisterCallback(@NonNull String codePath, + @NonNull IPackageLoadingProgressCallback callback) { + final IncrementalStorage storage = openStorage(codePath); + if (storage == null) { + // storage does not exist, package not installed + return false; + } + return mLoadingProgressCallbacks.unregisterCallback(storage, callback); + } + + private static class LoadingProgressCallbacks extends IStorageLoadingProgressListener.Stub { + @GuardedBy("mCallbacks") + private final SparseArray<RemoteCallbackList<IPackageLoadingProgressCallback>> mCallbacks = + new SparseArray<>(); + + // TODO(b/165841827): disable callbacks when app state changes to fully loaded + public void cleanUpCallbacks(@NonNull IncrementalStorage storage) { + final int storageId = storage.getId(); + final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; + synchronized (mCallbacks) { + callbacksForStorage = mCallbacks.removeReturnOld(storageId); + } + if (callbacksForStorage == null) { + return; + } + // Unregister all existing callbacks on this storage + callbacksForStorage.kill(); + storage.unregisterLoadingProgressListener(); + } + + // TODO(b/165841827): handle reboot and app update + public boolean registerCallback(@NonNull IncrementalStorage storage, + @NonNull IPackageLoadingProgressCallback callback) { + final int storageId = storage.getId(); + synchronized (mCallbacks) { + RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage = + mCallbacks.get(storageId); + if (callbacksForStorage == null) { + callbacksForStorage = new RemoteCallbackList<>(); + mCallbacks.put(storageId, callbacksForStorage); + } + // Registration in RemoteCallbackList needs to be done first, such that when events + // come from Incremental Service, the callback is already registered + callbacksForStorage.register(callback); + if (callbacksForStorage.getRegisteredCallbackCount() > 1) { + // already listening for progress for this storage + return true; + } + } + return storage.registerLoadingProgressListener(this); + } + + public boolean unregisterCallback(@NonNull IncrementalStorage storage, + @NonNull IPackageLoadingProgressCallback callback) { + final int storageId = storage.getId(); + final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; + synchronized (mCallbacks) { + callbacksForStorage = mCallbacks.get(storageId); + if (callbacksForStorage == null) { + // no callback has ever been registered on this storage + return false; + } + if (!callbacksForStorage.unregister(callback)) { + // the callback was not registered + return false; + } + if (callbacksForStorage.getRegisteredCallbackCount() > 0) { + // other callbacks are still listening on this storage + return true; + } + mCallbacks.delete(storageId); + } + // stop listening for this storage + return storage.unregisterLoadingProgressListener(); + } + + @Override + public void onStorageLoadingProgressChanged(int storageId, float progress) { + final RemoteCallbackList<IPackageLoadingProgressCallback> callbacksForStorage; + synchronized (mCallbacks) { + callbacksForStorage = mCallbacks.get(storageId); + } + if (callbacksForStorage == null) { + // no callback has ever been registered on this storage + return; + } + final int n = callbacksForStorage.beginBroadcast(); + // RemoteCallbackList use ArrayMap internally and it's safe to iterate this way + for (int i = 0; i < n; i++) { + final IPackageLoadingProgressCallback callback = + callbacksForStorage.getBroadcastItem(i); + try { + callback.onPackageLoadingProgressChanged(progress); + } catch (RemoteException ignored) { + } + } + callbacksForStorage.finishBroadcast(); + } + } + /* Native methods */ private static native boolean nativeIsEnabled(); private static native boolean nativeIsIncrementalPath(@NonNull String path); diff --git a/core/java/android/os/incremental/IncrementalStorage.java b/core/java/android/os/incremental/IncrementalStorage.java index ed386f79882f..f83541293330 100644 --- a/core/java/android/os/incremental/IncrementalStorage.java +++ b/core/java/android/os/incremental/IncrementalStorage.java @@ -519,4 +519,29 @@ public final class IncrementalStorage { 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; + } + } } diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index cf9324c13ae8..0c56d46151c1 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.PackageInfoFlags; 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.Retention; 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 @@ public abstract class PackageManagerInternal { * 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 5850dc012226..3e4e88c56995 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.IPackageDeleteObserver; 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 @@ public class PackageManagerService extends IPackageManager.Stub String codePath = codeFile.getAbsolutePath(); if (mIncrementalManager != null && isIncrementalPath(codePath)) { - mIncrementalManager.closeStorage(codePath); + mIncrementalManager.onPackageRemoved(codePath); } removeCodePathLI(codeFile); @@ -17125,10 +17126,11 @@ public class PackageManagerService extends IPackageManager.Stub & 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 class PackageManagerService extends IPackageManager.Stub 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 87ae4d719d11..bf3a8964465b 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -301,6 +301,20 @@ binder::Status BinderIncrementalService::waitForNativeBinariesExtraction(int sto 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 8478142b2d95..123849876095 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -81,6 +81,12 @@ public: 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 447ee552a335..10a508b06086 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -275,6 +275,7 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v 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 @@ IncrementalService::IncrementalService(ServiceManagerWrapper&& sm, std::string_v 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 @@ IncrementalService::~IncrementalService() { 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 @@ float IncrementalService::getLoadingProgressFromPath(const IncFsMount& ifs, 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::onAppOpChanged(const std::string& packageName) { } } -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 @@ void IncrementalService::DataLoaderStub::cleanupResources() { mHealthPath.clear(); unregisterFromPendingReads(); resetHealthControl(); - mService.removeTimedJobs(mId); + mService.removeTimedJobs(*mService.mTimedQueue, mId); } requestDestroy(); @@ -2169,7 +2204,8 @@ void IncrementalService::DataLoaderStub::updateHealthStatus(bool baseline) { } 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 267458d8769c..a49e0f36c9bc 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 DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>; 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 @@ public: 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 @@ private: 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 @@ private: 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 f6d89c53be32..144c466cf9e9 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -357,6 +357,10 @@ std::unique_ptr<TimedQueueWrapper> RealServiceManager::getTimedQueue() { 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 6376d86543f8..4815cafc0995 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -154,6 +154,7 @@ public: 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 @@ public: 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 a290a1791481..aec9fa1c3277 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -504,6 +504,14 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ public: 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 @@ protected: 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 @@ TEST_F(IncrementalServiceTest, testGetLoadingProgressSuccess) { 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 |