diff options
8 files changed, 492 insertions, 30 deletions
diff --git a/core/java/android/os/storage/DiskInfo.java b/core/java/android/os/storage/DiskInfo.java index e6160aa..dc96640 100644 --- a/core/java/android/os/storage/DiskInfo.java +++ b/core/java/android/os/storage/DiskInfo.java @@ -16,6 +16,7 @@ package android.os.storage; +import android.annotation.NonNull; import android.content.res.Resources; import android.os.Parcel; import android.os.Parcelable; @@ -59,6 +60,10 @@ public class DiskInfo implements Parcelable { volumeIds = parcel.readStringArray(); } + public @NonNull String getId() { + return id; + } + public String getDescription() { // TODO: splice vendor label into these strings if ((flags & FLAG_SD) != 0) { diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 10ffd48..0a8187e 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -923,12 +923,13 @@ public interface IMountService extends IInterface { } @Override - public VolumeInfo[] getVolumes() throws RemoteException { + public VolumeInfo[] getVolumes(int _flags) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); VolumeInfo[] _result; try { _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(_flags); mRemote.transact(Stub.TRANSACTION_getVolumes, _data, _reply, 0); _reply.readException(); _result = _reply.createTypedArray(VolumeInfo.CREATOR); @@ -1029,6 +1030,39 @@ public interface IMountService extends IInterface { _data.recycle(); } } + + @Override + public void setVolumeNickname(String volId, String nickname) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeString(nickname); + mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(volId); + _data.writeInt(flags); + _data.writeInt(mask); + mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } private static final String DESCRIPTOR = "IMountService"; @@ -1132,6 +1166,9 @@ public interface IMountService extends IInterface { static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; + static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + /** * Cast an IBinder object into an IMountService interface, generating a * proxy if needed. @@ -1566,7 +1603,8 @@ public interface IMountService extends IInterface { } case TRANSACTION_getVolumes: { data.enforceInterface(DESCRIPTOR); - VolumeInfo[] volumes = getVolumes(); + int _flags = data.readInt(); + VolumeInfo[] volumes = getVolumes(_flags); reply.writeNoException(); reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; @@ -1614,6 +1652,23 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_setVolumeNickname: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + String nickname = data.readString(); + setVolumeNickname(volId, nickname); + reply.writeNoException(); + return true; + } + case TRANSACTION_setVolumeUserFlags: { + data.enforceInterface(DESCRIPTOR); + String volId = data.readString(); + int _flags = data.readInt(); + int _mask = data.readInt(); + setVolumeUserFlags(volId, _flags, _mask); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -1902,7 +1957,7 @@ public interface IMountService extends IInterface { public void waitForAsecScan() throws RemoteException; public DiskInfo[] getDisks() throws RemoteException; - public VolumeInfo[] getVolumes() throws RemoteException; + public VolumeInfo[] getVolumes(int flags) throws RemoteException; public void mount(String volId) throws RemoteException; public void unmount(String volId) throws RemoteException; @@ -1911,4 +1966,7 @@ public interface IMountService extends IInterface { public void partitionPublic(String diskId) throws RemoteException; public void partitionPrivate(String diskId) throws RemoteException; public void partitionMixed(String diskId, int ratio) throws RemoteException; + + public void setVolumeNickname(String volId, String nickname) throws RemoteException; + public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException; } diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index 3965f9d..fd914bc 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -91,6 +91,13 @@ public interface IMountServiceListener extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_onVolumeMetadataChanged: { + data.enforceInterface(DESCRIPTOR); + final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); + onVolumeMetadataChanged(vol); + reply.writeNoException(); + return true; + } } return super.onTransact(code, data, reply, flags); } @@ -175,6 +182,22 @@ public interface IMountServiceListener extends IInterface { _data.recycle(); } } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeParcelable(vol, 0); + mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply, + android.os.IBinder.FLAG_ONEWAY); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } } static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); @@ -182,6 +205,7 @@ public interface IMountServiceListener extends IInterface { static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); static final int TRANSACTION_onVolumeStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 2); + static final int TRANSACTION_onVolumeMetadataChanged = (IBinder.FIRST_CALL_TRANSACTION + 3); } /** @@ -204,4 +228,6 @@ public interface IMountServiceListener extends IInterface { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) throws RemoteException; + + public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 29d5494..28a187d 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -40,4 +40,7 @@ public class StorageEventListener { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { } + + public void onVolumeMetadataChanged(VolumeInfo vol) { + } } diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index eb77477..0e977ff 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -70,6 +70,9 @@ public class StorageManager { /** {@hide} */ public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"; + /** {@hide} */ + public static final int FLAG_ALL_METADATA = 1 << 0; + private final Context mContext; private final ContentResolver mResolver; @@ -83,6 +86,7 @@ public class StorageManager { Handler.Callback { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; final StorageEventListener mCallback; final Handler mHandler; @@ -105,6 +109,10 @@ public class StorageManager { mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); args.recycle(); return true; + case MSG_VOLUME_METADATA_CHANGED: + mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + args.recycle(); + return true; } args.recycle(); return false; @@ -132,6 +140,13 @@ public class StorageManager { args.argi3 = newState; mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } } /** @@ -480,8 +495,13 @@ public class StorageManager { /** {@hide} */ public @NonNull List<VolumeInfo> getVolumes() { + return getVolumes(0); + } + + /** {@hide} */ + public @NonNull List<VolumeInfo> getVolumes(int flags) { try { - return Arrays.asList(mMountService.getVolumes()); + return Arrays.asList(mMountService.getVolumes(flags)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -556,6 +576,35 @@ public class StorageManager { } /** {@hide} */ + public void setVolumeNickname(String volId, String nickname) { + try { + mMountService.setVolumeNickname(volId, nickname); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeInited(String volId, boolean inited) { + try { + mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0, + VolumeInfo.USER_FLAG_INITED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeSnoozed(String volId, boolean snoozed) { + try { + mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0, + VolumeInfo.USER_FLAG_SNOOZED); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ public @Nullable StorageVolume getStorageVolume(File file) { return getStorageVolume(getVolumeList(), file); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index fe1e206..f06fc8c 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -71,6 +71,9 @@ public class VolumeInfo implements Parcelable { public static final int FLAG_PRIMARY = 1 << 0; public static final int FLAG_VISIBLE = 1 << 1; + public static final int USER_FLAG_INITED = 1 << 0; + public static final int USER_FLAG_SNOOZED = 1 << 1; + private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); @@ -104,8 +107,9 @@ public class VolumeInfo implements Parcelable { /** Framework state */ public final int mtpIndex; - public String nickname; public String diskId; + public String nickname; + public int userFlags = 0; public VolumeInfo(String id, int type, int mtpIndex) { this.id = Preconditions.checkNotNull(id); @@ -124,8 +128,9 @@ public class VolumeInfo implements Parcelable { fsLabel = parcel.readString(); path = parcel.readString(); mtpIndex = parcel.readInt(); - nickname = parcel.readString(); diskId = parcel.readString(); + nickname = parcel.readString(); + userFlags = parcel.readInt(); } public static @NonNull String getEnvironmentForState(int state) { @@ -145,6 +150,30 @@ public class VolumeInfo implements Parcelable { return getBroadcastForEnvironment(getEnvironmentForState(state)); } + public @NonNull String getId() { + return id; + } + + public @Nullable String getDiskId() { + return diskId; + } + + public int getType() { + return type; + } + + public int getState() { + return state; + } + + public @Nullable String getFsUuid() { + return fsUuid; + } + + public @Nullable String getNickname() { + return nickname; + } + public @Nullable String getDescription() { if (ID_PRIVATE_INTERNAL.equals(id)) { return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); @@ -165,6 +194,14 @@ public class VolumeInfo implements Parcelable { return (flags & FLAG_VISIBLE) != 0; } + public boolean isInited() { + return (userFlags & USER_FLAG_INITED) != 0; + } + + public boolean isSnoozed() { + return (userFlags & USER_FLAG_SNOOZED) != 0; + } + public boolean isVisibleToUser(int userId) { if (type == TYPE_PUBLIC && userId == this.userId) { return isVisible(); @@ -175,6 +212,10 @@ public class VolumeInfo implements Parcelable { } } + public File getPath() { + return new File(path); + } + public File getPathForUser(int userId) { if (path == null) { return null; @@ -284,8 +325,9 @@ public class VolumeInfo implements Parcelable { pw.println(); pw.printPair("path", path); pw.printPair("mtpIndex", mtpIndex); - pw.printPair("nickname", nickname); pw.printPair("diskId", diskId); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); pw.decreaseIndent(); pw.println(); } @@ -331,7 +373,8 @@ public class VolumeInfo implements Parcelable { parcel.writeString(fsLabel); parcel.writeString(path); parcel.writeInt(mtpIndex); - parcel.writeString(nickname); parcel.writeString(diskId); + parcel.writeString(nickname); + parcel.writeInt(userFlags); } } diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 6830957..818f5ee 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -20,7 +20,10 @@ import android.app.Notification; import android.app.Notification.Action; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.UserHandle; import android.os.storage.DiskInfo; import android.os.storage.StorageEventListener; @@ -38,6 +41,8 @@ public class StorageNotification extends SystemUI { private static final int NOTIF_ID = 0x53544f52; // STOR + private static final String ACTION_SNOOZE_VOLUME = "com.android.systemui.action.SNOOZE_VOLUME"; + // TODO: delay some notifications to avoid bumpy fast operations // TODO: annoy user when private media is missing @@ -49,6 +54,25 @@ public class StorageNotification extends SystemUI { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { onVolumeStateChangedInternal(vol, oldState, newState); } + + @Override + public void onVolumeMetadataChanged(VolumeInfo vol) { + // Avoid kicking notifications when getting early metadata before + // mounted. If already mounted, we're being kicked because of a + // nickname or init'ed change. + if (vol.getState() == VolumeInfo.STATE_MOUNTED) { + onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); + } + } + }; + + private final BroadcastReceiver mSnoozeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + // TODO: kick this onto background thread + final String volId = intent.getStringExtra(VolumeInfo.EXTRA_VOLUME_ID); + mStorageManager.setVolumeSnoozed(volId, true); + } }; @Override @@ -58,23 +82,26 @@ public class StorageNotification extends SystemUI { mStorageManager = mContext.getSystemService(StorageManager.class); mStorageManager.registerListener(mListener); + mContext.registerReceiver(mSnoozeReceiver, new IntentFilter(ACTION_SNOOZE_VOLUME), + android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS, null); + // Kick current state into place final List<VolumeInfo> vols = mStorageManager.getVolumes(); for (VolumeInfo vol : vols) { - onVolumeStateChangedInternal(vol, vol.state, vol.state); + onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); } } public void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) { // We only care about public volumes - if (vol.type != VolumeInfo.TYPE_PUBLIC) { + if (vol.getType() != VolumeInfo.TYPE_PUBLIC) { return; } Log.d(TAG, vol.toString()); // New state means we tear down any old notifications - mNotificationManager.cancelAsUser(vol.id, NOTIF_ID, UserHandle.ALL); + mNotificationManager.cancelAsUser(vol.getId(), NOTIF_ID, UserHandle.ALL); switch (newState) { case VolumeInfo.STATE_UNMOUNTED: @@ -106,7 +133,7 @@ public class StorageNotification extends SystemUI { } private void onVolumeMounting(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_checking_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -119,13 +146,16 @@ public class StorageNotification extends SystemUI { .setOngoing(true) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeMounted(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + // Don't annoy when user dismissed in past + if (vol.isSnoozed()) return; + + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final Notification notif; - if (disk.isAdoptable()) { + if (disk.isAdoptable() && !vol.isInited()) { final CharSequence title = disk.getDescription(); final CharSequence text = mContext.getString( R.string.ext_media_new_notification_message, disk.getDescription()); @@ -136,6 +166,7 @@ public class StorageNotification extends SystemUI { buildInitPendingIntent(vol))) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .build(); @@ -150,12 +181,13 @@ public class StorageNotification extends SystemUI { buildBrowsePendingIntent(vol))) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .setPriority(Notification.PRIORITY_LOW) .build(); } - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeFormatting(VolumeInfo vol) { @@ -163,7 +195,7 @@ public class StorageNotification extends SystemUI { } private void onVolumeUnmounting(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_unmounting_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -176,11 +208,11 @@ public class StorageNotification extends SystemUI { .setOngoing(true) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeUnmountable(VolumeInfo vol) { - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_unmountable_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -192,7 +224,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_ERROR) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private void onVolumeRemoved(VolumeInfo vol) { @@ -201,7 +233,7 @@ public class StorageNotification extends SystemUI { return; } - final DiskInfo disk = mStorageManager.findDiskById(vol.diskId); + final DiskInfo disk = mStorageManager.findDiskById(vol.getDiskId()); final CharSequence title = mContext.getString( R.string.ext_media_nomedia_notification_title, disk.getDescription()); final CharSequence text = mContext.getString( @@ -212,7 +244,7 @@ public class StorageNotification extends SystemUI { .setCategory(Notification.CATEGORY_ERROR) .build(); - mNotificationManager.notifyAsUser(vol.id, NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); } private Notification.Builder buildNotificationBuilder(CharSequence title, CharSequence text) { @@ -229,28 +261,49 @@ public class StorageNotification extends SystemUI { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.deviceinfo.StorageWizardInit"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } private PendingIntent buildUnmountPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.deviceinfo.StorageUnmountReceiver"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); } private PendingIntent buildBrowsePendingIntent(VolumeInfo vol) { final Intent intent = vol.buildBrowseIntent(); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); } private PendingIntent buildDetailsPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", "com.android.settings.Settings$StorageVolumeSettingsActivity"); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.id); - return PendingIntent.getActivityAsUser(mContext, 0, intent, 0, null, UserHandle.CURRENT); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + + private PendingIntent buildSnoozeIntent(VolumeInfo vol) { + final Intent intent = new Intent(ACTION_SNOOZE_VOLUME); + intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + + final int requestKey = vol.getId().hashCode(); + return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); } } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index a99f387..4c937f7 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -16,6 +16,13 @@ package com.android.server; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readStringAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeStringAttribute; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + import android.Manifest; import android.app.ActivityManagerNative; import android.app.AppOpsManager; @@ -55,9 +62,13 @@ import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; import android.text.TextUtils; import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.DebugUtils; import android.util.Log; import android.util.Slog; +import android.util.Xml; +import libcore.io.IoUtils; import libcore.util.EmptyArray; import libcore.util.HexEncoding; @@ -66,14 +77,21 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IMediaContainerService; import com.android.internal.os.SomeArgs; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; import com.android.server.NativeDaemonConnector.SensitiveArg; import com.android.server.pm.PackageManagerService; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import java.io.File; import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; @@ -214,6 +232,57 @@ class MountService extends IMountService.Stub public static final int FstrimCompleted = 700; } + private static final String TAG_VOLUMES = "volumes"; + private static final String TAG_VOLUME = "volume"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_FS_UUID = "fsUuid"; + private static final String ATTR_NICKNAME = "nickname"; + private static final String ATTR_USER_FLAGS = "userFlags"; + + private final AtomicFile mMetadataFile; + + private static class VolumeMetadata { + public final int type; + public final String fsUuid; + public String nickname; + public int userFlags; + + public VolumeMetadata(int type, String fsUuid) { + this.type = type; + this.fsUuid = Preconditions.checkNotNull(fsUuid); + } + + public static VolumeMetadata read(XmlPullParser in) throws IOException { + final int type = readIntAttribute(in, ATTR_TYPE); + final String fsUuid = readStringAttribute(in, ATTR_FS_UUID); + final VolumeMetadata meta = new VolumeMetadata(type, fsUuid); + meta.nickname = readStringAttribute(in, ATTR_NICKNAME); + meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS); + return meta; + } + + public static void write(XmlSerializer out, VolumeMetadata meta) throws IOException { + out.startTag(null, TAG_VOLUME); + writeIntAttribute(out, ATTR_TYPE, meta.type); + writeStringAttribute(out, ATTR_FS_UUID, meta.fsUuid); + writeStringAttribute(out, ATTR_NICKNAME, meta.nickname); + writeIntAttribute(out, ATTR_USER_FLAGS, meta.userFlags); + out.endTag(null, TAG_VOLUME); + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeMetadata:"); + pw.increaseIndent(); + pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type)); + pw.printPair("fsUuid", fsUuid); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", + DebugUtils.flagsToString(VolumeInfo.class, "USER_FLAG_", userFlags)); + pw.decreaseIndent(); + pw.println(); + } + } + /** * <em>Never</em> hold the lock while performing downcalls into vold, since * unsolicited events can suddenly appear to update data structures. @@ -222,11 +291,18 @@ class MountService extends IMountService.Stub @GuardedBy("mLock") private int[] mStartedUsers = EmptyArray.INT; + + /** Map from disk ID to disk */ @GuardedBy("mLock") private ArrayMap<String, DiskInfo> mDisks = new ArrayMap<>(); + /** Map from volume ID to disk */ @GuardedBy("mLock") private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>(); + /** Map from UUID to metadata */ + @GuardedBy("mLock") + private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>(); + private DiskInfo findDiskById(String id) { synchronized (mLock) { final DiskInfo disk = mDisks.get(id); @@ -260,6 +336,15 @@ class MountService extends IMountService.Stub throw new IllegalArgumentException("No volume found for path " + path); } + private VolumeMetadata findOrCreateMetadataLocked(VolumeInfo vol) { + VolumeMetadata meta = mMetadata.get(vol.fsUuid); + if (meta == null) { + meta = new VolumeMetadata(vol.type, vol.fsUuid); + mMetadata.put(meta.fsUuid, meta); + } + return meta; + } + private static int sNextMtpIndex = 1; private static int allocateMtpIndex(String volId) { @@ -799,6 +884,7 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsType = cooked[2]; } + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_UUID_CHANGED: { @@ -807,6 +893,8 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsUuid = cooked[2]; } + refreshMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: { @@ -815,6 +903,7 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsLabel = cooked[2]; } + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_PATH_CHANGED: { @@ -901,6 +990,25 @@ class MountService extends IMountService.Stub } } + /** + * Refresh latest metadata into any currently active {@link VolumeInfo}. + */ + private void refreshMetadataLocked() { + final int size = mVolumes.size(); + for (int i = 0; i < size; i++) { + final VolumeInfo vol = mVolumes.valueAt(i); + final VolumeMetadata meta = mMetadata.get(vol.fsUuid); + + if (meta != null) { + vol.nickname = meta.nickname; + vol.userFlags = meta.userFlags; + } else { + vol.nickname = null; + vol.userFlags = 0; + } + } + } + private void enforcePermission(String perm) { mContext.enforceCallingOrSelfPermission(perm, perm); } @@ -949,6 +1057,13 @@ class MountService extends IMountService.Stub mLastMaintenance = mLastMaintenanceFile.lastModified(); } + mMetadataFile = new AtomicFile( + new File(Environment.getSystemSecureDirectory(), "storage.xml")); + + synchronized (mLock) { + readMetadataLocked(); + } + /* * Create the connection to vold with a maximum queue of twice the * amount of containers we'd ever expect to have. This keeps an @@ -972,6 +1087,61 @@ class MountService extends IMountService.Stub mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); } + private void readMetadataLocked() { + mMetadata.clear(); + + FileInputStream fis = null; + try { + fis = mMetadataFile.openRead(); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, null); + + int type; + while ((type = in.next()) != END_DOCUMENT) { + if (type == START_TAG) { + final String tag = in.getName(); + if (TAG_VOLUME.equals(tag)) { + final VolumeMetadata meta = VolumeMetadata.read(in); + mMetadata.put(meta.fsUuid, meta); + } + } + } + } catch (FileNotFoundException e) { + // Missing metadata is okay, probably first boot + } catch (IOException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } catch (XmlPullParserException e) { + Slog.wtf(TAG, "Failed reading metadata", e); + } finally { + IoUtils.closeQuietly(fis); + } + } + + private void writeMetadataLocked() { + FileOutputStream fos = null; + try { + fos = mMetadataFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.startTag(null, TAG_VOLUMES); + final int size = mMetadata.size(); + for (int i = 0; i < size; i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + VolumeMetadata.write(out, meta); + } + out.endTag(null, TAG_VOLUMES); + out.endDocument(); + + mMetadataFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mMetadataFile.failWrite(fos); + } + } + } + /** * Exposed API calls below here */ @@ -1126,6 +1296,36 @@ class MountService extends IMountService.Stub } @Override + public void setVolumeNickname(String volId, String nickname) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + waitForReady(); + + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.nickname = nickname; + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + } + } + + @Override + public void setVolumeUserFlags(String volId, int flags, int mask) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + waitForReady(); + + synchronized (mLock) { + final VolumeInfo vol = findVolumeById(volId); + final VolumeMetadata meta = findOrCreateMetadataLocked(vol); + meta.userFlags = (meta.userFlags & ~mask) | (flags & mask); + refreshMetadataLocked(); + writeMetadataLocked(); + mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + } + } + + @Override public int[] getStorageUsers(String path) { enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); waitForReady(); @@ -1909,7 +2109,12 @@ class MountService extends IMountService.Stub } @Override - public VolumeInfo[] getVolumes() { + public VolumeInfo[] getVolumes(int flags) { + if ((flags & StorageManager.FLAG_ALL_METADATA) != 0) { + // TODO: implement support for returning all metadata + throw new UnsupportedOperationException(); + } + synchronized (mLock) { final VolumeInfo[] res = new VolumeInfo[mVolumes.size()]; for (int i = 0; i < mVolumes.size(); i++) { @@ -2422,6 +2627,7 @@ class MountService extends IMountService.Stub private static class Callbacks extends Handler { private static final int MSG_STORAGE_STATE_CHANGED = 1; private static final int MSG_VOLUME_STATE_CHANGED = 2; + private static final int MSG_VOLUME_METADATA_CHANGED = 3; private final RemoteCallbackList<IMountServiceListener> mCallbacks = new RemoteCallbackList<>(); @@ -2465,6 +2671,10 @@ class MountService extends IMountService.Stub callback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3); break; } + case MSG_VOLUME_METADATA_CHANGED: { + callback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + break; + } } } @@ -2483,6 +2693,12 @@ class MountService extends IMountService.Stub args.argi3 = newState; obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } + + private void notifyVolumeMetadataChanged(VolumeInfo vol) { + final SomeArgs args = SomeArgs.obtain(); + args.arg1 = vol; + obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); + } } @Override @@ -2539,6 +2755,15 @@ class MountService extends IMountService.Stub vol.dump(pw); } pw.decreaseIndent(); + + pw.println(); + pw.println("Metadata:"); + pw.increaseIndent(); + for (int i = 0; i < mMetadata.size(); i++) { + final VolumeMetadata meta = mMetadata.valueAt(i); + meta.dump(pw); + } + pw.decreaseIndent(); } pw.println(); |