Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 29dc0896 authored by Abhijeet Kaur's avatar Abhijeet Kaur
Browse files

Delay volume state changed broadcasts until the Storage Service processes it

In R, all file path access is proxied through the Media Provider database.
This change ensures that the filesystem is indexed in the database
before broadcasting volume state mounted. This is essential as we do
not want apps to access the volume before the data is ready.

Similarly, the Storage Service processes other volume state changes before
broadcasting to other apps.

Since we want to block volume state changed broadcasts until the volume
is processed/scanned by the Storage Service, which may take time, so we
handle volume state changed in a separate thread (so as to not block its
callers like vold).

Bug: 145194312
Test: builds
Test: atest AdoptableHostTest
Test: atest com.android.tests.fused.host.FuseDaemonHostTest
Change-Id: I1677fdd5672cea531e98cda76d967af13e5fac9e
parent bf770c69
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -10165,6 +10165,7 @@ package android.service.storage {
    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 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
    field public static final int FLAG_SESSION_TYPE_FUSE = 1; // 0x1
    field public static final String SERVICE_INTERFACE = "android.service.storage.ExternalStorageService";
+31 −2
Original line number Diff line number Diff line
@@ -25,11 +25,13 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.storage.StorageVolume;

import com.android.internal.os.BackgroundThread;

import java.io.File;
import java.io.IOException;
@@ -97,7 +99,7 @@ public abstract class ExternalStorageService extends Service {
    public @interface SessionFlag {}

    private final ExternalStorageServiceWrapper mWrapper = new ExternalStorageServiceWrapper();
    private final Handler mHandler = new Handler(Looper.getMainLooper(), null, true);
    private final Handler mHandler = BackgroundThread.getHandler();

    /**
     * Called when the system starts a session associated with {@code deviceFd}
@@ -131,6 +133,20 @@ public abstract class ExternalStorageService extends Service {
     */
    public abstract void onEndSession(@NonNull String sessionId) throws IOException;

    /**
     * Called when any volume's state changes.
     *
     * <p> This is required to communicate volume state changes with the Storage Service before
     * broadcasting to other apps. The Storage Service needs to process any change in the volume
     * state (before other apps receive a broadcast for the same) to update the database so that
     * other apps have the correct view of the volume.
     *
     * <p> Blocks until the Storage Service processes/scans the volume or fails in doing so.
     *
     * @param vol name of the volume that was changed
     */
    public abstract void onVolumeStateChanged(@NonNull StorageVolume vol) throws IOException;

    @Override
    @NonNull
    public final IBinder onBind(@NonNull Intent intent) {
@@ -153,6 +169,19 @@ public abstract class ExternalStorageService extends Service {
            });
        }

        @Override
        public void notifyVolumeStateChanged(String sessionId, StorageVolume vol,
                RemoteCallback callback) {
            mHandler.post(() -> {
                try {
                    onVolumeStateChanged(vol);
                    sendResult(sessionId, null /* throwable */, callback);
                } catch (Throwable t) {
                    sendResult(sessionId, t, callback);
                }
            });
        }

        @Override
        public void endSession(String sessionId, RemoteCallback callback) throws RemoteException {
            mHandler.post(() -> {
+3 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.service.storage;

import android.os.ParcelFileDescriptor;
import android.os.RemoteCallback;
import android.os.storage.StorageVolume;

/**
 * @hide
@@ -27,4 +28,6 @@ oneway interface IExternalStorageService
    void startSession(@utf8InCpp String sessionId, int type, in ParcelFileDescriptor deviceFd,
         @utf8InCpp String upperPath, @utf8InCpp String lowerPath, in RemoteCallback callback);
    void endSession(@utf8InCpp String sessionId, in RemoteCallback callback);
    void notifyVolumeStateChanged(@utf8InCpp String sessionId, in StorageVolume vol,
        in RemoteCallback callback);
}
 No newline at end of file
+84 −63
Original line number Diff line number Diff line
@@ -682,6 +682,7 @@ class StorageManagerService extends IStorageManager.Stub
    private static final int H_ABORT_IDLE_MAINT = 12;
    private static final int H_BOOT_COMPLETED = 13;
    private static final int H_COMPLETE_UNLOCK_USER = 14;
    private static final int H_VOLUME_STATE_CHANGED = 15;

    class StorageManagerServiceHandler extends Handler {
        public StorageManagerServiceHandler(Looper looper) {
@@ -805,6 +806,11 @@ class StorageManagerService extends IStorageManager.Stub
                    completeUnlockUser((int) msg.obj);
                    break;
                }
                case H_VOLUME_STATE_CHANGED: {
                    final SomeArgs args = (SomeArgs) msg.obj;
                    onVolumeStateChangedInternal((VolumeInfo) args.arg1, (int) args.arg2,
                            (int) args.arg3);
                }
            }
        }
    }
@@ -1323,7 +1329,11 @@ class StorageManagerService extends IStorageManager.Stub
                    final int oldState = vol.state;
                    final int newState = state;
                    vol.state = newState;
                    onVolumeStateChangedLocked(vol, oldState, newState);
                    final SomeArgs args = SomeArgs.obtain();
                    args.arg1 = vol;
                    args.arg2 = oldState;
                    args.arg3 = newState;
                    mHandler.obtainMessage(H_VOLUME_STATE_CHANGED, args).sendToTarget();
                }
            }
        }
@@ -1496,8 +1506,8 @@ class StorageManagerService extends IStorageManager.Stub
        return true;
    }

    @GuardedBy("mLock")
    private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
    private void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) {
        synchronized (mLock) {
            if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
                mFuseMountedUser.remove(vol.getMountUserId());
            }
@@ -1523,7 +1533,16 @@ class StorageManagerService extends IStorageManager.Stub
                rec.lastSeenMillis = System.currentTimeMillis();
                writeSettingsLocked();
            }

        }
        // This is a blocking call to Storage Service which needs to process volume state changed
        // before notifying other listeners.
        // Intentionally called without the mLock to avoid deadlocking from the Storage Service.
        try {
            mStorageSessionController.notifyVolumeStateChanged(vol);
        } catch (ExternalStorageServiceException e) {
            Log.e(TAG, "Failed to notify volume state changed to the Storage Service", e);
        }
        synchronized (mLock) {
            mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);

            // Do not broadcast before boot has completed to avoid launching the
@@ -1547,7 +1566,8 @@ class StorageManagerService extends IStorageManager.Stub
                // user-specific broadcasts.
                for (int userId : mSystemUnlockedUsers) {
                    if (vol.isVisibleForRead(userId)) {
                    final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
                        final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
                                false);
                        mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();

                        mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
@@ -1569,6 +1589,7 @@ class StorageManagerService extends IStorageManager.Stub
            }
            maybeLogMediaMount(vol, newState);
        }
    }

    private void maybeLogMediaMount(VolumeInfo vol, int newState) {
        if (!SecurityLog.isLoggingEnabled()) {
+32 −0
Original line number Diff line number Diff line
@@ -105,6 +105,38 @@ public final class StorageSessionController {
        }
    }

    /**
     * Notifies the Storage Service that volume state for {@code vol} is changed.
     * A session may already be created for this volume if it is mounted before or the volume state
     * has changed to mounted.
     *
     * Does nothing if {@link #shouldHandle} is {@code false}
     *
     * Blocks until the Storage Service processes/scans the volume or fails in doing so.
     *
     * @throws ExternalStorageServiceException if it fails to connect to ExternalStorageService
     */
    public void notifyVolumeStateChanged(VolumeInfo vol) throws ExternalStorageServiceException {
        if (!shouldHandle(vol)) {
            return;
        }
        String sessionId = vol.getId();
        int userId = vol.getMountUserId();

        StorageUserConnection connection = null;
        synchronized (mLock) {
            connection = mConnections.get(userId);
            if (connection != null) {
                Slog.i(TAG, "Notifying volume state changed for session with id: " + sessionId);
                connection.notifyVolumeStateChanged(sessionId,
                        vol.buildStorageVolume(mContext, userId, false));
            } else {
                Slog.w(TAG, "No available storage user connection for userId : " + userId);
            }
        }
    }


    /**
     * Removes and returns the {@link StorageUserConnection} for {@code vol}.
     *
Loading