diff options
author | Jeff Sharkey <jsharkey@android.com> | 2015-04-27 08:42:28 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2015-04-28 22:02:36 -0700 |
commit | b36586a7c9b7718f33961406537e27bbd9b16211 (patch) | |
tree | 19fd58491e0ab44dc0079ed5c62de8215f62741e | |
parent | 30565ed225e6d91c37b438a64f715725b1663058 (diff) | |
download | frameworks_base-b36586a7c9b7718f33961406537e27bbd9b16211.zip frameworks_base-b36586a7c9b7718f33961406537e27bbd9b16211.tar.gz frameworks_base-b36586a7c9b7718f33961406537e27bbd9b16211.tar.bz2 |
Split some VolumeInfo state into VolumeRecord.
VolumeRecord is a historical record of a volume that we've seen in
the past. It's now surfaced outside the framework for SystemUI to
drive the notifications that bug users to reinsert missing private
volumes.
Show progress notifications for both storage and package movement
operations. Notify when an empty disk is inserted (no usable volumes)
which launches into the normal format flow.
Add API to forget volumes.
Bug: 20275424, 20275424
Change-Id: I75602c17fdcd4d1f1f62324e1a08c4a33093eefa
-rw-r--r-- | core/java/android/app/ApplicationPackageManager.java | 27 | ||||
-rw-r--r-- | core/java/android/content/pm/IPackageMoveObserver.aidl | 3 | ||||
-rw-r--r-- | core/java/android/content/pm/PackageManager.java | 4 | ||||
-rw-r--r-- | core/java/android/os/storage/IMountService.java | 84 | ||||
-rw-r--r-- | core/java/android/os/storage/IMountServiceListener.java | 10 | ||||
-rw-r--r-- | core/java/android/os/storage/StorageEventListener.java | 2 | ||||
-rw-r--r-- | core/java/android/os/storage/StorageManager.java | 76 | ||||
-rw-r--r-- | core/java/android/os/storage/VolumeInfo.java | 25 | ||||
-rw-r--r-- | core/java/android/os/storage/VolumeRecord.java | 139 | ||||
-rw-r--r-- | core/res/res/values/strings.xml | 20 | ||||
-rwxr-xr-x | core/res/res/values/symbols.xml | 8 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java | 262 | ||||
-rw-r--r-- | services/core/java/com/android/server/MountService.java | 224 | ||||
-rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 57 |
14 files changed, 664 insertions, 277 deletions
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 16a2430..6e511f3 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -79,6 +79,7 @@ import android.view.Display; import dalvik.system.VMRuntime; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.internal.util.UserIcons; @@ -2054,8 +2055,7 @@ final class ApplicationPackageManager extends PackageManager { /** {@hide} */ private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements Handler.Callback { - private static final int MSG_STARTED = 1; - private static final int MSG_STATUS_CHANGED = 2; + private static final int MSG_STATUS_CHANGED = 1; final MoveCallback mCallback; final Handler mHandler; @@ -2067,26 +2067,25 @@ final class ApplicationPackageManager extends PackageManager { @Override public boolean handleMessage(Message msg) { - final int moveId = msg.arg1; switch (msg.what) { - case MSG_STARTED: - mCallback.onStarted(moveId, (String) msg.obj); - return true; case MSG_STATUS_CHANGED: - mCallback.onStatusChanged(moveId, msg.arg2, (long) msg.obj); + final SomeArgs args = (SomeArgs) msg.obj; + mCallback.onStatusChanged(args.argi1, (String) args.arg2, args.argi3, + (long) args.arg4); + args.recycle(); return true; } return false; } @Override - public void onStarted(int moveId, String title) { - mHandler.obtainMessage(MSG_STARTED, moveId, 0, title).sendToTarget(); - } - - @Override - public void onStatusChanged(int moveId, int status, long estMillis) { - mHandler.obtainMessage(MSG_STATUS_CHANGED, moveId, status, estMillis).sendToTarget(); + public void onStatusChanged(int moveId, String moveTitle, int status, long estMillis) { + final SomeArgs args = SomeArgs.obtain(); + args.argi1 = moveId; + args.arg2 = moveTitle; + args.argi3 = status; + args.arg4 = estMillis; + mHandler.obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); } } diff --git a/core/java/android/content/pm/IPackageMoveObserver.aidl b/core/java/android/content/pm/IPackageMoveObserver.aidl index 50ab3b5..155ed0b 100644 --- a/core/java/android/content/pm/IPackageMoveObserver.aidl +++ b/core/java/android/content/pm/IPackageMoveObserver.aidl @@ -22,6 +22,5 @@ package android.content.pm; * @hide */ oneway interface IPackageMoveObserver { - void onStarted(int moveId, String title); - void onStatusChanged(int moveId, int status, long estMillis); + void onStatusChanged(int moveId, String moveTitle, int status, long estMillis); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f01ca09..a1ee7fc 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -4212,8 +4212,8 @@ public abstract class PackageManager { /** {@hide} */ public static abstract class MoveCallback { - public abstract void onStarted(int moveId, String title); - public abstract void onStatusChanged(int moveId, int status, long estMillis); + public abstract void onStatusChanged(int moveId, String moveTitle, int status, + long estMillis); } /** {@hide} */ diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java index 16e0bf7..fcde3f4 100644 --- a/core/java/android/os/storage/IMountService.java +++ b/core/java/android/os/storage/IMountService.java @@ -942,6 +942,24 @@ public interface IMountService extends IInterface { } @Override + public VolumeRecord[] getVolumeRecords(int _flags) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + VolumeRecord[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(_flags); + mRemote.transact(Stub.TRANSACTION_getVolumeRecords, _data, _reply, 0); + _reply.readException(); + _result = _reply.createTypedArray(VolumeRecord.CREATOR); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + @Override public void mount(String volId) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -1033,12 +1051,12 @@ public interface IMountService extends IInterface { } @Override - public void setVolumeNickname(String volId, String nickname) throws RemoteException { + public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(volId); + _data.writeString(fsUuid); _data.writeString(nickname); mRemote.transact(Stub.TRANSACTION_setVolumeNickname, _data, _reply, 0); _reply.readException(); @@ -1049,12 +1067,12 @@ public interface IMountService extends IInterface { } @Override - public void setVolumeUserFlags(String volId, int flags, int mask) throws RemoteException { + public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeString(volId); + _data.writeString(fsUuid); _data.writeInt(flags); _data.writeInt(mask); mRemote.transact(Stub.TRANSACTION_setVolumeUserFlags, _data, _reply, 0); @@ -1066,6 +1084,21 @@ public interface IMountService extends IInterface { } @Override + public void forgetVolume(String fsUuid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(fsUuid); + mRemote.transact(Stub.TRANSACTION_forgetVolume, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + @Override public String getPrimaryStorageUuid() throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); @@ -1192,20 +1225,22 @@ public interface IMountService extends IInterface { static final int TRANSACTION_getDisks = IBinder.FIRST_CALL_TRANSACTION + 44; static final int TRANSACTION_getVolumes = IBinder.FIRST_CALL_TRANSACTION + 45; + static final int TRANSACTION_getVolumeRecords = IBinder.FIRST_CALL_TRANSACTION + 46; - static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 46; - static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 47; - static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 48; + static final int TRANSACTION_mount = IBinder.FIRST_CALL_TRANSACTION + 47; + static final int TRANSACTION_unmount = IBinder.FIRST_CALL_TRANSACTION + 48; + static final int TRANSACTION_format = IBinder.FIRST_CALL_TRANSACTION + 49; - static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 49; - static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 50; - static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_partitionPublic = IBinder.FIRST_CALL_TRANSACTION + 50; + static final int TRANSACTION_partitionPrivate = IBinder.FIRST_CALL_TRANSACTION + 51; + static final int TRANSACTION_partitionMixed = IBinder.FIRST_CALL_TRANSACTION + 52; - static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 52; - static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 53; + static final int TRANSACTION_setVolumeNickname = IBinder.FIRST_CALL_TRANSACTION + 53; + static final int TRANSACTION_setVolumeUserFlags = IBinder.FIRST_CALL_TRANSACTION + 54; + static final int TRANSACTION_forgetVolume = IBinder.FIRST_CALL_TRANSACTION + 55; - static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 54; - static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 55; + static final int TRANSACTION_getPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 56; + static final int TRANSACTION_setPrimaryStorageUuid = IBinder.FIRST_CALL_TRANSACTION + 57; /** * Cast an IBinder object into an IMountService interface, generating a @@ -1647,6 +1682,14 @@ public interface IMountService extends IInterface { reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); return true; } + case TRANSACTION_getVolumeRecords: { + data.enforceInterface(DESCRIPTOR); + int _flags = data.readInt(); + VolumeRecord[] volumes = getVolumeRecords(_flags); + reply.writeNoException(); + reply.writeTypedArray(volumes, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } case TRANSACTION_mount: { data.enforceInterface(DESCRIPTOR); String volId = data.readString(); @@ -1707,6 +1750,13 @@ public interface IMountService extends IInterface { reply.writeNoException(); return true; } + case TRANSACTION_forgetVolume: { + data.enforceInterface(DESCRIPTOR); + String fsUuid = data.readString(); + forgetVolume(fsUuid); + reply.writeNoException(); + return true; + } case TRANSACTION_getPrimaryStorageUuid: { data.enforceInterface(DESCRIPTOR); String volumeUuid = getPrimaryStorageUuid(); @@ -2012,6 +2062,7 @@ public interface IMountService extends IInterface { public DiskInfo[] getDisks() throws RemoteException; public VolumeInfo[] getVolumes(int flags) throws RemoteException; + public VolumeRecord[] getVolumeRecords(int flags) throws RemoteException; public void mount(String volId) throws RemoteException; public void unmount(String volId) throws RemoteException; @@ -2021,8 +2072,9 @@ public interface IMountService extends IInterface { 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; + public void setVolumeNickname(String fsUuid, String nickname) throws RemoteException; + public void setVolumeUserFlags(String fsUuid, int flags, int mask) throws RemoteException; + public void forgetVolume(String fsUuid) throws RemoteException; public String getPrimaryStorageUuid() throws RemoteException; public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java index fcb4779..2d13e49 100644 --- a/core/java/android/os/storage/IMountServiceListener.java +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -93,8 +93,8 @@ public interface IMountServiceListener extends IInterface { } case TRANSACTION_onVolumeMetadataChanged: { data.enforceInterface(DESCRIPTOR); - final VolumeInfo vol = (VolumeInfo) data.readParcelable(null); - onVolumeMetadataChanged(vol); + final String fsUuid = data.readString(); + onVolumeMetadataChanged(fsUuid); reply.writeNoException(); return true; } @@ -192,12 +192,12 @@ public interface IMountServiceListener extends IInterface { } @Override - public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException { + public void onVolumeMetadataChanged(String fsUuid) throws RemoteException { Parcel _data = Parcel.obtain(); Parcel _reply = Parcel.obtain(); try { _data.writeInterfaceToken(DESCRIPTOR); - _data.writeParcelable(vol, 0); + _data.writeString(fsUuid); mRemote.transact(Stub.TRANSACTION_onVolumeMetadataChanged, _data, _reply, android.os.IBinder.FLAG_ONEWAY); _reply.readException(); @@ -253,7 +253,7 @@ public interface IMountServiceListener extends IInterface { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) throws RemoteException; - public void onVolumeMetadataChanged(VolumeInfo vol) throws RemoteException; + public void onVolumeMetadataChanged(String fsUuid) throws RemoteException; public void onDiskScanned(DiskInfo disk, int volumeCount) throws RemoteException; } diff --git a/core/java/android/os/storage/StorageEventListener.java b/core/java/android/os/storage/StorageEventListener.java index 6a0140e..536aca9 100644 --- a/core/java/android/os/storage/StorageEventListener.java +++ b/core/java/android/os/storage/StorageEventListener.java @@ -41,7 +41,7 @@ public class StorageEventListener { public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { } - public void onVolumeMetadataChanged(VolumeInfo vol) { + public void onVolumeMetadataChanged(String fsUuid) { } public void onDiskScanned(DiskInfo disk, int volumeCount) { diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 6116aef..271ed9d 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -79,9 +79,6 @@ public class StorageManager { /** {@hide} */ public static final String UUID_PRIMARY_PHYSICAL = "primary_physical"; - /** {@hide} */ - public static final int FLAG_ALL_METADATA = 1 << 0; - private final Context mContext; private final ContentResolver mResolver; @@ -120,7 +117,7 @@ public class StorageManager { args.recycle(); return true; case MSG_VOLUME_METADATA_CHANGED: - mCallback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + mCallback.onVolumeMetadataChanged((String) args.arg1); args.recycle(); return true; case MSG_DISK_SCANNED: @@ -156,9 +153,9 @@ public class StorageManager { } @Override - public void onVolumeMetadataChanged(VolumeInfo vol) { + public void onVolumeMetadataChanged(String fsUuid) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = vol; + args.arg1 = fsUuid; mHandler.obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); } @@ -516,6 +513,18 @@ public class StorageManager { } /** {@hide} */ + public @Nullable VolumeRecord findRecordByUuid(String fsUuid) { + Preconditions.checkNotNull(fsUuid); + // TODO; go directly to service to make this faster + for (VolumeRecord rec : getVolumeRecords()) { + if (Objects.equals(rec.fsUuid, fsUuid)) { + return rec; + } + } + return null; + } + + /** {@hide} */ public @Nullable VolumeInfo findPrivateForEmulated(VolumeInfo emulatedVol) { return findVolumeById(emulatedVol.getId().replace("emulated", "private")); } @@ -527,13 +536,17 @@ public class StorageManager { /** {@hide} */ public @NonNull List<VolumeInfo> getVolumes() { - return getVolumes(0); + try { + return Arrays.asList(mMountService.getVolumes(0)); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } } /** {@hide} */ - public @NonNull List<VolumeInfo> getVolumes(int flags) { + public @NonNull List<VolumeRecord> getVolumeRecords() { try { - return Arrays.asList(mMountService.getVolumes(flags)); + return Arrays.asList(mMountService.getVolumeRecords(0)); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } @@ -541,13 +554,23 @@ public class StorageManager { /** {@hide} */ public @Nullable String getBestVolumeDescription(VolumeInfo vol) { - String descrip = vol.getDescription(); - if (vol.disk != null) { - if (TextUtils.isEmpty(descrip)) { - descrip = vol.disk.getDescription(); + // Nickname always takes precedence when defined + if (!TextUtils.isEmpty(vol.fsUuid)) { + final VolumeRecord rec = findRecordByUuid(vol.fsUuid); + if (!TextUtils.isEmpty(rec.nickname)) { + return rec.nickname; } } - return descrip; + + if (!TextUtils.isEmpty(vol.getDescription())) { + return vol.getDescription(); + } + + if (vol.disk != null) { + return vol.disk.getDescription(); + } + + return null; } /** {@hide} */ @@ -616,29 +639,38 @@ public class StorageManager { } /** {@hide} */ - public void setVolumeNickname(String volId, String nickname) { + public void setVolumeNickname(String fsUuid, String nickname) { + try { + mMountService.setVolumeNickname(fsUuid, nickname); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } + } + + /** {@hide} */ + public void setVolumeInited(String fsUuid, boolean inited) { try { - mMountService.setVolumeNickname(volId, nickname); + mMountService.setVolumeUserFlags(fsUuid, inited ? VolumeRecord.USER_FLAG_INITED : 0, + VolumeRecord.USER_FLAG_INITED); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** {@hide} */ - public void setVolumeInited(String volId, boolean inited) { + public void setVolumeSnoozed(String fsUuid, boolean snoozed) { try { - mMountService.setVolumeUserFlags(volId, inited ? VolumeInfo.USER_FLAG_INITED : 0, - VolumeInfo.USER_FLAG_INITED); + mMountService.setVolumeUserFlags(fsUuid, snoozed ? VolumeRecord.USER_FLAG_SNOOZED : 0, + VolumeRecord.USER_FLAG_SNOOZED); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } } /** {@hide} */ - public void setVolumeSnoozed(String volId, boolean snoozed) { + public void forgetVolume(String fsUuid) { try { - mMountService.setVolumeUserFlags(volId, snoozed ? VolumeInfo.USER_FLAG_SNOOZED : 0, - VolumeInfo.USER_FLAG_SNOOZED); + mMountService.forgetVolume(fsUuid); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/os/storage/VolumeInfo.java b/core/java/android/os/storage/VolumeInfo.java index 4e9cfc7..fd10cae 100644 --- a/core/java/android/os/storage/VolumeInfo.java +++ b/core/java/android/os/storage/VolumeInfo.java @@ -78,9 +78,6 @@ public class VolumeInfo implements Parcelable { public static final int MOUNT_FLAG_PRIMARY = 1 << 0; public static final int MOUNT_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<>(); @@ -135,8 +132,6 @@ public class VolumeInfo implements Parcelable { /** Framework state */ public final int mtpIndex; - public String nickname; - public int userFlags = 0; public VolumeInfo(String id, int type, DiskInfo disk, int mtpIndex) { this.id = Preconditions.checkNotNull(id); @@ -161,8 +156,6 @@ public class VolumeInfo implements Parcelable { fsLabel = parcel.readString(); path = parcel.readString(); mtpIndex = parcel.readInt(); - nickname = parcel.readString(); - userFlags = parcel.readInt(); } public static @NonNull String getEnvironmentForState(int state) { @@ -210,10 +203,6 @@ public class VolumeInfo implements Parcelable { return fsUuid; } - public @Nullable String getNickname() { - return nickname; - } - public int getMountUserId() { return mountUserId; } @@ -221,8 +210,6 @@ public class VolumeInfo implements Parcelable { public @Nullable String getDescription() { if (ID_PRIVATE_INTERNAL.equals(id)) { return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); - } else if (!TextUtils.isEmpty(nickname)) { - return nickname; } else if (!TextUtils.isEmpty(fsLabel)) { return fsLabel; } else { @@ -250,14 +237,6 @@ public class VolumeInfo implements Parcelable { return (mountFlags & MOUNT_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.mountUserId) { return isVisible(); @@ -394,8 +373,6 @@ public class VolumeInfo implements Parcelable { pw.println(); pw.printPair("path", path); pw.printPair("mtpIndex", mtpIndex); - pw.printPair("nickname", nickname); - pw.printPair("userFlags", DebugUtils.flagsToString(getClass(), "USER_FLAG_", userFlags)); pw.decreaseIndent(); pw.println(); } @@ -461,7 +438,5 @@ public class VolumeInfo implements Parcelable { parcel.writeString(fsLabel); parcel.writeString(path); parcel.writeInt(mtpIndex); - parcel.writeString(nickname); - parcel.writeInt(userFlags); } } diff --git a/core/java/android/os/storage/VolumeRecord.java b/core/java/android/os/storage/VolumeRecord.java new file mode 100644 index 0000000..d12d150 --- /dev/null +++ b/core/java/android/os/storage/VolumeRecord.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os.storage; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.DebugUtils; + +import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.Preconditions; + +import java.util.Objects; + +/** + * Notes for a storage volume which may not be currently present. + * + * @hide + */ +public class VolumeRecord implements Parcelable { + public static final String EXTRA_FS_UUID = + "android.os.storage.extra.FS_UUID"; + + public static final int USER_FLAG_INITED = 1 << 0; + public static final int USER_FLAG_SNOOZED = 1 << 1; + + public final int type; + public final String fsUuid; + public String nickname; + public int userFlags; + + public VolumeRecord(int type, String fsUuid) { + this.type = type; + this.fsUuid = Preconditions.checkNotNull(fsUuid); + } + + public VolumeRecord(Parcel parcel) { + type = parcel.readInt(); + fsUuid = parcel.readString(); + nickname = parcel.readString(); + userFlags = parcel.readInt(); + } + + public int getType() { + return type; + } + + public String getFsUuid() { + return fsUuid; + } + + public String getNickname() { + return nickname; + } + + public boolean isInited() { + return (userFlags & USER_FLAG_INITED) != 0; + } + + public boolean isSnoozed() { + return (userFlags & USER_FLAG_SNOOZED) != 0; + } + + public void dump(IndentingPrintWriter pw) { + pw.println("VolumeRecord:"); + pw.increaseIndent(); + pw.printPair("type", DebugUtils.valueToString(VolumeInfo.class, "TYPE_", type)); + pw.printPair("fsUuid", fsUuid); + pw.printPair("nickname", nickname); + pw.printPair("userFlags", + DebugUtils.flagsToString(VolumeRecord.class, "USER_FLAG_", userFlags)); + pw.decreaseIndent(); + pw.println(); + } + + @Override + public VolumeRecord clone() { + final Parcel temp = Parcel.obtain(); + try { + writeToParcel(temp, 0); + temp.setDataPosition(0); + return CREATOR.createFromParcel(temp); + } finally { + temp.recycle(); + } + } + + @Override + public boolean equals(Object o) { + if (o instanceof VolumeRecord) { + return Objects.equals(fsUuid, ((VolumeRecord) o).fsUuid); + } else { + return false; + } + } + + @Override + public int hashCode() { + return fsUuid.hashCode(); + } + + public static final Creator<VolumeRecord> CREATOR = new Creator<VolumeRecord>() { + @Override + public VolumeRecord createFromParcel(Parcel in) { + return new VolumeRecord(in); + } + + @Override + public VolumeRecord[] newArray(int size) { + return new VolumeRecord[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel parcel, int flags) { + parcel.writeInt(type); + parcel.writeString(fsUuid); + parcel.writeString(nickname); + parcel.writeInt(userFlags); + } +} diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 51c2062..3146f41 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3012,6 +3012,26 @@ <!-- Notification action to browse external media [CHAR LIMIT=20] --> <string name="ext_media_browse_action">Explore</string> + <!-- Notification title when external media is missing [CHAR LIMIT=30] --> + <string name="ext_media_missing_title"><xliff:g id="name" example="SD card">%s</xliff:g> missing</string> + <!-- Notification body when external media is missing [CHAR LIMIT=30] --> + <string name="ext_media_missing_message">Reinsert this device</string> + + <!-- Notification title when moving an application to external storage [CHAR LIMIT=30] --> + <string name="ext_media_move_specific_title">Moving <xliff:g id="name" example="Calculator">%s</xliff:g></string> + <!-- Notification title when moving data to external storage [CHAR LIMIT=32] --> + <string name="ext_media_move_title">Moving data</string> + + <!-- Notification title when moving data to external storage [CHAR LIMIT=32] --> + <string name="ext_media_move_success_title">Move complete</string> + <!-- Notification title when moving data to external storage [CHAR LIMIT=64] --> + <string name="ext_media_move_success_message">Data moved to <xliff:g id="name" example="SD card">%s</xliff:g></string> + + <!-- Notification title when moving data to external storage failed [CHAR LIMIT=32] --> + <string name="ext_media_move_failure_title">Couldn\'t move data</string> + <!-- Notification title when moving data to external storage failed [CHAR LIMIT=64] --> + <string name="ext_media_move_failure_message">Data left at original location</string> + <!-- Shown in LauncherActivity when the requested target Intent didn't return any matching Activities, leaving the list empty. --> <string name="activity_list_empty">No matching activities found.</string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b05fe07..90437b9 100755 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1899,6 +1899,14 @@ <java-symbol type="string" name="ext_media_init_action" /> <java-symbol type="string" name="ext_media_unmount_action" /> <java-symbol type="string" name="ext_media_browse_action" /> + <java-symbol type="string" name="ext_media_missing_title" /> + <java-symbol type="string" name="ext_media_missing_message" /> + <java-symbol type="string" name="ext_media_move_specific_title" /> + <java-symbol type="string" name="ext_media_move_title" /> + <java-symbol type="string" name="ext_media_move_success_title" /> + <java-symbol type="string" name="ext_media_move_success_message" /> + <java-symbol type="string" name="ext_media_move_failure_title" /> + <java-symbol type="string" name="ext_media_move_failure_message" /> <java-symbol type="string" name="usb_storage_error_message" /> <java-symbol type="string" name="usb_storage_message" /> <java-symbol type="string" name="usb_storage_notification_message" /> diff --git a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java index 240c210..deed895 100644 --- a/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java +++ b/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java @@ -24,11 +24,17 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.MoveCallback; +import android.os.Handler; import android.os.UserHandle; import android.os.storage.DiskInfo; import android.os.storage.StorageEventListener; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; +import android.text.TextUtils; +import android.text.format.DateUtils; import android.util.Log; import com.android.internal.R; @@ -39,12 +45,14 @@ import java.util.List; public class StorageNotification extends SystemUI { private static final String TAG = "StorageNotification"; - private static final int NOTIF_ID = 0x53544f52; // STOR + private static final int PUBLIC_ID = 0x53505542; // SPUB + private static final int PRIVATE_ID = 0x53505256; // SPRV + private static final int DISK_ID = 0x5344534b; // SDSK + private static final int MOVE_ID = 0x534d4f56; // SMOV 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 private NotificationManager mNotificationManager; private StorageManager mStorageManager; @@ -52,17 +60,29 @@ public class StorageNotification extends SystemUI { private final StorageEventListener mListener = new StorageEventListener() { @Override public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { - onVolumeStateChangedInternal(vol, oldState, newState); + onVolumeStateChangedInternal(vol); } @Override - public void onVolumeMetadataChanged(VolumeInfo vol) { + public void onVolumeMetadataChanged(String fsUuid) { // 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.isMountedReadable()) { - onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); + final VolumeInfo vol = mStorageManager.findVolumeByUuid(fsUuid); + if (vol != null && vol.isMountedReadable()) { + onVolumeStateChangedInternal(vol); } + + final VolumeRecord rec = mStorageManager.findRecordByUuid(fsUuid); + if (rec == null) { + // Private volume was probably just forgotten + mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL); + } + } + + @Override + public void onDiskScanned(DiskInfo disk, int volumeCount) { + onDiskScannedInternal(disk, volumeCount); } }; @@ -70,8 +90,19 @@ public class StorageNotification extends SystemUI { @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); + final String fsUuid = intent.getStringExtra(VolumeRecord.EXTRA_FS_UUID); + mStorageManager.setVolumeSnoozed(fsUuid, true); + } + }; + + private final MoveCallback mMoveCallback = new MoveCallback() { + @Override + public void onStatusChanged(int moveId, String moveTitle, int status, long estMillis) { + if (PackageManager.isMoveStatusFinished(status)) { + onMoveFinished(moveId, moveTitle, status); + } else { + onMoveProgress(moveId, moveTitle, status, estMillis); + } } }; @@ -88,20 +119,99 @@ public class StorageNotification extends SystemUI { // Kick current state into place final List<VolumeInfo> vols = mStorageManager.getVolumes(); for (VolumeInfo vol : vols) { - onVolumeStateChangedInternal(vol, vol.getState(), vol.getState()); + onVolumeStateChangedInternal(vol); } + + mContext.getPackageManager().registerMoveCallback(mMoveCallback, new Handler()); + + updateMissingPrivateVolumes(); } - public void onVolumeStateChangedInternal(VolumeInfo vol, int oldState, int newState) { - // We only care about public volumes - if (vol.getType() != VolumeInfo.TYPE_PUBLIC) { - return; + private void updateMissingPrivateVolumes() { + final List<VolumeRecord> recs = mStorageManager.getVolumeRecords(); + for (VolumeRecord rec : recs) { + if (rec.getType() != VolumeInfo.TYPE_PRIVATE) continue; + + final String fsUuid = rec.getFsUuid(); + final VolumeInfo info = mStorageManager.findVolumeByUuid(fsUuid); + if (info != null && info.isMountedWritable()) { + // Yay, private volume is here! + mNotificationManager.cancelAsUser(fsUuid, PRIVATE_ID, UserHandle.ALL); + + } else { + // Boo, annoy the user to reinsert the private volume + final CharSequence title = mContext.getString(R.string.ext_media_missing_title, + rec.getNickname()); + final CharSequence text = mContext.getString(R.string.ext_media_missing_message); + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setContentIntent(buildForgetPendingIntent(rec)) + .setCategory(Notification.CATEGORY_SYSTEM) + .setOngoing(true) + .build(); + + mNotificationManager.notifyAsUser(fsUuid, PRIVATE_ID, notif, UserHandle.ALL); + } + } + } + + private void onDiskScannedInternal(DiskInfo disk, int volumeCount) { + if (volumeCount == 0) { + // No supported volumes found, give user option to format + final CharSequence title = mContext.getString( + R.string.ext_media_unmountable_notification_title, disk.getDescription()); + final CharSequence text = mContext.getString( + R.string.ext_media_unmountable_notification_message, disk.getDescription()); + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE)) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setContentIntent(buildInitPendingIntent(disk)) + .setCategory(Notification.CATEGORY_ERROR) + .build(); + + mNotificationManager.notifyAsUser(disk.getId(), DISK_ID, notif, UserHandle.ALL); + + } else { + // Yay, we have volumes! + mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL); } + } - Log.d(TAG, vol.toString()); + private void onVolumeStateChangedInternal(VolumeInfo vol) { + switch (vol.getType()) { + case VolumeInfo.TYPE_PRIVATE: + onPrivateVolumeStateChangedInternal(vol); + break; + case VolumeInfo.TYPE_PUBLIC: + onPublicVolumeStateChangedInternal(vol); + break; + } + } + + private void onPrivateVolumeStateChangedInternal(VolumeInfo vol) { + Log.d(TAG, "Notifying about private volume: " + vol.toString()); + + updateMissingPrivateVolumes(); + } + + private void onPublicVolumeStateChangedInternal(VolumeInfo vol) { + Log.d(TAG, "Notifying about public volume: " + vol.toString()); final Notification notif; - switch (newState) { + switch (vol.getState()) { case VolumeInfo.STATE_UNMOUNTED: notif = onVolumeUnmounted(vol); break; @@ -133,9 +243,9 @@ public class StorageNotification extends SystemUI { } if (notif != null) { - mNotificationManager.notifyAsUser(vol.getId(), NOTIF_ID, notif, UserHandle.ALL); + mNotificationManager.notifyAsUser(vol.getId(), PUBLIC_ID, notif, UserHandle.ALL); } else { - mNotificationManager.cancelAsUser(vol.getId(), NOTIF_ID, UserHandle.ALL); + mNotificationManager.cancelAsUser(vol.getId(), PUBLIC_ID, UserHandle.ALL); } } @@ -159,20 +269,24 @@ public class StorageNotification extends SystemUI { } private Notification onVolumeMounted(VolumeInfo vol) { + final VolumeRecord rec = mStorageManager.findRecordByUuid(vol.getFsUuid()); + // Don't annoy when user dismissed in past - if (vol.isSnoozed()) return null; + if (rec.isSnoozed()) return null; final DiskInfo disk = vol.getDisk(); - if (disk.isAdoptable() && !vol.isInited()) { + if (disk.isAdoptable() && !rec.isInited()) { final CharSequence title = disk.getDescription(); final CharSequence text = mContext.getString( R.string.ext_media_new_notification_message, disk.getDescription()); + final PendingIntent initAction = buildInitPendingIntent(vol); return buildNotificationBuilder(vol, title, text) .addAction(new Action(0, mContext.getString(R.string.ext_media_init_action), - buildInitPendingIntent(vol))) + initAction)) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setContentIntent(initAction) .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .build(); @@ -182,11 +296,13 @@ public class StorageNotification extends SystemUI { final CharSequence text = mContext.getString( R.string.ext_media_ready_notification_message, disk.getDescription()); + final PendingIntent browseAction = buildBrowsePendingIntent(vol); return buildNotificationBuilder(vol, title, text) .addAction(new Action(0, mContext.getString(R.string.ext_media_browse_action), - buildBrowsePendingIntent(vol))) + browseAction)) .addAction(new Action(0, mContext.getString(R.string.ext_media_unmount_action), buildUnmountPendingIntent(vol))) + .setContentIntent(browseAction) .setDeleteIntent(buildSnoozeIntent(vol)) .setCategory(Notification.CATEGORY_SYSTEM) .setPriority(Notification.PRIORITY_LOW) @@ -260,16 +376,84 @@ public class StorageNotification extends SystemUI { .build(); } - private int getSmallIcon(VolumeInfo vol) { - if (vol.disk.isSd()) { - switch (vol.getState()) { + private void onMoveProgress(int moveId, String moveTitle, int status, long estMillis) { + final CharSequence title; + if (!TextUtils.isEmpty(moveTitle)) { + title = mContext.getString(R.string.ext_media_move_specific_title, moveTitle); + } else { + title = mContext.getString(R.string.ext_media_move_title); + } + + final CharSequence text; + if (estMillis < 0) { + text = null; + } else { + text = DateUtils.formatDuration(estMillis); + } + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_PROGRESS) + .setPriority(Notification.PRIORITY_LOW) + .setProgress(100, status, false) + .setOngoing(true) + .build(); + + mNotificationManager.notifyAsUser(moveTitle, MOVE_ID, notif, UserHandle.ALL); + } + + private void onMoveFinished(int moveId, String moveTitle, int status) { + if (!TextUtils.isEmpty(moveTitle)) { + // We currently ignore finished app moves; just clear the last + // published progress + mNotificationManager.cancelAsUser(moveTitle, MOVE_ID, UserHandle.ALL); + return; + } + + final VolumeInfo vol = mContext.getPackageManager().getPrimaryStorageCurrentVolume(); + final String descrip = mStorageManager.getBestVolumeDescription(vol); + + final CharSequence title; + final CharSequence text; + if (status == PackageManager.MOVE_SUCCEEDED) { + title = mContext.getString(R.string.ext_media_move_success_title); + text = mContext.getString(R.string.ext_media_move_success_message, descrip); + } else { + title = mContext.getString(R.string.ext_media_move_failure_title); + text = mContext.getString(R.string.ext_media_move_failure_message); + } + + final Notification notif = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.stat_notify_sdcard) + .setColor(mContext.getColor(R.color.system_notification_accent_color)) + .setContentTitle(title) + .setContentText(text) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setCategory(Notification.CATEGORY_SYSTEM) + .setPriority(Notification.PRIORITY_LOW) + .build(); + + mNotificationManager.notifyAsUser(moveTitle, MOVE_ID, notif, UserHandle.ALL); + } + + private int getSmallIcon(DiskInfo disk, int state) { + if (disk.isSd()) { + switch (state) { case VolumeInfo.STATE_CHECKING: case VolumeInfo.STATE_EJECTING: return R.drawable.stat_notify_sdcard_prepare; default: return R.drawable.stat_notify_sdcard; } - } else if (vol.disk.isUsb()) { + } else if (disk.isUsb()) { return R.drawable.stat_sys_data_usb; } else { return R.drawable.stat_notify_sdcard; @@ -279,7 +463,7 @@ public class StorageNotification extends SystemUI { private Notification.Builder buildNotificationBuilder(VolumeInfo vol, CharSequence title, CharSequence text) { return new Notification.Builder(mContext) - .setSmallIcon(getSmallIcon(vol)) + .setSmallIcon(getSmallIcon(vol.getDisk(), vol.getState())) .setColor(mContext.getColor(R.color.system_notification_accent_color)) .setContentTitle(title) .setContentText(text) @@ -288,6 +472,17 @@ public class StorageNotification extends SystemUI { .setLocalOnly(true); } + private PendingIntent buildInitPendingIntent(DiskInfo disk) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.deviceinfo.StorageWizardInit"); + intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId()); + + final int requestKey = disk.getId().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } + private PendingIntent buildInitPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", @@ -321,7 +516,7 @@ public class StorageNotification extends SystemUI { private PendingIntent buildDetailsPendingIntent(VolumeInfo vol) { final Intent intent = new Intent(); intent.setClassName("com.android.settings", - "com.android.settings.Settings$StorageVolumeSettingsActivity"); + "com.android.settings.Settings$PublicVolumeSettingsActivity"); intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); final int requestKey = vol.getId().hashCode(); @@ -331,10 +526,21 @@ public class StorageNotification extends SystemUI { private PendingIntent buildSnoozeIntent(VolumeInfo vol) { final Intent intent = new Intent(ACTION_SNOOZE_VOLUME); - intent.putExtra(VolumeInfo.EXTRA_VOLUME_ID, vol.getId()); + intent.putExtra(VolumeRecord.EXTRA_FS_UUID, vol.getFsUuid()); final int requestKey = vol.getId().hashCode(); return PendingIntent.getBroadcastAsUser(mContext, requestKey, intent, PendingIntent.FLAG_CANCEL_CURRENT, UserHandle.CURRENT); } + + private PendingIntent buildForgetPendingIntent(VolumeRecord rec) { + final Intent intent = new Intent(); + intent.setClassName("com.android.settings", + "com.android.settings.Settings$PrivateVolumeForgetActivity"); + intent.putExtra(VolumeRecord.EXTRA_FS_UUID, rec.getFsUuid()); + + final int requestKey = rec.getFsUuid().hashCode(); + return PendingIntent.getActivityAsUser(mContext, requestKey, intent, + PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT); + } } diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java index 643ff5f..74adeb7 100644 --- a/services/core/java/com/android/server/MountService.java +++ b/services/core/java/com/android/server/MountService.java @@ -62,11 +62,11 @@ import android.os.storage.StorageManager; import android.os.storage.StorageResultCode; import android.os.storage.StorageVolume; import android.os.storage.VolumeInfo; +import android.os.storage.VolumeRecord; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; import android.util.AtomicFile; -import android.util.DebugUtils; import android.util.Log; import android.util.Slog; import android.util.Xml; @@ -251,49 +251,7 @@ class MountService extends IMountService.Stub 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(); - } - } + private final AtomicFile mSettingsFile; /** * <em>Never</em> hold the lock while performing downcalls into vold, since @@ -311,9 +269,9 @@ class MountService extends IMountService.Stub @GuardedBy("mLock") private ArrayMap<String, VolumeInfo> mVolumes = new ArrayMap<>(); - /** Map from UUID to metadata */ + /** Map from UUID to record */ @GuardedBy("mLock") - private ArrayMap<String, VolumeMetadata> mMetadata = new ArrayMap<>(); + private ArrayMap<String, VolumeRecord> mRecords = new ArrayMap<>(); @GuardedBy("mLock") private String mPrimaryStorageUuid; @@ -370,15 +328,6 @@ class MountService extends IMountService.Stub } } - 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 CountDownLatch findOrCreateDiskScanLatch(String diskId) { synchronized (mLock) { CountDownLatch latch = mDiskScanLatches.get(diskId); @@ -913,7 +862,7 @@ class MountService extends IMountService.Stub final int oldState = vol.state; final int newState = Integer.parseInt(cooked[2]); vol.state = newState; - onVolumeStateChangedLocked(vol.clone(), oldState, newState); + onVolumeStateChangedLocked(vol, oldState, newState); } break; } @@ -923,7 +872,6 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsType = cooked[2]; } - mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_UUID_CHANGED: { @@ -932,8 +880,6 @@ class MountService extends IMountService.Stub if (vol != null) { vol.fsUuid = cooked[2]; } - refreshMetadataLocked(); - mCallbacks.notifyVolumeMetadataChanged(vol.clone()); break; } case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: { @@ -945,7 +891,7 @@ class MountService extends IMountService.Stub } vol.fsLabel = builder.toString().trim(); } - mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + // TODO: notify listeners that label changed break; } case VoldResponseCode.VOLUME_PATH_CHANGED: { @@ -1070,6 +1016,19 @@ class MountService extends IMountService.Stub } private void onVolumeStateChangedLocked(VolumeInfo vol, int oldState, int newState) { + // 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 (vol.isMountedReadable() && !TextUtils.isEmpty(vol.fsUuid)) { + if (!mRecords.containsKey(vol.fsUuid)) { + final VolumeRecord rec = new VolumeRecord(vol.type, vol.fsUuid); + if (vol.type == VolumeInfo.TYPE_PRIVATE) { + rec.nickname = vol.disk.getDescription(); + } + mRecords.put(rec.fsUuid, rec); + writeSettingsLocked(); + } + } + mCallbacks.notifyVolumeStateChanged(vol, oldState, newState); if (isBroadcastWorthy(vol)) { @@ -1117,7 +1076,7 @@ class MountService extends IMountService.Stub // TODO: estimate remaining time try { - mMoveCallback.onStatusChanged(-1, status, -1); + mMoveCallback.onStatusChanged(-1, null, status, -1); } catch (RemoteException ignored) { } @@ -1127,7 +1086,7 @@ class MountService extends IMountService.Stub Slog.d(TAG, "Move to " + mMoveTargetUuid + " copy phase finshed; persisting"); mPrimaryStorageUuid = mMoveTargetUuid; - writeMetadataLocked(); + writeSettingsLocked(); } if (PackageManager.isMoveStatusFinished(status)) { @@ -1138,25 +1097,6 @@ 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); } @@ -1205,11 +1145,11 @@ class MountService extends IMountService.Stub mLastMaintenance = mLastMaintenanceFile.lastModified(); } - mMetadataFile = new AtomicFile( + mSettingsFile = new AtomicFile( new File(Environment.getSystemSecureDirectory(), "storage.xml")); synchronized (mLock) { - readMetadataLocked(); + readSettingsLocked(); } /* @@ -1235,12 +1175,12 @@ class MountService extends IMountService.Stub mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget(); } - private void readMetadataLocked() { - mMetadata.clear(); + private void readSettingsLocked() { + mRecords.clear(); FileInputStream fis = null; try { - fis = mMetadataFile.openRead(); + fis = mSettingsFile.openRead(); final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, null); @@ -1263,8 +1203,8 @@ class MountService extends IMountService.Stub } } else if (TAG_VOLUME.equals(tag)) { - final VolumeMetadata meta = VolumeMetadata.read(in); - mMetadata.put(meta.fsUuid, meta); + final VolumeRecord rec = readVolumeRecord(in); + mRecords.put(rec.fsUuid, rec); } } } @@ -1279,10 +1219,10 @@ class MountService extends IMountService.Stub } } - private void writeMetadataLocked() { + private void writeSettingsLocked() { FileOutputStream fos = null; try { - fos = mMetadataFile.startWrite(); + fos = mSettingsFile.startWrite(); XmlSerializer out = new FastXmlSerializer(); out.setOutput(fos, "utf-8"); @@ -1290,22 +1230,40 @@ class MountService extends IMountService.Stub out.startTag(null, TAG_VOLUMES); writeIntAttribute(out, ATTR_VERSION, VERSION_ADD_PRIMARY); writeStringAttribute(out, ATTR_PRIMARY_STORAGE_UUID, mPrimaryStorageUuid); - final int size = mMetadata.size(); + final int size = mRecords.size(); for (int i = 0; i < size; i++) { - final VolumeMetadata meta = mMetadata.valueAt(i); - VolumeMetadata.write(out, meta); + final VolumeRecord rec = mRecords.valueAt(i); + writeVolumeRecord(out, rec); } out.endTag(null, TAG_VOLUMES); out.endDocument(); - mMetadataFile.finishWrite(fos); + mSettingsFile.finishWrite(fos); } catch (IOException e) { if (fos != null) { - mMetadataFile.failWrite(fos); + mSettingsFile.failWrite(fos); } } } + public static VolumeRecord readVolumeRecord(XmlPullParser in) throws IOException { + final int type = readIntAttribute(in, ATTR_TYPE); + final String fsUuid = readStringAttribute(in, ATTR_FS_UUID); + final VolumeRecord meta = new VolumeRecord(type, fsUuid); + meta.nickname = readStringAttribute(in, ATTR_NICKNAME); + meta.userFlags = readIntAttribute(in, ATTR_USER_FLAGS); + return meta; + } + + public static void writeVolumeRecord(XmlSerializer out, VolumeRecord rec) throws IOException { + out.startTag(null, TAG_VOLUME); + writeIntAttribute(out, ATTR_TYPE, rec.type); + writeStringAttribute(out, ATTR_FS_UUID, rec.fsUuid); + writeStringAttribute(out, ATTR_NICKNAME, rec.nickname); + writeIntAttribute(out, ATTR_USER_FLAGS, rec.userFlags); + out.endTag(null, TAG_VOLUME); + } + /** * Exposed API calls below here */ @@ -1471,32 +1429,40 @@ class MountService extends IMountService.Stub } @Override - public void setVolumeNickname(String volId, String nickname) { + public void setVolumeNickname(String fsUuid, String nickname) { + enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS); + waitForReady(); + + synchronized (mLock) { + final VolumeRecord rec = mRecords.get(fsUuid); + rec.nickname = nickname; + mCallbacks.notifyVolumeMetadataChanged(fsUuid); + writeSettingsLocked(); + } + } + + @Override + public void setVolumeUserFlags(String fsUuid, 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.nickname = nickname; - refreshMetadataLocked(); - writeMetadataLocked(); - mCallbacks.notifyVolumeMetadataChanged(vol.clone()); + final VolumeRecord rec = mRecords.get(fsUuid); + rec.userFlags = (rec.userFlags & ~mask) | (flags & mask); + mCallbacks.notifyVolumeMetadataChanged(fsUuid); + writeSettingsLocked(); } } @Override - public void setVolumeUserFlags(String volId, int flags, int mask) { + public void forgetVolume(String fsUuid) { 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()); + mRecords.remove(fsUuid); + mCallbacks.notifyVolumeMetadataChanged(fsUuid); + writeSettingsLocked(); } } @@ -2329,11 +2295,6 @@ class MountService extends IMountService.Stub @Override 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++) { @@ -2343,6 +2304,17 @@ class MountService extends IMountService.Stub } } + @Override + public VolumeRecord[] getVolumeRecords(int flags) { + synchronized (mLock) { + final VolumeRecord[] res = new VolumeRecord[mRecords.size()]; + for (int i = 0; i < mRecords.size(); i++) { + res[i] = mRecords.valueAt(i); + } + return res; + } + } + private void addObbStateLocked(ObbState obbState) throws RemoteException { final IBinder binder = obbState.getBinder(); List<ObbState> obbStates = mObbMounts.get(binder); @@ -2892,7 +2864,7 @@ class MountService extends IMountService.Stub break; } case MSG_VOLUME_METADATA_CHANGED: { - callback.onVolumeMetadataChanged((VolumeInfo) args.arg1); + callback.onVolumeMetadataChanged((String) args.arg1); break; } case MSG_DISK_SCANNED: { @@ -2912,21 +2884,21 @@ class MountService extends IMountService.Stub private void notifyVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = vol; + args.arg1 = vol.clone(); args.argi2 = oldState; args.argi3 = newState; obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget(); } - private void notifyVolumeMetadataChanged(VolumeInfo vol) { + private void notifyVolumeMetadataChanged(String fsUuid) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = vol; + args.arg1 = fsUuid; obtainMessage(MSG_VOLUME_METADATA_CHANGED, args).sendToTarget(); } private void notifyDiskScanned(DiskInfo disk, int volumeCount) { final SomeArgs args = SomeArgs.obtain(); - args.arg1 = disk; + args.arg1 = disk.clone(); args.argi2 = volumeCount; obtainMessage(MSG_DISK_SCANNED, args).sendToTarget(); } @@ -2937,10 +2909,10 @@ class MountService extends IMountService.Stub mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); for (String arg : args) { - if ("--clear-metadata".equals(arg)) { + if ("--clear".equals(arg)) { synchronized (mLock) { - mMetadata.clear(); - writeMetadataLocked(); + mRecords.clear(); + writeSettingsLocked(); } } } @@ -2966,11 +2938,11 @@ class MountService extends IMountService.Stub pw.decreaseIndent(); pw.println(); - pw.println("Metadata:"); + pw.println("Records:"); pw.increaseIndent(); - for (int i = 0; i < mMetadata.size(); i++) { - final VolumeMetadata meta = mMetadata.valueAt(i); - meta.dump(pw); + for (int i = 0; i < mRecords.size(); i++) { + final VolumeRecord note = mRecords.valueAt(i); + note.dump(pw); } pw.decreaseIndent(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 6c18e25..26f8f70 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -14192,7 +14192,8 @@ public class PackageManagerService extends IPackageManager.Stub { movePackageInternal(packageName, volumeUuid, moveId); } catch (PackageManagerException e) { Slog.d(TAG, "Failed to move " + packageName, e); - mMoveCallbacks.notifyStatusChanged(moveId, PackageManager.MOVE_FAILED_INTERNAL_ERROR); + mMoveCallbacks.notifyStatusChanged(moveId, null, + PackageManager.MOVE_FAILED_INTERNAL_ERROR); } return moveId; } @@ -14209,6 +14210,7 @@ public class PackageManagerService extends IPackageManager.Stub { final String packageAbiOverride; final int appId; final String seinfo; + final String moveTitle; // reader synchronized (mPackages) { @@ -14228,9 +14230,6 @@ public class PackageManagerService extends IPackageManager.Stub { // TODO: yell if already in desired location - mMoveCallbacks.notifyStarted(moveId, - String.valueOf(pm.getApplicationLabel(pkg.applicationInfo))); - pkg.mOperationPending = true; currentAsec = pkg.applicationInfo.isForwardLocked() @@ -14241,6 +14240,7 @@ public class PackageManagerService extends IPackageManager.Stub { packageAbiOverride = ps.cpuAbiOverrideString; appId = UserHandle.getAppId(pkg.applicationInfo.uid); seinfo = pkg.applicationInfo.seinfo; + moveTitle = String.valueOf(pm.getApplicationLabel(pkg.applicationInfo)); } int installFlags; @@ -14268,7 +14268,7 @@ public class PackageManagerService extends IPackageManager.Stub { } Slog.d(TAG, "Moving " + packageName + " from " + currentVolumeUuid + " to " + volumeUuid); - mMoveCallbacks.notifyStatusChanged(moveId, 10, -1); + mMoveCallbacks.notifyStatusChanged(moveId, moveTitle, 10); if (moveData) { synchronized (mInstallLock) { @@ -14288,7 +14288,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - mMoveCallbacks.notifyStatusChanged(moveId, 50); + mMoveCallbacks.notifyStatusChanged(moveId, moveTitle, 50); final IPackageInstallObserver2 installObserver = new IPackageInstallObserver2.Stub() { @Override @@ -14315,15 +14315,15 @@ public class PackageManagerService extends IPackageManager.Stub { final int status = PackageManager.installStatusToPublicStatus(returnCode); switch (status) { case PackageInstaller.STATUS_SUCCESS: - mMoveCallbacks.notifyStatusChanged(moveId, + mMoveCallbacks.notifyStatusChanged(moveId, moveTitle, PackageManager.MOVE_SUCCEEDED); break; case PackageInstaller.STATUS_FAILURE_STORAGE: - mMoveCallbacks.notifyStatusChanged(moveId, + mMoveCallbacks.notifyStatusChanged(moveId, moveTitle, PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE); break; default: - mMoveCallbacks.notifyStatusChanged(moveId, + mMoveCallbacks.notifyStatusChanged(moveId, moveTitle, PackageManager.MOVE_FAILED_INTERNAL_ERROR); break; } @@ -14346,15 +14346,12 @@ public class PackageManagerService extends IPackageManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MOVE_PACKAGE, null); final int realMoveId = mNextMoveId.getAndIncrement(); - final IPackageMoveObserver callback = new IPackageMoveObserver.Stub() { - @Override - public void onStarted(int moveId, String title) { - // Ignored - } + final String realTitle = null; + final IPackageMoveObserver callback = new IPackageMoveObserver.Stub() { @Override - public void onStatusChanged(int moveId, int status, long estMillis) { - mMoveCallbacks.notifyStatusChanged(realMoveId, status, estMillis); + public void onStatusChanged(int moveId, String title, int status, long estMillis) { + mMoveCallbacks.notifyStatusChanged(realMoveId, realTitle, status, estMillis); } }; @@ -14709,7 +14706,6 @@ public class PackageManagerService extends IPackageManager.Stub { } private static class MoveCallbacks extends Handler { - private static final int MSG_STARTED = 1; private static final int MSG_STATUS_CHANGED = 2; private final RemoteCallbackList<IPackageMoveObserver> @@ -14747,37 +14743,26 @@ public class PackageManagerService extends IPackageManager.Stub { private void invokeCallback(IPackageMoveObserver callback, int what, SomeArgs args) throws RemoteException { switch (what) { - case MSG_STARTED: { - callback.onStarted(args.argi1, (String) args.arg2); - break; - } case MSG_STATUS_CHANGED: { - callback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3); + callback.onStatusChanged(args.argi1, (String) args.arg2, args.argi3, + (long) args.arg4); break; } } } - private void notifyStarted(int moveId, String title) { - Slog.v(TAG, "Move " + moveId + " started with title " + title); - - final SomeArgs args = SomeArgs.obtain(); - args.argi1 = moveId; - args.arg2 = title; - obtainMessage(MSG_STARTED, args).sendToTarget(); - } - - private void notifyStatusChanged(int moveId, int status) { - notifyStatusChanged(moveId, status, -1); + private void notifyStatusChanged(int moveId, String moveTitle, int status) { + notifyStatusChanged(moveId, moveTitle, status, -1); } - private void notifyStatusChanged(int moveId, int status, long estMillis) { + private void notifyStatusChanged(int moveId, String moveTitle, int status, long estMillis) { Slog.v(TAG, "Move " + moveId + " status " + status); final SomeArgs args = SomeArgs.obtain(); args.argi1 = moveId; - args.argi2 = status; - args.arg3 = estMillis; + args.arg2 = moveTitle; + args.argi3 = status; + args.arg4 = estMillis; obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget(); synchronized (mLastStatus) { |