Merge "Delay volume state changed broadcasts until the Storage Service processes it" into rvc-dev am: d5b24870c6

Change-Id: Iffece2c81989e0c0ea6c57586e8fb75c043721ab
This commit is contained in:
Abhijeet Kaur
2020-03-20 22:12:45 +00:00
committed by Automerger Merge Worker
6 changed files with 187 additions and 69 deletions

View File

@@ -10183,6 +10183,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";

View File

@@ -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(() -> {

View File

@@ -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);
}

View File

@@ -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,78 +1506,89 @@ class StorageManagerService extends IStorageManager.Stub
return true;
}
@GuardedBy("mLock")
private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) {
if (vol.type == VolumeInfo.TYPE_EMULATED && newState != VolumeInfo.STATE_MOUNTED) {
mFuseMountedUser.remove(vol.getMountUserId());
}
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
if (!TextUtils.isEmpty(vol.fsUuid)) {
VolumeRecord rec = mRecords.get(vol.fsUuid);
if (rec == null) {
rec = new VolumeRecord(vol.type, vol.fsUuid);
rec.partGuid = vol.partGuid;
rec.createdMillis = System.currentTimeMillis();
if (vol.type == VolumeInfo.TYPE_PRIVATE) {
rec.nickname = vol.disk.getDescription();
}
mRecords.put(rec.fsUuid, rec);
} else {
// Handle upgrade case where we didn't store partition GUID
if (TextUtils.isEmpty(rec.partGuid)) {
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());
}
// Remember that we saw this volume so we're ready to accept user
// metadata, or so we can annoy them when a private volume is ejected
if (!TextUtils.isEmpty(vol.fsUuid)) {
VolumeRecord rec = mRecords.get(vol.fsUuid);
if (rec == null) {
rec = new VolumeRecord(vol.type, vol.fsUuid);
rec.partGuid = vol.partGuid;
rec.createdMillis = System.currentTimeMillis();
if (vol.type == VolumeInfo.TYPE_PRIVATE) {
rec.nickname = vol.disk.getDescription();
}
mRecords.put(rec.fsUuid, rec);
} else {
// Handle upgrade case where we didn't store partition GUID
if (TextUtils.isEmpty(rec.partGuid)) {
rec.partGuid = vol.partGuid;
}
}
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
// processes that receive the intent unnecessarily.
if (mBootCompleted && isBroadcastWorthy(vol)) {
final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
}
final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
if (!Objects.equals(oldStateEnv, newStateEnv)) {
// Kick state changed event towards all started users. Any users
// started after this point will trigger additional
// user-specific broadcasts.
for (int userId : mSystemUnlockedUsers) {
if (vol.isVisibleForRead(userId)) {
final StorageVolume userVol = vol.buildStorageVolume(mContext, userId,
false);
mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
newStateEnv);
}
}
}
rec.lastSeenMillis = System.currentTimeMillis();
writeSettingsLocked();
}
mCallbacks.notifyVolumeStateChanged(vol, oldState, newState);
// Do not broadcast before boot has completed to avoid launching the
// processes that receive the intent unnecessarily.
if (mBootCompleted && isBroadcastWorthy(vol)) {
final Intent intent = new Intent(VolumeInfo.ACTION_VOLUME_STATE_CHANGED);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id);
intent.putExtra(VolumeInfo.EXTRA_VOLUME_STATE, newState);
intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.fsUuid);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
mHandler.obtainMessage(H_INTERNAL_BROADCAST, intent).sendToTarget();
}
final String oldStateEnv = VolumeInfo.getEnvironmentForState(oldState);
final String newStateEnv = VolumeInfo.getEnvironmentForState(newState);
if (!Objects.equals(oldStateEnv, newStateEnv)) {
// Kick state changed event towards all started users. Any users
// started after this point will trigger additional
// user-specific broadcasts.
for (int userId : mSystemUnlockedUsers) {
if (vol.isVisibleForRead(userId)) {
final StorageVolume userVol = vol.buildStorageVolume(mContext, userId, false);
mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
mCallbacks.notifyStorageStateChanged(userVol.getPath(), oldStateEnv,
newStateEnv);
}
}
}
if ((vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_STUB)
if ((vol.type == VolumeInfo.TYPE_PUBLIC || vol.type == VolumeInfo.TYPE_STUB)
&& vol.state == VolumeInfo.STATE_EJECTING) {
// TODO: this should eventually be handled by new ObbVolume state changes
/*
* Some OBBs might have been unmounted when this volume was
* unmounted, so send a message to the handler to let it know to
* remove those from the list of mounted OBBS.
*/
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
OBB_FLUSH_MOUNT_STATE, vol.path));
// TODO: this should eventually be handled by new ObbVolume state changes
/*
* Some OBBs might have been unmounted when this volume was
* unmounted, so send a message to the handler to let it know to
* remove those from the list of mounted OBBS.
*/
mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
OBB_FLUSH_MOUNT_STATE, vol.path));
}
maybeLogMediaMount(vol, newState);
}
maybeLogMediaMount(vol, newState);
}
private void maybeLogMediaMount(VolumeInfo vol, int newState) {

View File

@@ -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}.
*

View File

@@ -35,6 +35,7 @@ import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.UserHandle;
import android.os.storage.StorageManagerInternal;
import android.os.storage.StorageVolume;
import android.service.storage.ExternalStorageService;
import android.service.storage.IExternalStorageService;
import android.text.TextUtils;
@@ -99,6 +100,23 @@ public final class StorageUserConnection {
}
}
/**
* Notifies Storage Service about volume state changed.
*
* @throws ExternalStorageServiceException if failed to notify the Storage Service that
* {@code StorageVolume} is changed
*/
public void notifyVolumeStateChanged(String sessionId, StorageVolume vol)
throws ExternalStorageServiceException {
Objects.requireNonNull(sessionId);
Objects.requireNonNull(vol);
prepareRemote();
synchronized (mLock) {
mActiveConnection.notifyVolumeStateChangedLocked(sessionId, vol);
}
}
/**
* Removes a session without ending it or waiting for exit.
*
@@ -287,6 +305,20 @@ public final class StorageUserConnection {
}
}
public void notifyVolumeStateChangedLocked(String sessionId, StorageVolume vol) throws
ExternalStorageServiceException {
CountDownLatch latch = new CountDownLatch(1);
try {
mRemote.notifyVolumeStateChanged(sessionId, vol, new RemoteCallback(
result -> setResultLocked(latch, result)));
waitForLatch(latch, "notify_volume_state_changed " + vol);
maybeThrowExceptionLocked();
} catch (Exception e) {
throw new ExternalStorageServiceException("Failed to notify volume state changed "
+ "for vol : " + vol, e);
}
}
private void setResultLocked(CountDownLatch latch, Bundle result) {
mLastException = result.getParcelable(EXTRA_ERROR);
latch.countDown();