blob: 225ecfa315aaa2afa49922cb0f462c105bb3531b [file] [log] [blame]
Mike Lockwood2f6a3882011-05-09 19:08:06 -07001/*
2 * Copyright (C) 2011 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
Amin Shaikh305e87e2018-11-19 12:33:27 -050019import android.annotation.NonNull;
Felipe Leme04a5d402016-02-08 16:44:06 -080020import android.annotation.Nullable;
Jeff Sharkeyc6091162018-06-29 17:15:40 -060021import android.annotation.TestApi;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010022import android.annotation.UnsupportedAppUsage;
Fabrice Di Meglio13fe2a52012-05-18 18:08:58 -070023import android.content.Context;
Felipe Leme04a5d402016-02-08 16:44:06 -080024import android.content.Intent;
Felipe Leme04a5d402016-02-08 16:44:06 -080025import android.net.Uri;
Mathew Inwood8c854f82018-09-14 12:35:36 +010026import android.os.Build;
Felipe Leme04a5d402016-02-08 16:44:06 -080027import android.os.Environment;
Mike Lockwood2f6a3882011-05-09 19:08:06 -070028import android.os.Parcel;
29import android.os.Parcelable;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070030import android.os.UserHandle;
Felipe Leme04a5d402016-02-08 16:44:06 -080031import android.provider.DocumentsContract;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070032
Jeff Sharkey5aca2b82013-10-16 16:21:54 -070033import com.android.internal.util.IndentingPrintWriter;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070034import com.android.internal.util.Preconditions;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -070035
36import java.io.CharArrayWriter;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070037import java.io.File;
Jeff Sharkey3f64ec52019-01-22 13:19:40 -070038import java.util.Locale;
Mike Lockwood2f6a3882011-05-09 19:08:06 -070039
40/**
Felipe Leme04a5d402016-02-08 16:44:06 -080041 * Information about a shared/external storage volume for a specific user.
Jeff Sharkeyb049e212012-09-07 23:16:01 -070042 *
Felipe Leme04a5d402016-02-08 16:44:06 -080043 * <p>
44 * A device always has one (and one only) primary storage volume, but it could have extra volumes,
45 * like SD cards and USB drives. This object represents the logical view of a storage
46 * volume for a specific user: different users might have different views for the same physical
47 * volume (for example, if the volume is a built-in emulated storage).
48 *
49 * <p>
50 * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
51 * verify its state.
52 *
53 * <p>
54 * Applications willing to read or write to this storage volume needs to get a permission from the
55 * user first, which can be achieved in the following ways:
56 *
57 * <ul>
58 * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
59 * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
60 * simpler API and narrows the access to the given directory (and its descendants).
Felipe Leme53fcc752016-02-17 14:45:52 -080061 * <li>To get access to any directory (and its descendants), they can use the Storage Acess
62 * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
63 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
64 * select this specific volume.
Felipe Leme04a5d402016-02-08 16:44:06 -080065 * <li>To get read and write access to the primary storage volume, applications can declare the
66 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
67 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
68 * latter including the former. This approach is discouraged, since users may be hesitant to grant
69 * broad access to all files contained on a storage device.
70 * </ul>
71 *
Jeff Sharkeyc02bfae2016-03-27 15:06:53 -060072 * <p>It can be obtained through {@link StorageManager#getStorageVolumes()} and
73 * {@link StorageManager#getPrimaryStorageVolume()} and also as an extra in some broadcasts
Felipe Leme04a5d402016-02-08 16:44:06 -080074 * (see {@link #EXTRA_STORAGE_VOLUME}).
75 *
76 * <p>
77 * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
78 * storage semantics.
Mike Lockwood2f6a3882011-05-09 19:08:06 -070079 */
Felipe Leme04a5d402016-02-08 16:44:06 -080080// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
81// user, but is now part of the public API.
Jeff Sharkey50d1c042016-02-29 16:34:46 -070082public final class StorageVolume implements Parcelable {
Mike Lockwood2f6a3882011-05-09 19:08:06 -070083
Mathew Inwood98e9ad12018-08-30 13:11:50 +010084 @UnsupportedAppUsage
Jeff Sharkey48877892015-03-18 11:27:19 -070085 private final String mId;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010086 @UnsupportedAppUsage
Jeff Sharkeyb049e212012-09-07 23:16:01 -070087 private final File mPath;
Jerry Zhang71938e12018-05-10 18:28:29 -070088 private final File mInternalPath;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010089 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070090 private final String mDescription;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010091 @UnsupportedAppUsage
Jeff Sharkey9545dc022012-09-06 22:46:30 -070092 private final boolean mPrimary;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010093 @UnsupportedAppUsage
Mike Lockwood2f6a3882011-05-09 19:08:06 -070094 private final boolean mRemovable;
95 private final boolean mEmulated;
Mike Lockwood8e8b2802011-06-07 08:03:33 -070096 private final boolean mAllowMassStorage;
Mike Lockwood7a59dd22011-07-11 09:18:03 -040097 private final long mMaxFileSize;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070098 private final UserHandle mOwner;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070099 private final String mFsUuid;
Jeff Sharkey48877892015-03-18 11:27:19 -0700100 private final String mState;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700101
Felipe Leme04a5d402016-02-08 16:44:06 -0800102 /**
103 * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
104 * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
105 * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
106 * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
107 * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
108 * contains a {@link StorageVolume}.
109 */
110 // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
111 public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
Mike Lockwooda5250c92011-05-23 13:44:04 -0400112
Felipe Leme34a9d522016-02-17 10:12:04 -0800113 /**
114 * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
115 *
116 * @hide
117 */
118 public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
119
120 /**
121 * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
122 */
123 private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
124 "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
125
Felipe Leme04a5d402016-02-08 16:44:06 -0800126 /** {@hide} */
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700127 public static final int STORAGE_ID_INVALID = 0x00000000;
Felipe Leme04a5d402016-02-08 16:44:06 -0800128 /** {@hide} */
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700129 public static final int STORAGE_ID_PRIMARY = 0x00010001;
130
Felipe Leme04a5d402016-02-08 16:44:06 -0800131 /** {@hide} */
Jerry Zhang71938e12018-05-10 18:28:29 -0700132 public StorageVolume(String id, File path, File internalPath, String description,
133 boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700134 long maxFileSize, UserHandle owner, String fsUuid, String state) {
135 mId = Preconditions.checkNotNull(id);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700136 mPath = Preconditions.checkNotNull(path);
Jerry Zhang71938e12018-05-10 18:28:29 -0700137 mInternalPath = Preconditions.checkNotNull(internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700138 mDescription = Preconditions.checkNotNull(description);
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700139 mPrimary = primary;
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700140 mRemovable = removable;
141 mEmulated = emulated;
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700142 mAllowMassStorage = allowMassStorage;
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400143 mMaxFileSize = maxFileSize;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700144 mOwner = Preconditions.checkNotNull(owner);
145 mFsUuid = fsUuid;
146 mState = Preconditions.checkNotNull(state);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700147 }
148
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700149 private StorageVolume(Parcel in) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700150 mId = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700151 mPath = new File(in.readString());
Jerry Zhang71938e12018-05-10 18:28:29 -0700152 mInternalPath = new File(in.readString());
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700153 mDescription = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700154 mPrimary = in.readInt() != 0;
155 mRemovable = in.readInt() != 0;
156 mEmulated = in.readInt() != 0;
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700157 mAllowMassStorage = in.readInt() != 0;
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700158 mMaxFileSize = in.readLong();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700159 mOwner = in.readParcelable(null);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700160 mFsUuid = in.readString();
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700161 mState = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700162 }
163
Felipe Leme04a5d402016-02-08 16:44:06 -0800164 /** {@hide} */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100165 @UnsupportedAppUsage
Jeff Sharkey48877892015-03-18 11:27:19 -0700166 public String getId() {
167 return mId;
Mike Lockwoodfbfe5552011-05-17 17:19:37 -0400168 }
169
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700170 /**
171 * Returns the mount path for the volume.
172 *
173 * @return the mount path
Felipe Leme04a5d402016-02-08 16:44:06 -0800174 * @hide
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700175 */
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600176 @TestApi
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700177 public String getPath() {
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700178 return mPath.toString();
179 }
180
Jerry Zhang71938e12018-05-10 18:28:29 -0700181 /**
182 * Returns the path of the underlying filesystem.
183 *
184 * @return the internal path
185 * @hide
186 */
187 public String getInternalPath() {
188 return mInternalPath.toString();
189 }
190
Felipe Leme04a5d402016-02-08 16:44:06 -0800191 /** {@hide} */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100192 @UnsupportedAppUsage
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700193 public File getPathFile() {
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700194 return mPath;
195 }
196
197 /**
Felipe Leme04a5d402016-02-08 16:44:06 -0800198 * Returns a user-visible description of the volume.
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700199 *
200 * @return the volume description
201 */
Fabrice Di Meglio13fe2a52012-05-18 18:08:58 -0700202 public String getDescription(Context context) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700203 return mDescription;
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700204 }
205
Felipe Leme04a5d402016-02-08 16:44:06 -0800206 /**
207 * Returns true if the volume is the primary shared/external storage, which is the volume
208 * backed by {@link Environment#getExternalStorageDirectory()}.
209 */
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700210 public boolean isPrimary() {
211 return mPrimary;
212 }
213
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700214 /**
215 * Returns true if the volume is removable.
216 *
217 * @return is removable
218 */
219 public boolean isRemovable() {
220 return mRemovable;
221 }
222
223 /**
224 * Returns true if the volume is emulated.
225 *
226 * @return is removable
227 */
228 public boolean isEmulated() {
229 return mEmulated;
230 }
231
232 /**
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700233 * Returns true if this volume can be shared via USB mass storage.
234 *
235 * @return whether mass storage is allowed
Felipe Leme04a5d402016-02-08 16:44:06 -0800236 * @hide
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700237 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100238 @UnsupportedAppUsage
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700239 public boolean allowMassStorage() {
240 return mAllowMassStorage;
241 }
242
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400243 /**
244 * Returns maximum file size for the volume, or zero if it is unbounded.
245 *
246 * @return maximum file size
Felipe Leme04a5d402016-02-08 16:44:06 -0800247 * @hide
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400248 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100249 @UnsupportedAppUsage
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400250 public long getMaxFileSize() {
251 return mMaxFileSize;
252 }
253
Felipe Leme04a5d402016-02-08 16:44:06 -0800254 /** {@hide} */
Mathew Inwood8c854f82018-09-14 12:35:36 +0100255 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700256 public UserHandle getOwner() {
257 return mOwner;
258 }
259
Felipe Leme04a5d402016-02-08 16:44:06 -0800260 /**
261 * Gets the volume UUID, if any.
262 */
263 public @Nullable String getUuid() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700264 return mFsUuid;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700265 }
266
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700267 /** {@hide} */
268 public @Nullable String getNormalizedUuid() {
269 return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
270 }
271
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700272 /**
273 * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
274 * parse or UUID is unknown.
Felipe Leme04a5d402016-02-08 16:44:06 -0800275 * @hide
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700276 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100277 @UnsupportedAppUsage
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700278 public int getFatVolumeId() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700279 if (mFsUuid == null || mFsUuid.length() != 9) {
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700280 return -1;
281 }
282 try {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700283 return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700284 } catch (NumberFormatException e) {
285 return -1;
286 }
287 }
288
Felipe Leme04a5d402016-02-08 16:44:06 -0800289 /** {@hide} */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100290 @UnsupportedAppUsage
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700291 public String getUserLabel() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700292 return mDescription;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700293 }
294
Felipe Leme04a5d402016-02-08 16:44:06 -0800295 /**
296 * Returns the current state of the volume.
297 *
298 * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
299 * {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
300 * {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
301 * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
302 * {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
303 */
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700304 public String getState() {
305 return mState;
306 }
307
Felipe Leme04a5d402016-02-08 16:44:06 -0800308 /**
Felipe Lemedb892b82016-03-17 18:56:20 -0700309 * Builds an intent to give access to a standard storage directory or entire volume after
310 * obtaining the user's approval.
Felipe Leme04a5d402016-02-08 16:44:06 -0800311 * <p>
312 * When invoked, the system will ask the user to grant access to the requested directory (and
313 * its descendants). The result of the request will be returned to the activity through the
314 * {@code onActivityResult} method.
315 * <p>
316 * To gain access to descendants (child, grandchild, etc) documents, use
317 * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
318 * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
Felipe Leme2ac87692016-03-29 19:06:02 -0700319 * <p>
320 * If your application only needs to store internal data, consider using
Felipe Leme04a5d402016-02-08 16:44:06 -0800321 * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
Felipe Leme2ac87692016-03-29 19:06:02 -0700322 * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which
323 * require no permissions to read or write.
324 * <p>
325 * Access to the entire volume is only available for non-primary volumes (for the primary
326 * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
327 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used
328 * with caution, since users are more likely to deny access when asked for entire volume access
329 * rather than specific directories.
Felipe Leme04a5d402016-02-08 16:44:06 -0800330 *
Felipe Leme2ac87692016-03-29 19:06:02 -0700331 * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC},
332 * {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES},
333 * {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS},
334 * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES},
335 * {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or
Garfield Tan92b96ba2016-11-01 14:33:48 -0700336 * {@link Environment#DIRECTORY_DOCUMENTS}, or {@code null} to request access to the
Felipe Leme2ac87692016-03-29 19:06:02 -0700337 * entire volume.
338 * @return intent to request access, or {@code null} if the requested directory is invalid for
339 * that volume.
Felipe Leme04a5d402016-02-08 16:44:06 -0800340 * @see DocumentsContract
Amin Shaikhb49cedf2018-10-23 13:37:19 -0400341 * @deprecated Callers should migrate to using {@link Intent#ACTION_OPEN_DOCUMENT_TREE} instead.
342 * Launching this {@link Intent} on devices running
343 * {@link android.os.Build.VERSION_CODES#Q} or higher, will immediately finish
344 * with a result code of {@link android.app.Activity#RESULT_CANCELED}.
Felipe Leme04a5d402016-02-08 16:44:06 -0800345 */
Amin Shaikhb49cedf2018-10-23 13:37:19 -0400346 @Deprecated
Felipe Leme2ac87692016-03-29 19:06:02 -0700347 public @Nullable Intent createAccessIntent(String directoryName) {
348 if ((isPrimary() && directoryName == null) ||
349 (directoryName != null && !Environment.isStandardDirectory(directoryName))) {
350 return null;
351 }
Felipe Leme34a9d522016-02-17 10:12:04 -0800352 final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
353 intent.putExtra(EXTRA_STORAGE_VOLUME, this);
354 intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
Felipe Leme04a5d402016-02-08 16:44:06 -0800355 return intent;
356 }
357
Amin Shaikh305e87e2018-11-19 12:33:27 -0500358 /**
359 * Builds an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} to allow the user to grant access to any
360 * directory subtree (or entire volume) from the {@link android.provider.DocumentsProvider}s
361 * available on the device. The initial location of the document navigation will be the root of
362 * this {@link StorageVolume}.
363 *
364 * Note that the returned {@link Intent} simply suggests that the user picks this {@link
365 * StorageVolume} by default, but the user may select a different location. Callers must respect
366 * the user's chosen location, even if it is different from the originally requested location.
367 *
368 * @return intent to {@link Intent#ACTION_OPEN_DOCUMENT_TREE} initially showing the contents
369 * of this {@link StorageVolume}
370 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
371 */
372 @NonNull public Intent createOpenDocumentTreeIntent() {
373 final String rootId = isEmulated()
374 ? DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
375 : mFsUuid;
376 final Uri rootUri = DocumentsContract.buildRootUri(
377 DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, rootId);
378 final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
379 .putExtra(DocumentsContract.EXTRA_INITIAL_URI, rootUri)
380 .putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
381 return intent;
382 }
383
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700384 @Override
385 public boolean equals(Object obj) {
386 if (obj instanceof StorageVolume && mPath != null) {
387 StorageVolume volume = (StorageVolume)obj;
388 return (mPath.equals(volume.mPath));
389 }
390 return false;
391 }
392
393 @Override
394 public int hashCode() {
395 return mPath.hashCode();
396 }
397
398 @Override
399 public String toString() {
Felipe Leme04a5d402016-02-08 16:44:06 -0800400 final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
401 if (mFsUuid != null) {
402 buffer.append(" (").append(mFsUuid).append(")");
403 }
404 return buffer.toString();
405 }
406
407 /** {@hide} */
Felipe Leme104b9322017-03-14 08:40:06 -0700408 // TODO: find out where toString() is called internally and replace these calls by dump().
Felipe Leme04a5d402016-02-08 16:44:06 -0800409 public String dump() {
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700410 final CharArrayWriter writer = new CharArrayWriter();
411 dump(new IndentingPrintWriter(writer, " ", 80));
412 return writer.toString();
413 }
414
Felipe Leme04a5d402016-02-08 16:44:06 -0800415 /** {@hide} */
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700416 public void dump(IndentingPrintWriter pw) {
417 pw.println("StorageVolume:");
418 pw.increaseIndent();
Jeff Sharkey48877892015-03-18 11:27:19 -0700419 pw.printPair("mId", mId);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700420 pw.printPair("mPath", mPath);
Jerry Zhang71938e12018-05-10 18:28:29 -0700421 pw.printPair("mInternalPath", mInternalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700422 pw.printPair("mDescription", mDescription);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700423 pw.printPair("mPrimary", mPrimary);
424 pw.printPair("mRemovable", mRemovable);
425 pw.printPair("mEmulated", mEmulated);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700426 pw.printPair("mAllowMassStorage", mAllowMassStorage);
427 pw.printPair("mMaxFileSize", mMaxFileSize);
428 pw.printPair("mOwner", mOwner);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700429 pw.printPair("mFsUuid", mFsUuid);
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700430 pw.printPair("mState", mState);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700431 pw.decreaseIndent();
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700432 }
433
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700434 public static final @android.annotation.NonNull Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700435 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700436 public StorageVolume createFromParcel(Parcel in) {
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700437 return new StorageVolume(in);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700438 }
439
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700440 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700441 public StorageVolume[] newArray(int size) {
442 return new StorageVolume[size];
443 }
444 };
445
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700446 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700447 public int describeContents() {
448 return 0;
449 }
450
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700451 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700452 public void writeToParcel(Parcel parcel, int flags) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700453 parcel.writeString(mId);
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700454 parcel.writeString(mPath.toString());
Jerry Zhang71938e12018-05-10 18:28:29 -0700455 parcel.writeString(mInternalPath.toString());
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700456 parcel.writeString(mDescription);
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700457 parcel.writeInt(mPrimary ? 1 : 0);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700458 parcel.writeInt(mRemovable ? 1 : 0);
459 parcel.writeInt(mEmulated ? 1 : 0);
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700460 parcel.writeInt(mAllowMassStorage ? 1 : 0);
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400461 parcel.writeLong(mMaxFileSize);
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700462 parcel.writeParcelable(mOwner, flags);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700463 parcel.writeString(mFsUuid);
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700464 parcel.writeString(mState);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700465 }
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700466}