diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 6085faca862d3..f85a8101da6ae 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9630,6 +9630,7 @@ package android.service.storage { ctor public ExternalStorageService(); method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void onEndSession(@NonNull String) throws java.io.IOException; + method public void onFreeCacheRequested(@NonNull java.util.UUID, long); method public abstract void onStartSession(@NonNull String, int, @NonNull android.os.ParcelFileDescriptor, @NonNull java.io.File, @NonNull java.io.File) throws java.io.IOException; method public abstract void onVolumeStateChanged(@NonNull android.os.storage.StorageVolume) throws java.io.IOException; field public static final int FLAG_SESSION_ATTRIBUTE_INDEXABLE = 2; // 0x2 diff --git a/core/java/android/os/storage/StorageManagerInternal.java b/core/java/android/os/storage/StorageManagerInternal.java index 55ba15a5e57be..b12bb2ece4c2f 100644 --- a/core/java/android/os/storage/StorageManagerInternal.java +++ b/core/java/android/os/storage/StorageManagerInternal.java @@ -101,4 +101,15 @@ public abstract class StorageManagerInternal { * Return true if uid is external storage service. */ public abstract boolean isExternalStorageService(int uid); + + /** + * Frees cache held by ExternalStorageService. + * + *

Blocks until the service frees the cache or fails in doing so. + * + * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed, + * null value indicates private internal volume. + * @param bytes number of bytes which need to be freed + */ + public abstract void freeCache(@Nullable String volumeUuid, long bytes); } diff --git a/core/java/android/service/storage/ExternalStorageService.java b/core/java/android/service/storage/ExternalStorageService.java index 3b4d84a1c6682..0123c368583c2 100644 --- a/core/java/android/service/storage/ExternalStorageService.java +++ b/core/java/android/service/storage/ExternalStorageService.java @@ -16,6 +16,7 @@ package android.service.storage; +import android.annotation.BytesLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SdkConstant; @@ -29,6 +30,7 @@ import android.os.ParcelFileDescriptor; import android.os.ParcelableException; import android.os.RemoteCallback; import android.os.RemoteException; +import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import com.android.internal.os.BackgroundThread; @@ -37,6 +39,7 @@ import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.UUID; /** * A service to handle filesystem I/O from other apps. @@ -147,6 +150,18 @@ public abstract class ExternalStorageService extends Service { */ public abstract void onVolumeStateChanged(@NonNull StorageVolume vol) throws IOException; + /** + * Called when any cache held by the ExternalStorageService needs to be freed. + * + *

Blocks until the service frees the cache or fails in doing so. + * + * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed + * @param bytes number of bytes which need to be freed + */ + public void onFreeCacheRequested(@NonNull UUID volumeUuid, @BytesLong long bytes) { + throw new UnsupportedOperationException("onFreeCacheRequested not implemented"); + } + @Override @NonNull public final IBinder onBind(@NonNull Intent intent) { @@ -182,6 +197,19 @@ public abstract class ExternalStorageService extends Service { }); } + @Override + public void freeCache(String sessionId, String volumeUuid, long bytes, + RemoteCallback callback) { + mHandler.post(() -> { + try { + onFreeCacheRequested(StorageManager.convert(volumeUuid), bytes); + sendResult(sessionId, null /* throwable */, callback); + } catch (Throwable t) { + sendResult(sessionId, t, callback); + } + }); + } + @Override public void endSession(String sessionId, RemoteCallback callback) throws RemoteException { mHandler.post(() -> { diff --git a/core/java/android/service/storage/IExternalStorageService.aidl b/core/java/android/service/storage/IExternalStorageService.aidl index 30fefd33016da..d06671b3fb9f0 100644 --- a/core/java/android/service/storage/IExternalStorageService.aidl +++ b/core/java/android/service/storage/IExternalStorageService.aidl @@ -30,4 +30,6 @@ oneway interface IExternalStorageService void endSession(@utf8InCpp String sessionId, in RemoteCallback callback); void notifyVolumeStateChanged(@utf8InCpp String sessionId, in StorageVolume vol, in RemoteCallback callback); + void freeCache(@utf8InCpp String sessionId, in String volumeUuid, long bytes, + in RemoteCallback callback); } \ No newline at end of file diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 6b45b4833c844..c951fd438b783 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -4475,6 +4475,15 @@ class StorageManagerService extends IStorageManager.Stub return mMediaStoreAuthorityAppId == UserHandle.getAppId(uid); } + @Override + public void freeCache(String volumeUuid, long freeBytes) { + try { + mStorageSessionController.freeCache(volumeUuid, freeBytes); + } catch (ExternalStorageServiceException e) { + Log.e(TAG, "Failed to free cache of vol : " + volumeUuid, e); + } + } + public boolean hasExternalStorage(int uid, String packageName) { // No need to check for system uid. This avoids a deadlock between // PackageManagerService and AppOpsService. diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 282fb5837cf51..4cf9933bef0a8 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -5417,6 +5417,14 @@ public class PackageManagerService extends IPackageManager.Stub InstantAppRegistry.DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD))) { return; } + + // 11. Free storage service cache + StorageManagerInternal smInternal = + mInjector.getLocalService(StorageManagerInternal.class); + // TODO(b/170481432): Decide what value of bytes needs to be sent instead of + // sending the bytes parameter of freeStorage + smInternal.freeCache(volumeUuid, bytes); + if (file.getUsableSpace() >= bytes) return; } else { try { mInstaller.freeCache(volumeUuid, bytes, 0, 0); diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java index 0abeac890df1e..808d130c6d6fd 100644 --- a/services/core/java/com/android/server/storage/StorageSessionController.java +++ b/services/core/java/com/android/server/storage/StorageSessionController.java @@ -32,6 +32,7 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.os.UserHandle; +import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.provider.MediaStore; import android.service.storage.ExternalStorageService; @@ -133,6 +134,28 @@ public final class StorageSessionController { } } + /** + * Frees any cache held by ExternalStorageService. + * + *

Blocks until the service frees the cache or fails in doing so. + * + * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed + * @param bytes number of bytes which need to be freed + * @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService + */ + public void freeCache(String volumeUuid, long bytes) + throws ExternalStorageServiceException { + synchronized (mLock) { + int size = mConnections.size(); + for (int i = 0; i < size; i++) { + int key = mConnections.keyAt(i); + StorageUserConnection connection = mConnections.get(key); + if (connection != null) { + connection.freeCache(volumeUuid, bytes); + } + } + } + } /** * Removes and returns the {@link StorageUserConnection} for {@code vol}. diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java index af2628971bddf..4a32d0f8be4e8 100644 --- a/services/core/java/com/android/server/storage/StorageUserConnection.java +++ b/services/core/java/com/android/server/storage/StorageUserConnection.java @@ -124,6 +124,26 @@ public final class StorageUserConnection { mActiveConnection.notifyVolumeStateChanged(sessionId, vol); } + /** + * Frees any cache held by ExternalStorageService. + * + *

Blocks until the service frees the cache or fails in doing so. + * + * @param volumeUuid uuid of the {@link StorageVolume} from which cache needs to be freed + * @param bytes number of bytes which need to be freed + * @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService + */ + public void freeCache(String volumeUuid, long bytes) + throws ExternalStorageServiceException { + Objects.requireNonNull(volumeUuid); + + synchronized (mSessionsLock) { + for (String sessionId : mSessions.keySet()) { + mActiveConnection.freeCache(sessionId, volumeUuid, bytes); + } + } + } + /** * Removes a session without ending it or waiting for exit. * @@ -214,7 +234,7 @@ public final class StorageUserConnection { // A list of outstanding futures for async calls, for which we are still waiting // for a callback. Used to unblock waiters if the service dies. @GuardedBy("mLock") - private ArrayList> mOutstandingOps = new ArrayList<>(); + private final ArrayList> mOutstandingOps = new ArrayList<>(); @Override public void close() { @@ -309,6 +329,17 @@ public final class StorageUserConnection { } } + public void freeCache(String sessionId, String volumeUuid, long bytes) + throws ExternalStorageServiceException { + try { + waitForAsync((service, callback) -> + service.freeCache(sessionId, volumeUuid, bytes, callback)); + } catch (Exception e) { + throw new ExternalStorageServiceException("Failed to free " + bytes + + " bytes for volumeUuid : " + volumeUuid, e); + } + } + private void setResult(Bundle result, CompletableFuture future) { ParcelableException ex = result.getParcelable(EXTRA_ERROR); if (ex != null) {