Merge "Persist nickname and flags for volumes."
This commit is contained in:
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -40,4 +40,7 @@ public class StorageEventListener {
|
||||
|
||||
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
|
||||
}
|
||||
|
||||
public void onVolumeMetadataChanged(VolumeInfo vol) {
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
@ -555,6 +575,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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
@ -1125,6 +1295,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);
|
||||
@ -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();
|
||||
|
Reference in New Issue
Block a user