blob: d6ec52fac8a5b51687fe38540caafe8c28fc53ad [file] [log] [blame]
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.os.storage;
18
Jeff Sharkey7151a9a2015-04-04 15:22:37 -070019import android.annotation.NonNull;
20import android.annotation.Nullable;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010021import android.annotation.UnsupportedAppUsage;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070022import android.content.Context;
23import android.content.Intent;
Jeff Sharkey59d577a2015-04-11 21:27:21 -070024import android.content.res.Resources;
Jeff Sharkey56bd3122015-04-14 10:30:34 -070025import android.net.Uri;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070026import android.os.Environment;
Jeff Sharkeyace874b2017-09-07 15:27:33 -060027import android.os.IVold;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070028import android.os.Parcel;
29import android.os.Parcelable;
30import android.os.UserHandle;
Jeff Sharkey56bd3122015-04-14 10:30:34 -070031import android.provider.DocumentsContract;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070032import android.text.TextUtils;
33import android.util.ArrayMap;
34import android.util.DebugUtils;
35import android.util.SparseArray;
Jeff Sharkey5fc24732015-06-10 14:21:27 -070036import android.util.SparseIntArray;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070037
Jeff Sharkey5fc24732015-06-10 14:21:27 -070038import com.android.internal.R;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070039import com.android.internal.util.IndentingPrintWriter;
40import com.android.internal.util.Preconditions;
41
Jeff Sharkey7151a9a2015-04-04 15:22:37 -070042import java.io.CharArrayWriter;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070043import java.io.File;
Jeff Sharkeye2d45be2015-04-15 17:14:12 -070044import java.util.Comparator;
Jeff Sharkey3f64ec52019-01-22 13:19:40 -070045import java.util.Locale;
Jeff Sharkeye2d45be2015-04-15 17:14:12 -070046import java.util.Objects;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070047
48/**
49 * Information about a storage volume that may be mounted. A volume may be a
50 * partition on a physical {@link DiskInfo}, an emulated volume above some other
51 * storage medium, or a standalone container like an ASEC or OBB.
Jeff Sharkey46349872015-07-28 10:49:47 -070052 * <p>
53 * Volumes may be mounted with various flags:
54 * <ul>
55 * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external
56 * storage, historically found at {@code /sdcard}.
57 * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party
58 * apps for direct filesystem access. The system should send out relevant
59 * storage broadcasts and index any media on visible volumes. Visible volumes
60 * are considered a more stable part of the device, which is why we take the
61 * time to index them. In particular, transient volumes like USB OTG devices
62 * <em>should not</em> be marked as visible; their contents should be surfaced
63 * to apps through the Storage Access Framework.
64 * </ul>
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070065 *
66 * @hide
67 */
68public class VolumeInfo implements Parcelable {
Jeff Sharkeye6c04f92015-04-18 21:38:05 -070069 public static final String ACTION_VOLUME_STATE_CHANGED =
70 "android.os.storage.action.VOLUME_STATE_CHANGED";
71 public static final String EXTRA_VOLUME_ID =
72 "android.os.storage.extra.VOLUME_ID";
Jeff Sharkeyc7acac62015-06-12 16:16:56 -070073 public static final String EXTRA_VOLUME_STATE =
74 "android.os.storage.extra.VOLUME_STATE";
Jeff Sharkey56bd3122015-04-14 10:30:34 -070075
Jeff Sharkey59d577a2015-04-11 21:27:21 -070076 /** Stub volume representing internal private storage */
77 public static final String ID_PRIVATE_INTERNAL = "private";
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -070078 /** Real volume representing internal emulated storage */
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070079 public static final String ID_EMULATED_INTERNAL = "emulated";
80
Mathew Inwood98e9ad12018-08-30 13:11:50 +010081 @UnsupportedAppUsage
Jeff Sharkey8058fe62017-09-13 11:50:33 -060082 public static final int TYPE_PUBLIC = IVold.VOLUME_TYPE_PUBLIC;
83 public static final int TYPE_PRIVATE = IVold.VOLUME_TYPE_PRIVATE;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010084 @UnsupportedAppUsage
Jeff Sharkey8058fe62017-09-13 11:50:33 -060085 public static final int TYPE_EMULATED = IVold.VOLUME_TYPE_EMULATED;
86 public static final int TYPE_ASEC = IVold.VOLUME_TYPE_ASEC;
87 public static final int TYPE_OBB = IVold.VOLUME_TYPE_OBB;
Risan05c41e62018-10-29 08:57:43 +090088 public static final int TYPE_STUB = IVold.VOLUME_TYPE_STUB;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070089
Jeff Sharkey8058fe62017-09-13 11:50:33 -060090 public static final int STATE_UNMOUNTED = IVold.VOLUME_STATE_UNMOUNTED;
91 public static final int STATE_CHECKING = IVold.VOLUME_STATE_CHECKING;
92 public static final int STATE_MOUNTED = IVold.VOLUME_STATE_MOUNTED;
93 public static final int STATE_MOUNTED_READ_ONLY = IVold.VOLUME_STATE_MOUNTED_READ_ONLY;
94 public static final int STATE_FORMATTING = IVold.VOLUME_STATE_FORMATTING;
95 public static final int STATE_EJECTING = IVold.VOLUME_STATE_EJECTING;
96 public static final int STATE_UNMOUNTABLE = IVold.VOLUME_STATE_UNMOUNTABLE;
97 public static final int STATE_REMOVED = IVold.VOLUME_STATE_REMOVED;
98 public static final int STATE_BAD_REMOVAL = IVold.VOLUME_STATE_BAD_REMOVAL;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070099
Jeff Sharkeyace874b2017-09-07 15:27:33 -0600100 public static final int MOUNT_FLAG_PRIMARY = IVold.MOUNT_FLAG_PRIMARY;
101 public static final int MOUNT_FLAG_VISIBLE = IVold.MOUNT_FLAG_VISIBLE;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700102
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700103 private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
104 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
Jeff Sharkey5fc24732015-06-10 14:21:27 -0700105 private static SparseIntArray sStateToDescrip = new SparseIntArray();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700106
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700107 private static final Comparator<VolumeInfo>
108 sDescriptionComparator = new Comparator<VolumeInfo>() {
109 @Override
110 public int compare(VolumeInfo lhs, VolumeInfo rhs) {
111 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
112 return -1;
113 } else if (lhs.getDescription() == null) {
114 return 1;
115 } else if (rhs.getDescription() == null) {
116 return -1;
117 } else {
118 return lhs.getDescription().compareTo(rhs.getDescription());
119 }
120 }
121 };
122
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700123 static {
124 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700125 sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700126 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700127 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700128 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700129 sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
Jeff Sharkey16c9c242015-04-04 21:37:20 -0700130 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700131 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700132 sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700133
134 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
135 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
136 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700137 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700138 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
Jeff Sharkey16c9c242015-04-04 21:37:20 -0700139 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700140 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700141 sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
Jeff Sharkey5fc24732015-06-10 14:21:27 -0700142
143 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted);
144 sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking);
145 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted);
146 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro);
147 sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting);
148 sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting);
149 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable);
150 sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed);
151 sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700152 }
153
154 /** vold state */
155 public final String id;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100156 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700157 public final int type;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100158 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700159 public final DiskInfo disk;
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700160 public final String partGuid;
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700161 public int mountFlags = 0;
Sudheer Shanka3f0645b2018-09-18 13:07:59 -0700162 public int mountUserId = UserHandle.USER_NULL;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100163 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700164 public int state = STATE_UNMOUNTED;
165 public String fsType;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100166 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700167 public String fsUuid;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100168 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700169 public String fsLabel;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100170 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700171 public String path;
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100172 @UnsupportedAppUsage
Jeff Sharkey50a05452015-04-29 11:24:52 -0700173 public String internalPath;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700174
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700175 public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700176 this.id = Preconditions.checkNotNull(id);
177 this.type = type;
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700178 this.disk = disk;
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700179 this.partGuid = partGuid;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700180 }
181
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100182 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700183 public VolumeInfo(Parcel parcel) {
184 id = parcel.readString();
185 type = parcel.readInt();
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700186 if (parcel.readInt() != 0) {
187 disk = DiskInfo.CREATOR.createFromParcel(parcel);
188 } else {
189 disk = null;
190 }
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700191 partGuid = parcel.readString();
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700192 mountFlags = parcel.readInt();
193 mountUserId = parcel.readInt();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700194 state = parcel.readInt();
195 fsType = parcel.readString();
196 fsUuid = parcel.readString();
197 fsLabel = parcel.readString();
198 path = parcel.readString();
Jeff Sharkey50a05452015-04-29 11:24:52 -0700199 internalPath = parcel.readString();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700200 }
201
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100202 @UnsupportedAppUsage
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700203 public static @NonNull String getEnvironmentForState(int state) {
204 final String envState = sStateToEnvironment.get(state);
205 if (envState != null) {
206 return envState;
207 } else {
208 return Environment.MEDIA_UNKNOWN;
209 }
210 }
211
212 public static @Nullable String getBroadcastForEnvironment(String envState) {
213 return sEnvironmentToBroadcast.get(envState);
214 }
215
216 public static @Nullable String getBroadcastForState(int state) {
217 return getBroadcastForEnvironment(getEnvironmentForState(state));
218 }
219
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700220 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
221 return sDescriptionComparator;
222 }
223
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100224 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700225 public @NonNull String getId() {
226 return id;
227 }
228
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100229 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700230 public @Nullable DiskInfo getDisk() {
231 return disk;
232 }
233
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100234 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700235 public @Nullable String getDiskId() {
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700236 return (disk != null) ? disk.id : null;
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700237 }
238
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100239 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700240 public int getType() {
241 return type;
242 }
243
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100244 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700245 public int getState() {
246 return state;
247 }
248
Jeff Sharkey5fc24732015-06-10 14:21:27 -0700249 public int getStateDescription() {
250 return sStateToDescrip.get(state, 0);
251 }
252
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100253 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700254 public @Nullable String getFsUuid() {
255 return fsUuid;
256 }
257
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700258 public @Nullable String getNormalizedFsUuid() {
259 return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
260 }
261
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100262 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700263 public int getMountUserId() {
264 return mountUserId;
265 }
266
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100267 @UnsupportedAppUsage
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700268 public @Nullable String getDescription() {
Zim17be6f92019-09-25 14:37:55 +0100269 if (ID_PRIVATE_INTERNAL.equals(id) || id.startsWith(ID_EMULATED_INTERNAL + ";")) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700270 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700271 } else if (!TextUtils.isEmpty(fsLabel)) {
272 return fsLabel;
273 } else {
274 return null;
275 }
276 }
277
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100278 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700279 public boolean isMountedReadable() {
280 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
281 }
282
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100283 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700284 public boolean isMountedWritable() {
285 return state == STATE_MOUNTED;
286 }
287
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100288 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700289 public boolean isPrimary() {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700290 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700291 }
292
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100293 @UnsupportedAppUsage
Jeff Sharkey620b32b2015-04-23 19:36:02 -0700294 public boolean isPrimaryPhysical() {
295 return isPrimary() && (getType() == TYPE_PUBLIC);
296 }
297
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100298 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700299 public boolean isVisible() {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700300 return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700301 }
302
Jeff Sharkeyd1257462018-03-27 11:30:53 -0600303 public boolean isVisibleForUser(int userId) {
Zim17be6f92019-09-25 14:37:55 +0100304 if ((type == TYPE_PUBLIC || type == TYPE_STUB || type == TYPE_EMULATED)
305 && mountUserId == userId) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700306 return isVisible();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700307 }
Zim17be6f92019-09-25 14:37:55 +0100308 return false;
309 }
310
311 /**
312 * Returns {@code true} if this volume is the primary emulated volume for {@code userId},
313 * {@code false} otherwise.
314 */
315 @UnsupportedAppUsage
316 public boolean isPrimaryEmulatedForUser(int userId) {
317 return id.equals(ID_EMULATED_INTERNAL + ";" + userId);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700318 }
319
Jeff Sharkeyd1257462018-03-27 11:30:53 -0600320 public boolean isVisibleForRead(int userId) {
321 return isVisibleForUser(userId);
322 }
323
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100324 @UnsupportedAppUsage
Jeff Sharkeyd1257462018-03-27 11:30:53 -0600325 public boolean isVisibleForWrite(int userId) {
326 return isVisibleForUser(userId);
327 }
328
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100329 @UnsupportedAppUsage
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700330 public File getPath() {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700331 return (path != null) ? new File(path) : null;
332 }
333
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100334 @UnsupportedAppUsage
Jeff Sharkey50a05452015-04-29 11:24:52 -0700335 public File getInternalPath() {
336 return (internalPath != null) ? new File(internalPath) : null;
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700337 }
338
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100339 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700340 public File getPathForUser(int userId) {
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700341 if (path == null) {
342 return null;
Risan05c41e62018-10-29 08:57:43 +0900343 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700344 return new File(path);
345 } else if (type == TYPE_EMULATED) {
346 return new File(path, Integer.toString(userId));
347 } else {
348 return null;
349 }
350 }
351
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700352 /**
353 * Path which is accessible to apps holding
354 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
355 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100356 @UnsupportedAppUsage
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700357 public File getInternalPathForUser(int userId) {
Jerry Zhang6f6154b2018-06-06 11:04:46 -0700358 if (path == null) {
359 return null;
Risan05c41e62018-10-29 08:57:43 +0900360 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700361 // TODO: plumb through cleaner path from vold
362 return new File(path.replace("/storage/", "/mnt/media_rw/"));
363 } else {
364 return getPathForUser(userId);
365 }
366 }
367
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100368 @UnsupportedAppUsage
Svet Ganov6ee871e2015-07-10 14:29:33 -0700369 public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700370 final StorageManager storage = context.getSystemService(StorageManager.class);
371
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700372 final boolean removable;
373 final boolean emulated;
374 final boolean allowMassStorage = false;
Svet Ganov6ee871e2015-07-10 14:29:33 -0700375 final String envState = reportUnmounted
376 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
Jeff Sharkey46349872015-07-28 10:49:47 -0700377
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700378 File userPath = getPathForUser(userId);
379 if (userPath == null) {
380 userPath = new File("/dev/null");
381 }
Jerry Zhang71938e12018-05-10 18:28:29 -0700382 File internalPath = getInternalPathForUser(userId);
383 if (internalPath == null) {
384 internalPath = new File("/dev/null");
385 }
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700386
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700387 String description = null;
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700388 String derivedFsUuid = fsUuid;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700389 long maxFileSize = 0;
390
391 if (type == TYPE_EMULATED) {
392 emulated = true;
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700393
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700394 final VolumeInfo privateVol = storage.findPrivateForEmulated(this);
395 if (privateVol != null) {
396 description = storage.getBestVolumeDescription(privateVol);
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700397 derivedFsUuid = privateVol.fsUuid;
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700398 }
399
Zim17be6f92019-09-25 14:37:55 +0100400 if (isPrimaryEmulatedForUser(userId)) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700401 removable = false;
402 } else {
403 removable = true;
404 }
405
Risan05c41e62018-10-29 08:57:43 +0900406 } else if (type == TYPE_PUBLIC || type == TYPE_STUB) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700407 emulated = false;
408 removable = true;
409
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700410 description = storage.getBestVolumeDescription(this);
411
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700412 if ("vfat".equals(fsType)) {
413 maxFileSize = 4294967295L;
414 }
415
416 } else {
417 throw new IllegalStateException("Unexpected volume type " + type);
418 }
419
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700420 if (description == null) {
421 description = context.getString(android.R.string.unknownName);
422 }
423
Jerry Zhang71938e12018-05-10 18:28:29 -0700424 return new StorageVolume(id, userPath, internalPath, description, isPrimary(), removable,
Jerry Zhangf9c5c252017-08-16 18:07:51 -0700425 emulated, allowMassStorage, maxFileSize, new UserHandle(userId),
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700426 derivedFsUuid, envState);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700427 }
428
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100429 @UnsupportedAppUsage
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700430 public static int buildStableMtpStorageId(String fsUuid) {
431 if (TextUtils.isEmpty(fsUuid)) {
432 return StorageVolume.STORAGE_ID_INVALID;
433 } else {
434 int hash = 0;
435 for (int i = 0; i < fsUuid.length(); ++i) {
436 hash = 31 * hash + fsUuid.charAt(i);
437 }
438 hash = (hash ^ (hash << 16)) & 0xffff0000;
439 // Work around values that the spec doesn't allow, or that we've
440 // reserved for primary
441 if (hash == 0x00000000) hash = 0x00020000;
442 if (hash == 0x00010000) hash = 0x00020000;
443 if (hash == 0xffff0000) hash = 0xfffe0000;
444 return hash | 0x0001;
445 }
446 }
447
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700448 // TODO: avoid this layering violation
449 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
450 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
451
452 /**
453 * Build an intent to browse the contents of this volume. Only valid for
454 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
455 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100456 @UnsupportedAppUsage
Jeff Sharkey0000d8a2018-03-27 11:35:32 -0600457 public @Nullable Intent buildBrowseIntent() {
Jeff Sharkeyb4ee5d72018-05-22 12:01:31 -0600458 return buildBrowseIntentForUser(UserHandle.myUserId());
459 }
460
461 public @Nullable Intent buildBrowseIntentForUser(int userId) {
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700462 final Uri uri;
Risan05c41e62018-10-29 08:57:43 +0900463 if ((type == VolumeInfo.TYPE_PUBLIC || type == VolumeInfo.TYPE_STUB)
464 && mountUserId == userId) {
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700465 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700466 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) {
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700467 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
468 DOCUMENT_ROOT_PRIMARY_EMULATED);
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700469 } else {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700470 return null;
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700471 }
472
Garfield Tan5d3b37b2017-03-01 11:01:05 -0800473 final Intent intent = new Intent(Intent.ACTION_VIEW);
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700474 intent.addCategory(Intent.CATEGORY_DEFAULT);
Jeff Sharkey42a4aaa2016-10-10 15:26:14 -0600475 intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
Aga Wronska1719b352016-03-21 11:28:03 -0700476
477 // note that docsui treats this as *force* show advanced. So sending
478 // false permits advanced to be shown based on user preferences.
479 intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700480 return intent;
481 }
482
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700483 @Override
484 public String toString() {
485 final CharArrayWriter writer = new CharArrayWriter();
486 dump(new IndentingPrintWriter(writer, " ", 80));
487 return writer.toString();
488 }
489
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700490 public void dump(IndentingPrintWriter pw) {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700491 pw.println("VolumeInfo{" + id + "}:");
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700492 pw.increaseIndent();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700493 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700494 pw.printPair("diskId", getDiskId());
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700495 pw.printPair("partGuid", partGuid);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700496 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
497 pw.printPair("mountUserId", mountUserId);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700498 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
499 pw.println();
500 pw.printPair("fsType", fsType);
501 pw.printPair("fsUuid", fsUuid);
502 pw.printPair("fsLabel", fsLabel);
503 pw.println();
504 pw.printPair("path", path);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700505 pw.printPair("internalPath", internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700506 pw.decreaseIndent();
507 pw.println();
508 }
509
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700510 @Override
511 public VolumeInfo clone() {
512 final Parcel temp = Parcel.obtain();
513 try {
514 writeToParcel(temp, 0);
515 temp.setDataPosition(0);
516 return CREATOR.createFromParcel(temp);
517 } finally {
518 temp.recycle();
519 }
520 }
521
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700522 @Override
523 public boolean equals(Object o) {
524 if (o instanceof VolumeInfo) {
525 return Objects.equals(id, ((VolumeInfo) o).id);
526 } else {
527 return false;
528 }
529 }
530
531 @Override
532 public int hashCode() {
533 return id.hashCode();
534 }
535
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100536 @UnsupportedAppUsage
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700537 public static final @android.annotation.NonNull Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700538 @Override
539 public VolumeInfo createFromParcel(Parcel in) {
540 return new VolumeInfo(in);
541 }
542
543 @Override
544 public VolumeInfo[] newArray(int size) {
545 return new VolumeInfo[size];
546 }
547 };
548
549 @Override
550 public int describeContents() {
551 return 0;
552 }
553
554 @Override
555 public void writeToParcel(Parcel parcel, int flags) {
556 parcel.writeString(id);
557 parcel.writeInt(type);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700558 if (disk != null) {
559 parcel.writeInt(1);
560 disk.writeToParcel(parcel, flags);
561 } else {
562 parcel.writeInt(0);
563 }
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700564 parcel.writeString(partGuid);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700565 parcel.writeInt(mountFlags);
566 parcel.writeInt(mountUserId);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700567 parcel.writeInt(state);
568 parcel.writeString(fsType);
569 parcel.writeString(fsUuid);
570 parcel.writeString(fsLabel);
571 parcel.writeString(path);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700572 parcel.writeString(internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700573 }
574}