blob: a21e05e314b2d3806fac5483ad34975bb6775696 [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;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070021import android.content.Context;
22import android.content.Intent;
Jeff Sharkey59d577a2015-04-11 21:27:21 -070023import android.content.res.Resources;
Jeff Sharkey56bd3122015-04-14 10:30:34 -070024import android.net.Uri;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070025import android.os.Environment;
26import android.os.Parcel;
27import android.os.Parcelable;
28import android.os.UserHandle;
Jeff Sharkey56bd3122015-04-14 10:30:34 -070029import android.provider.DocumentsContract;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070030import android.text.TextUtils;
31import android.util.ArrayMap;
32import android.util.DebugUtils;
33import android.util.SparseArray;
Jeff Sharkey5fc24732015-06-10 14:21:27 -070034import android.util.SparseIntArray;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070035
Jeff Sharkey5fc24732015-06-10 14:21:27 -070036import com.android.internal.R;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070037import com.android.internal.util.IndentingPrintWriter;
38import com.android.internal.util.Preconditions;
39
Jeff Sharkey7151a9a2015-04-04 15:22:37 -070040import java.io.CharArrayWriter;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070041import java.io.File;
Jeff Sharkeye2d45be2015-04-15 17:14:12 -070042import java.util.Comparator;
43import java.util.Objects;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070044
45/**
46 * Information about a storage volume that may be mounted. A volume may be a
47 * partition on a physical {@link DiskInfo}, an emulated volume above some other
48 * storage medium, or a standalone container like an ASEC or OBB.
Jeff Sharkey46349872015-07-28 10:49:47 -070049 * <p>
50 * Volumes may be mounted with various flags:
51 * <ul>
52 * <li>{@link #MOUNT_FLAG_PRIMARY} means the volume provides primary external
53 * storage, historically found at {@code /sdcard}.
54 * <li>{@link #MOUNT_FLAG_VISIBLE} means the volume is visible to third-party
55 * apps for direct filesystem access. The system should send out relevant
56 * storage broadcasts and index any media on visible volumes. Visible volumes
57 * are considered a more stable part of the device, which is why we take the
58 * time to index them. In particular, transient volumes like USB OTG devices
59 * <em>should not</em> be marked as visible; their contents should be surfaced
60 * to apps through the Storage Access Framework.
61 * </ul>
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070062 *
63 * @hide
64 */
65public class VolumeInfo implements Parcelable {
Jeff Sharkeye6c04f92015-04-18 21:38:05 -070066 public static final String ACTION_VOLUME_STATE_CHANGED =
67 "android.os.storage.action.VOLUME_STATE_CHANGED";
68 public static final String EXTRA_VOLUME_ID =
69 "android.os.storage.extra.VOLUME_ID";
Jeff Sharkeyc7acac62015-06-12 16:16:56 -070070 public static final String EXTRA_VOLUME_STATE =
71 "android.os.storage.extra.VOLUME_STATE";
Jeff Sharkey56bd3122015-04-14 10:30:34 -070072
Jeff Sharkey59d577a2015-04-11 21:27:21 -070073 /** Stub volume representing internal private storage */
74 public static final String ID_PRIVATE_INTERNAL = "private";
Jeff Sharkeyb2b9ab82015-04-05 21:10:42 -070075 /** Real volume representing internal emulated storage */
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070076 public static final String ID_EMULATED_INTERNAL = "emulated";
77
78 public static final int TYPE_PUBLIC = 0;
79 public static final int TYPE_PRIVATE = 1;
80 public static final int TYPE_EMULATED = 2;
81 public static final int TYPE_ASEC = 3;
82 public static final int TYPE_OBB = 4;
83
84 public static final int STATE_UNMOUNTED = 0;
Jeff Sharkey7e92ef32015-04-17 17:35:07 -070085 public static final int STATE_CHECKING = 1;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070086 public static final int STATE_MOUNTED = 2;
Jeff Sharkey7e92ef32015-04-17 17:35:07 -070087 public static final int STATE_MOUNTED_READ_ONLY = 3;
88 public static final int STATE_FORMATTING = 4;
89 public static final int STATE_EJECTING = 5;
90 public static final int STATE_UNMOUNTABLE = 6;
91 public static final int STATE_REMOVED = 7;
92 public static final int STATE_BAD_REMOVAL = 8;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070093
Jeff Sharkey7e92ef32015-04-17 17:35:07 -070094 public static final int MOUNT_FLAG_PRIMARY = 1 << 0;
95 public static final int MOUNT_FLAG_VISIBLE = 1 << 1;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070096
Jeff Sharkey7151a9a2015-04-04 15:22:37 -070097 private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
98 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
Jeff Sharkey5fc24732015-06-10 14:21:27 -070099 private static SparseIntArray sStateToDescrip = new SparseIntArray();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700100
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700101 private static final Comparator<VolumeInfo>
102 sDescriptionComparator = new Comparator<VolumeInfo>() {
103 @Override
104 public int compare(VolumeInfo lhs, VolumeInfo rhs) {
105 if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(lhs.getId())) {
106 return -1;
107 } else if (lhs.getDescription() == null) {
108 return 1;
109 } else if (rhs.getDescription() == null) {
110 return -1;
111 } else {
112 return lhs.getDescription().compareTo(rhs.getDescription());
113 }
114 }
115 };
116
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700117 static {
118 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700119 sStateToEnvironment.put(VolumeInfo.STATE_CHECKING, Environment.MEDIA_CHECKING);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700120 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700121 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, Environment.MEDIA_MOUNTED_READ_ONLY);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700122 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700123 sStateToEnvironment.put(VolumeInfo.STATE_EJECTING, Environment.MEDIA_EJECTING);
Jeff Sharkey16c9c242015-04-04 21:37:20 -0700124 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE);
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700125 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700126 sStateToEnvironment.put(VolumeInfo.STATE_BAD_REMOVAL, Environment.MEDIA_BAD_REMOVAL);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700127
128 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
129 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
130 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700131 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED_READ_ONLY, Intent.ACTION_MEDIA_MOUNTED);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700132 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
Jeff Sharkey16c9c242015-04-04 21:37:20 -0700133 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE);
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700134 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700135 sEnvironmentToBroadcast.put(Environment.MEDIA_BAD_REMOVAL, Intent.ACTION_MEDIA_BAD_REMOVAL);
Jeff Sharkey5fc24732015-06-10 14:21:27 -0700136
137 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTED, R.string.ext_media_status_unmounted);
138 sStateToDescrip.put(VolumeInfo.STATE_CHECKING, R.string.ext_media_status_checking);
139 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED, R.string.ext_media_status_mounted);
140 sStateToDescrip.put(VolumeInfo.STATE_MOUNTED_READ_ONLY, R.string.ext_media_status_mounted_ro);
141 sStateToDescrip.put(VolumeInfo.STATE_FORMATTING, R.string.ext_media_status_formatting);
142 sStateToDescrip.put(VolumeInfo.STATE_EJECTING, R.string.ext_media_status_ejecting);
143 sStateToDescrip.put(VolumeInfo.STATE_UNMOUNTABLE, R.string.ext_media_status_unmountable);
144 sStateToDescrip.put(VolumeInfo.STATE_REMOVED, R.string.ext_media_status_removed);
145 sStateToDescrip.put(VolumeInfo.STATE_BAD_REMOVAL, R.string.ext_media_status_bad_removal);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700146 }
147
148 /** vold state */
149 public final String id;
150 public final int type;
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700151 public final DiskInfo disk;
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700152 public final String partGuid;
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700153 public int mountFlags = 0;
154 public int mountUserId = -1;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700155 public int state = STATE_UNMOUNTED;
156 public String fsType;
157 public String fsUuid;
158 public String fsLabel;
159 public String path;
Jeff Sharkey50a05452015-04-29 11:24:52 -0700160 public String internalPath;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700161
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700162 public VolumeInfo(String id, int type, DiskInfo disk, String partGuid) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700163 this.id = Preconditions.checkNotNull(id);
164 this.type = type;
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700165 this.disk = disk;
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700166 this.partGuid = partGuid;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700167 }
168
169 public VolumeInfo(Parcel parcel) {
170 id = parcel.readString();
171 type = parcel.readInt();
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700172 if (parcel.readInt() != 0) {
173 disk = DiskInfo.CREATOR.createFromParcel(parcel);
174 } else {
175 disk = null;
176 }
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700177 partGuid = parcel.readString();
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700178 mountFlags = parcel.readInt();
179 mountUserId = parcel.readInt();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700180 state = parcel.readInt();
181 fsType = parcel.readString();
182 fsUuid = parcel.readString();
183 fsLabel = parcel.readString();
184 path = parcel.readString();
Jeff Sharkey50a05452015-04-29 11:24:52 -0700185 internalPath = parcel.readString();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700186 }
187
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700188 public static @NonNull String getEnvironmentForState(int state) {
189 final String envState = sStateToEnvironment.get(state);
190 if (envState != null) {
191 return envState;
192 } else {
193 return Environment.MEDIA_UNKNOWN;
194 }
195 }
196
197 public static @Nullable String getBroadcastForEnvironment(String envState) {
198 return sEnvironmentToBroadcast.get(envState);
199 }
200
201 public static @Nullable String getBroadcastForState(int state) {
202 return getBroadcastForEnvironment(getEnvironmentForState(state));
203 }
204
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700205 public static @NonNull Comparator<VolumeInfo> getDescriptionComparator() {
206 return sDescriptionComparator;
207 }
208
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700209 public @NonNull String getId() {
210 return id;
211 }
212
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700213 public @Nullable DiskInfo getDisk() {
214 return disk;
215 }
216
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700217 public @Nullable String getDiskId() {
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700218 return (disk != null) ? disk.id : null;
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700219 }
220
221 public int getType() {
222 return type;
223 }
224
225 public int getState() {
226 return state;
227 }
228
Jeff Sharkey5fc24732015-06-10 14:21:27 -0700229 public int getStateDescription() {
230 return sStateToDescrip.get(state, 0);
231 }
232
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700233 public @Nullable String getFsUuid() {
234 return fsUuid;
235 }
236
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700237 public int getMountUserId() {
238 return mountUserId;
239 }
240
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700241 public @Nullable String getDescription() {
Jeff Sharkey7a788a82015-07-07 14:33:55 -0700242 if (ID_PRIVATE_INTERNAL.equals(id) || ID_EMULATED_INTERNAL.equals(id)) {
Jeff Sharkey59d577a2015-04-11 21:27:21 -0700243 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700244 } else if (!TextUtils.isEmpty(fsLabel)) {
245 return fsLabel;
246 } else {
247 return null;
248 }
249 }
250
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700251 public boolean isMountedReadable() {
252 return state == STATE_MOUNTED || state == STATE_MOUNTED_READ_ONLY;
253 }
254
255 public boolean isMountedWritable() {
256 return state == STATE_MOUNTED;
257 }
258
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700259 public boolean isPrimary() {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700260 return (mountFlags & MOUNT_FLAG_PRIMARY) != 0;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700261 }
262
Jeff Sharkey620b32b2015-04-23 19:36:02 -0700263 public boolean isPrimaryPhysical() {
264 return isPrimary() && (getType() == TYPE_PUBLIC);
265 }
266
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700267 public boolean isVisible() {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700268 return (mountFlags & MOUNT_FLAG_VISIBLE) != 0;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700269 }
270
Jeff Sharkey46349872015-07-28 10:49:47 -0700271 public boolean isVisibleForRead(int userId) {
272 if (type == TYPE_PUBLIC) {
273 if (isPrimary() && mountUserId != userId) {
274 // Primary physical is only visible to single user
275 return false;
276 } else {
277 return isVisible();
278 }
279 } else if (type == TYPE_EMULATED) {
280 return isVisible();
281 } else {
282 return false;
283 }
284 }
285
286 public boolean isVisibleForWrite(int userId) {
287 if (type == TYPE_PUBLIC && mountUserId == userId) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700288 return isVisible();
289 } else if (type == TYPE_EMULATED) {
290 return isVisible();
291 } else {
292 return false;
293 }
294 }
295
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700296 public File getPath() {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700297 return (path != null) ? new File(path) : null;
298 }
299
300 public File getInternalPath() {
301 return (internalPath != null) ? new File(internalPath) : null;
Jeff Sharkeyd95d3bf2015-04-14 21:39:44 -0700302 }
303
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700304 public File getPathForUser(int userId) {
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700305 if (path == null) {
306 return null;
Jeff Sharkey46349872015-07-28 10:49:47 -0700307 } else if (type == TYPE_PUBLIC) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700308 return new File(path);
309 } else if (type == TYPE_EMULATED) {
310 return new File(path, Integer.toString(userId));
311 } else {
312 return null;
313 }
314 }
315
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700316 /**
317 * Path which is accessible to apps holding
318 * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
319 */
320 public File getInternalPathForUser(int userId) {
321 if (type == TYPE_PUBLIC) {
322 // TODO: plumb through cleaner path from vold
323 return new File(path.replace("/storage/", "/mnt/media_rw/"));
324 } else {
325 return getPathForUser(userId);
326 }
327 }
328
Svet Ganov6ee871e2015-07-10 14:29:33 -0700329 public StorageVolume buildStorageVolume(Context context, int userId, boolean reportUnmounted) {
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700330 final StorageManager storage = context.getSystemService(StorageManager.class);
331
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700332 final boolean removable;
333 final boolean emulated;
334 final boolean allowMassStorage = false;
Svet Ganov6ee871e2015-07-10 14:29:33 -0700335 final String envState = reportUnmounted
336 ? Environment.MEDIA_UNMOUNTED : getEnvironmentForState(state);
Jeff Sharkey46349872015-07-28 10:49:47 -0700337
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700338 File userPath = getPathForUser(userId);
339 if (userPath == null) {
340 userPath = new File("/dev/null");
341 }
342
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700343 String description = null;
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700344 String derivedFsUuid = fsUuid;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700345 long mtpReserveSize = 0;
346 long maxFileSize = 0;
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700347 int mtpStorageId = StorageVolume.STORAGE_ID_INVALID;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700348
349 if (type == TYPE_EMULATED) {
350 emulated = true;
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700351
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700352 final VolumeInfo privateVol = storage.findPrivateForEmulated(this);
353 if (privateVol != null) {
354 description = storage.getBestVolumeDescription(privateVol);
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700355 derivedFsUuid = privateVol.fsUuid;
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700356 }
357
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700358 if (isPrimary()) {
359 mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
360 }
361
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700362 mtpReserveSize = storage.getStorageLowBytes(userPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700363
364 if (ID_EMULATED_INTERNAL.equals(id)) {
365 removable = false;
366 } else {
367 removable = true;
368 }
369
370 } else if (type == TYPE_PUBLIC) {
371 emulated = false;
372 removable = true;
373
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700374 description = storage.getBestVolumeDescription(this);
375
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700376 if (isPrimary()) {
377 mtpStorageId = StorageVolume.STORAGE_ID_PRIMARY;
378 } else {
379 // Since MediaProvider currently persists this value, we need a
380 // value that is stable over time.
381 mtpStorageId = buildStableMtpStorageId(fsUuid);
382 }
383
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700384 if ("vfat".equals(fsType)) {
385 maxFileSize = 4294967295L;
386 }
387
388 } else {
389 throw new IllegalStateException("Unexpected volume type " + type);
390 }
391
Jeff Sharkeya83bf192015-07-08 09:18:20 -0700392 if (description == null) {
393 description = context.getString(android.R.string.unknownName);
394 }
395
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700396 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable,
397 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId),
Jeff Sharkey8e2ea2a2015-08-19 14:15:33 -0700398 derivedFsUuid, envState);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700399 }
400
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700401 public static int buildStableMtpStorageId(String fsUuid) {
402 if (TextUtils.isEmpty(fsUuid)) {
403 return StorageVolume.STORAGE_ID_INVALID;
404 } else {
405 int hash = 0;
406 for (int i = 0; i < fsUuid.length(); ++i) {
407 hash = 31 * hash + fsUuid.charAt(i);
408 }
409 hash = (hash ^ (hash << 16)) & 0xffff0000;
410 // Work around values that the spec doesn't allow, or that we've
411 // reserved for primary
412 if (hash == 0x00000000) hash = 0x00020000;
413 if (hash == 0x00010000) hash = 0x00020000;
414 if (hash == 0xffff0000) hash = 0xfffe0000;
415 return hash | 0x0001;
416 }
417 }
418
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700419 // TODO: avoid this layering violation
420 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents";
421 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary";
422
423 /**
424 * Build an intent to browse the contents of this volume. Only valid for
425 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}.
426 */
427 public Intent buildBrowseIntent() {
428 final Uri uri;
429 if (type == VolumeInfo.TYPE_PUBLIC) {
430 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700431 } else if (type == VolumeInfo.TYPE_EMULATED && isPrimary()) {
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700432 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY,
433 DOCUMENT_ROOT_PRIMARY_EMULATED);
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700434 } else {
Jeff Sharkey50a05452015-04-29 11:24:52 -0700435 return null;
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700436 }
437
Garfield Tan5d3b37b2017-03-01 11:01:05 -0800438 final Intent intent = new Intent(Intent.ACTION_VIEW);
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700439 intent.addCategory(Intent.CATEGORY_DEFAULT);
Jeff Sharkey42a4aaa2016-10-10 15:26:14 -0600440 intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
Aga Wronska1719b352016-03-21 11:28:03 -0700441
442 // note that docsui treats this as *force* show advanced. So sending
443 // false permits advanced to be shown based on user preferences.
444 intent.putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, isPrimary());
Jeff Sharkey56bd3122015-04-14 10:30:34 -0700445 return intent;
446 }
447
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700448 @Override
449 public String toString() {
450 final CharArrayWriter writer = new CharArrayWriter();
451 dump(new IndentingPrintWriter(writer, " ", 80));
452 return writer.toString();
453 }
454
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700455 public void dump(IndentingPrintWriter pw) {
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700456 pw.println("VolumeInfo{" + id + "}:");
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700457 pw.increaseIndent();
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700458 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700459 pw.printPair("diskId", getDiskId());
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700460 pw.printPair("partGuid", partGuid);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700461 pw.printPair("mountFlags", DebugUtils.flagsToString(getClass(), "MOUNT_FLAG_", mountFlags));
462 pw.printPair("mountUserId", mountUserId);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700463 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
464 pw.println();
465 pw.printPair("fsType", fsType);
466 pw.printPair("fsUuid", fsUuid);
467 pw.printPair("fsLabel", fsLabel);
468 pw.println();
469 pw.printPair("path", path);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700470 pw.printPair("internalPath", internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700471 pw.decreaseIndent();
472 pw.println();
473 }
474
Jeff Sharkey7151a9a2015-04-04 15:22:37 -0700475 @Override
476 public VolumeInfo clone() {
477 final Parcel temp = Parcel.obtain();
478 try {
479 writeToParcel(temp, 0);
480 temp.setDataPosition(0);
481 return CREATOR.createFromParcel(temp);
482 } finally {
483 temp.recycle();
484 }
485 }
486
Jeff Sharkeye2d45be2015-04-15 17:14:12 -0700487 @Override
488 public boolean equals(Object o) {
489 if (o instanceof VolumeInfo) {
490 return Objects.equals(id, ((VolumeInfo) o).id);
491 } else {
492 return false;
493 }
494 }
495
496 @Override
497 public int hashCode() {
498 return id.hashCode();
499 }
500
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700501 public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() {
502 @Override
503 public VolumeInfo createFromParcel(Parcel in) {
504 return new VolumeInfo(in);
505 }
506
507 @Override
508 public VolumeInfo[] newArray(int size) {
509 return new VolumeInfo[size];
510 }
511 };
512
513 @Override
514 public int describeContents() {
515 return 0;
516 }
517
518 @Override
519 public void writeToParcel(Parcel parcel, int flags) {
520 parcel.writeString(id);
521 parcel.writeInt(type);
Jeff Sharkey27de30d2015-04-18 16:20:27 -0700522 if (disk != null) {
523 parcel.writeInt(1);
524 disk.writeToParcel(parcel, flags);
525 } else {
526 parcel.writeInt(0);
527 }
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700528 parcel.writeString(partGuid);
Jeff Sharkey7e92ef32015-04-17 17:35:07 -0700529 parcel.writeInt(mountFlags);
530 parcel.writeInt(mountUserId);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700531 parcel.writeInt(state);
532 parcel.writeString(fsType);
533 parcel.writeString(fsUuid);
534 parcel.writeString(fsLabel);
535 parcel.writeString(path);
Jeff Sharkey50a05452015-04-29 11:24:52 -0700536 parcel.writeString(internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700537 }
538}