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:
@@ -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";
|
||||
|
||||
@@ -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(() -> {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user