blob: 2ab226f81bb40a7732e69a79ee3bd7dbc54f9606 [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;
Artur Satayevafdb23a2019-12-10 17:47:53 +000022import android.compat.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 Sharkey04b4ba12019-12-15 22:42:42 -070032import android.provider.MediaStore;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070033
Jeff Sharkey5aca2b82013-10-16 16:21:54 -070034import com.android.internal.util.IndentingPrintWriter;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070035import com.android.internal.util.Preconditions;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -070036
37import java.io.CharArrayWriter;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070038import java.io.File;
Jeff Sharkey3f64ec52019-01-22 13:19:40 -070039import java.util.Locale;
Mike Lockwood2f6a3882011-05-09 19:08:06 -070040
41/**
Felipe Leme04a5d402016-02-08 16:44:06 -080042 * Information about a shared/external storage volume for a specific user.
Jeff Sharkeyb049e212012-09-07 23:16:01 -070043 *
Felipe Leme04a5d402016-02-08 16:44:06 -080044 * <p>
45 * A device always has one (and one only) primary storage volume, but it could have extra volumes,
46 * like SD cards and USB drives. This object represents the logical view of a storage
47 * volume for a specific user: different users might have different views for the same physical
48 * volume (for example, if the volume is a built-in emulated storage).
49 *
50 * <p>
51 * The storage volume is not necessarily mounted, applications should use {@link #getState()} to
52 * verify its state.
53 *
54 * <p>
55 * Applications willing to read or write to this storage volume needs to get a permission from the
56 * user first, which can be achieved in the following ways:
57 *
58 * <ul>
59 * <li>To get access to standard directories (like the {@link Environment#DIRECTORY_PICTURES}), they
60 * can use the {@link #createAccessIntent(String)}. This is the recommend way, since it provides a
61 * simpler API and narrows the access to the given directory (and its descendants).
Felipe Leme53fcc752016-02-17 14:45:52 -080062 * <li>To get access to any directory (and its descendants), they can use the Storage Acess
63 * Framework APIs (such as {@link Intent#ACTION_OPEN_DOCUMENT} and
64 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, although these APIs do not guarantee the user will
65 * select this specific volume.
Felipe Leme04a5d402016-02-08 16:44:06 -080066 * <li>To get read and write access to the primary storage volume, applications can declare the
67 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
68 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions respectively, with the
69 * latter including the former. This approach is discouraged, since users may be hesitant to grant
70 * broad access to all files contained on a storage device.
71 * </ul>
72 *
Jeff Sharkeyc02bfae2016-03-27 15:06:53 -060073 * <p>It can be obtained through {@link StorageManager#getStorageVolumes()} and
74 * {@link StorageManager#getPrimaryStorageVolume()} and also as an extra in some broadcasts
Felipe Leme04a5d402016-02-08 16:44:06 -080075 * (see {@link #EXTRA_STORAGE_VOLUME}).
76 *
77 * <p>
78 * See {@link Environment#getExternalStorageDirectory()} for more info about shared/external
79 * storage semantics.
Mike Lockwood2f6a3882011-05-09 19:08:06 -070080 */
Felipe Leme04a5d402016-02-08 16:44:06 -080081// NOTE: This is a legacy specialization of VolumeInfo which describes the volume for a specific
82// user, but is now part of the public API.
Jeff Sharkey50d1c042016-02-29 16:34:46 -070083public final class StorageVolume implements Parcelable {
Mike Lockwood2f6a3882011-05-09 19:08:06 -070084
Mathew Inwood98e9ad12018-08-30 13:11:50 +010085 @UnsupportedAppUsage
Jeff Sharkey48877892015-03-18 11:27:19 -070086 private final String mId;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010087 @UnsupportedAppUsage
Jeff Sharkeyb049e212012-09-07 23:16:01 -070088 private final File mPath;
Jerry Zhang71938e12018-05-10 18:28:29 -070089 private final File mInternalPath;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010090 @UnsupportedAppUsage
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -070091 private final String mDescription;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010092 @UnsupportedAppUsage
Jeff Sharkey9545dc022012-09-06 22:46:30 -070093 private final boolean mPrimary;
Mathew Inwood98e9ad12018-08-30 13:11:50 +010094 @UnsupportedAppUsage
Mike Lockwood2f6a3882011-05-09 19:08:06 -070095 private final boolean mRemovable;
96 private final boolean mEmulated;
Mike Lockwood8e8b2802011-06-07 08:03:33 -070097 private final boolean mAllowMassStorage;
Mike Lockwood7a59dd22011-07-11 09:18:03 -040098 private final long mMaxFileSize;
Jeff Sharkeyb049e212012-09-07 23:16:01 -070099 private final UserHandle mOwner;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700100 private final String mFsUuid;
Jeff Sharkey48877892015-03-18 11:27:19 -0700101 private final String mState;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700102
Felipe Leme04a5d402016-02-08 16:44:06 -0800103 /**
104 * Name of the {@link Parcelable} extra in the {@link Intent#ACTION_MEDIA_REMOVED},
105 * {@link Intent#ACTION_MEDIA_UNMOUNTED}, {@link Intent#ACTION_MEDIA_CHECKING},
106 * {@link Intent#ACTION_MEDIA_NOFS}, {@link Intent#ACTION_MEDIA_MOUNTED},
107 * {@link Intent#ACTION_MEDIA_SHARED}, {@link Intent#ACTION_MEDIA_BAD_REMOVAL},
108 * {@link Intent#ACTION_MEDIA_UNMOUNTABLE}, and {@link Intent#ACTION_MEDIA_EJECT} broadcast that
109 * contains a {@link StorageVolume}.
110 */
111 // Also sent on ACTION_MEDIA_UNSHARED, which is @hide
112 public static final String EXTRA_STORAGE_VOLUME = "android.os.storage.extra.STORAGE_VOLUME";
Mike Lockwooda5250c92011-05-23 13:44:04 -0400113
Felipe Leme34a9d522016-02-17 10:12:04 -0800114 /**
115 * Name of the String extra used by {@link #createAccessIntent(String) createAccessIntent}.
116 *
117 * @hide
118 */
119 public static final String EXTRA_DIRECTORY_NAME = "android.os.storage.extra.DIRECTORY_NAME";
120
121 /**
122 * Name of the intent used by {@link #createAccessIntent(String) createAccessIntent}.
123 */
124 private static final String ACTION_OPEN_EXTERNAL_DIRECTORY =
125 "android.os.storage.action.OPEN_EXTERNAL_DIRECTORY";
126
Felipe Leme04a5d402016-02-08 16:44:06 -0800127 /** {@hide} */
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700128 public static final int STORAGE_ID_INVALID = 0x00000000;
Felipe Leme04a5d402016-02-08 16:44:06 -0800129 /** {@hide} */
Jeff Sharkey5af1835d2015-07-07 17:26:59 -0700130 public static final int STORAGE_ID_PRIMARY = 0x00010001;
131
Felipe Leme04a5d402016-02-08 16:44:06 -0800132 /** {@hide} */
Jerry Zhang71938e12018-05-10 18:28:29 -0700133 public StorageVolume(String id, File path, File internalPath, String description,
134 boolean primary, boolean removable, boolean emulated, boolean allowMassStorage,
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700135 long maxFileSize, UserHandle owner, String fsUuid, String state) {
136 mId = Preconditions.checkNotNull(id);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700137 mPath = Preconditions.checkNotNull(path);
Jerry Zhang71938e12018-05-10 18:28:29 -0700138 mInternalPath = Preconditions.checkNotNull(internalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700139 mDescription = Preconditions.checkNotNull(description);
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700140 mPrimary = primary;
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700141 mRemovable = removable;
142 mEmulated = emulated;
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700143 mAllowMassStorage = allowMassStorage;
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400144 mMaxFileSize = maxFileSize;
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700145 mOwner = Preconditions.checkNotNull(owner);
146 mFsUuid = fsUuid;
147 mState = Preconditions.checkNotNull(state);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700148 }
149
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700150 private StorageVolume(Parcel in) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700151 mId = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700152 mPath = new File(in.readString());
Jerry Zhang71938e12018-05-10 18:28:29 -0700153 mInternalPath = new File(in.readString());
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700154 mDescription = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700155 mPrimary = in.readInt() != 0;
156 mRemovable = in.readInt() != 0;
157 mEmulated = in.readInt() != 0;
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700158 mAllowMassStorage = in.readInt() != 0;
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700159 mMaxFileSize = in.readLong();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700160 mOwner = in.readParcelable(null);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700161 mFsUuid = in.readString();
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700162 mState = in.readString();
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700163 }
164
Felipe Leme04a5d402016-02-08 16:44:06 -0800165 /** {@hide} */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100166 @UnsupportedAppUsage
Jeff Sharkey48877892015-03-18 11:27:19 -0700167 public String getId() {
168 return mId;
Mike Lockwoodfbfe5552011-05-17 17:19:37 -0400169 }
170
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700171 /**
172 * Returns the mount path for the volume.
173 *
174 * @return the mount path
Felipe Leme04a5d402016-02-08 16:44:06 -0800175 * @hide
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700176 */
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700177 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
Jeff Sharkeyc6091162018-06-29 17:15:40 -0600178 @TestApi
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700179 public String getPath() {
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700180 return mPath.toString();
181 }
182
Jerry Zhang71938e12018-05-10 18:28:29 -0700183 /**
184 * Returns the path of the underlying filesystem.
185 *
186 * @return the internal path
187 * @hide
188 */
189 public String getInternalPath() {
190 return mInternalPath.toString();
191 }
192
Felipe Leme04a5d402016-02-08 16:44:06 -0800193 /** {@hide} */
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700194 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "{@link StorageVolume#getDirectory()}")
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700195 public File getPathFile() {
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700196 return mPath;
197 }
198
199 /**
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700200 * Returns the directory where this volume is currently mounted.
201 * <p>
202 * Direct filesystem access via this path has significant emulation
203 * overhead, and apps are instead strongly encouraged to interact with media
204 * on storage volumes via the {@link MediaStore} APIs.
205 * <p>
206 * This directory does not give apps any additional access beyond what they
207 * already have via {@link MediaStore}.
208 *
209 * @return directory where this volume is mounted, or {@code null} if the
210 * volume is not currently mounted.
211 */
212 public @Nullable File getDirectory() {
213 switch (mState) {
214 case Environment.MEDIA_MOUNTED:
215 case Environment.MEDIA_MOUNTED_READ_ONLY:
216 return mPath;
217 default:
218 return null;
219 }
220 }
221
222 /**
Felipe Leme04a5d402016-02-08 16:44:06 -0800223 * Returns a user-visible description of the volume.
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700224 *
225 * @return the volume description
226 */
Fabrice Di Meglio13fe2a52012-05-18 18:08:58 -0700227 public String getDescription(Context context) {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700228 return mDescription;
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700229 }
230
Felipe Leme04a5d402016-02-08 16:44:06 -0800231 /**
232 * Returns true if the volume is the primary shared/external storage, which is the volume
233 * backed by {@link Environment#getExternalStorageDirectory()}.
234 */
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700235 public boolean isPrimary() {
236 return mPrimary;
237 }
238
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700239 /**
240 * Returns true if the volume is removable.
241 *
242 * @return is removable
243 */
244 public boolean isRemovable() {
245 return mRemovable;
246 }
247
248 /**
249 * Returns true if the volume is emulated.
250 *
251 * @return is removable
252 */
253 public boolean isEmulated() {
254 return mEmulated;
255 }
256
257 /**
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700258 * Returns true if this volume can be shared via USB mass storage.
259 *
260 * @return whether mass storage is allowed
Felipe Leme04a5d402016-02-08 16:44:06 -0800261 * @hide
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700262 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100263 @UnsupportedAppUsage
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700264 public boolean allowMassStorage() {
265 return mAllowMassStorage;
266 }
267
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400268 /**
269 * Returns maximum file size for the volume, or zero if it is unbounded.
270 *
271 * @return maximum file size
Felipe Leme04a5d402016-02-08 16:44:06 -0800272 * @hide
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400273 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100274 @UnsupportedAppUsage
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400275 public long getMaxFileSize() {
276 return mMaxFileSize;
277 }
278
Felipe Leme04a5d402016-02-08 16:44:06 -0800279 /** {@hide} */
Mathew Inwood8c854f82018-09-14 12:35:36 +0100280 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700281 public UserHandle getOwner() {
282 return mOwner;
283 }
284
Felipe Leme04a5d402016-02-08 16:44:06 -0800285 /**
286 * Gets the volume UUID, if any.
287 */
288 public @Nullable String getUuid() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700289 return mFsUuid;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700290 }
291
Jeff Sharkey04b4ba12019-12-15 22:42:42 -0700292 /**
293 * Return the volume name that can be used to interact with this storage
294 * device through {@link MediaStore}.
295 *
296 * @return opaque volume name, or {@code null} if this volume is not indexed
297 * by {@link MediaStore}.
298 * @see android.provider.MediaStore.Audio.Media#getContentUri(String)
299 * @see android.provider.MediaStore.Video.Media#getContentUri(String)
300 * @see android.provider.MediaStore.Images.Media#getContentUri(String)
301 */
302 public @Nullable String getMediaStoreVolumeName() {
303 if (isPrimary()) {
304 return MediaStore.VOLUME_EXTERNAL_PRIMARY;
305 } else {
306 return getNormalizedUuid();
307 }
308 }
309
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700310 /** {@hide} */
Jeff Sharkey586d3c02019-04-09 23:46:48 -0600311 public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
312 return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
313 }
314
315 /** {@hide} */
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700316 public @Nullable String getNormalizedUuid() {
Jeff Sharkey586d3c02019-04-09 23:46:48 -0600317 return normalizeUuid(mFsUuid);
Jeff Sharkey3f64ec52019-01-22 13:19:40 -0700318 }
319
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700320 /**
321 * Parse and return volume UUID as FAT volume ID, or return -1 if unable to
322 * parse or UUID is unknown.
Felipe Leme04a5d402016-02-08 16:44:06 -0800323 * @hide
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700324 */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100325 @UnsupportedAppUsage
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700326 public int getFatVolumeId() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700327 if (mFsUuid == null || mFsUuid.length() != 9) {
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700328 return -1;
329 }
330 try {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700331 return (int) Long.parseLong(mFsUuid.replace("-", ""), 16);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700332 } catch (NumberFormatException e) {
333 return -1;
334 }
335 }
336
Felipe Leme04a5d402016-02-08 16:44:06 -0800337 /** {@hide} */
Mathew Inwood98e9ad12018-08-30 13:11:50 +0100338 @UnsupportedAppUsage
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700339 public String getUserLabel() {
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700340 return mDescription;
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700341 }
342
Felipe Leme04a5d402016-02-08 16:44:06 -0800343 /**
344 * Returns the current state of the volume.
345 *
346 * @return one of {@link Environment#MEDIA_UNKNOWN}, {@link Environment#MEDIA_REMOVED},
347 * {@link Environment#MEDIA_UNMOUNTED}, {@link Environment#MEDIA_CHECKING},
348 * {@link Environment#MEDIA_NOFS}, {@link Environment#MEDIA_MOUNTED},
349 * {@link Environment#MEDIA_MOUNTED_READ_ONLY}, {@link Environment#MEDIA_SHARED},
350 * {@link Environment#MEDIA_BAD_REMOVAL}, or {@link Environment#MEDIA_UNMOUNTABLE}.
351 */
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700352 public String getState() {
353 return mState;
354 }
355
Felipe Leme04a5d402016-02-08 16:44:06 -0800356 /**
Felipe Lemedb892b82016-03-17 18:56:20 -0700357 * Builds an intent to give access to a standard storage directory or entire volume after
358 * obtaining the user's approval.
Felipe Leme04a5d402016-02-08 16:44:06 -0800359 * <p>
360 * When invoked, the system will ask the user to grant access to the requested directory (and
361 * its descendants). The result of the request will be returned to the activity through the
362 * {@code onActivityResult} method.
363 * <p>
364 * To gain access to descendants (child, grandchild, etc) documents, use
365 * {@link DocumentsContract#buildDocumentUriUsingTree(Uri, String)}, or
366 * {@link DocumentsContract#buildChildDocumentsUriUsingTree(Uri, String)} with the returned URI.
Felipe Leme2ac87692016-03-29 19:06:02 -0700367 * <p>
368 * If your application only needs to store internal data, consider using
Felipe Leme04a5d402016-02-08 16:44:06 -0800369 * {@link Context#getExternalFilesDirs(String) Context.getExternalFilesDirs},
Felipe Leme2ac87692016-03-29 19:06:02 -0700370 * {@link Context#getExternalCacheDirs()}, or {@link Context#getExternalMediaDirs()}, which
371 * require no permissions to read or write.
372 * <p>
373 * Access to the entire volume is only available for non-primary volumes (for the primary
374 * volume, apps can use the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} and
375 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} permissions) and should be used
376 * with caution, since users are more likely to deny access when asked for entire volume access
377 * rather than specific directories.
Felipe Leme04a5d402016-02-08 16:44:06 -0800378 *
Felipe Leme2ac87692016-03-29 19:06:02 -0700379 * @param directoryName must be one of {@link Environment#DIRECTORY_MUSIC},
380 * {@link Environment#DIRECTORY_PODCASTS}, {@link Environment#DIRECTORY_RINGTONES},
381 * {@link Environment#DIRECTORY_ALARMS}, {@link Environment#DIRECTORY_NOTIFICATIONS},
382 * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES},
383 * {@link Environment#DIRECTORY_DOWNLOADS}, {@link Environment#DIRECTORY_DCIM}, or
Garfield Tan92b96ba2016-11-01 14:33:48 -0700384 * {@link Environment#DIRECTORY_DOCUMENTS}, or {@code null} to request access to the
Felipe Leme2ac87692016-03-29 19:06:02 -0700385 * entire volume.
386 * @return intent to request access, or {@code null} if the requested directory is invalid for
387 * that volume.
Felipe Leme04a5d402016-02-08 16:44:06 -0800388 * @see DocumentsContract
Amin Shaikhb49cedf2018-10-23 13:37:19 -0400389 * @deprecated Callers should migrate to using {@link Intent#ACTION_OPEN_DOCUMENT_TREE} instead.
390 * Launching this {@link Intent} on devices running
391 * {@link android.os.Build.VERSION_CODES#Q} or higher, will immediately finish
392 * with a result code of {@link android.app.Activity#RESULT_CANCELED}.
Felipe Leme04a5d402016-02-08 16:44:06 -0800393 */
Amin Shaikhb49cedf2018-10-23 13:37:19 -0400394 @Deprecated
Felipe Leme2ac87692016-03-29 19:06:02 -0700395 public @Nullable Intent createAccessIntent(String directoryName) {
396 if ((isPrimary() && directoryName == null) ||
397 (directoryName != null && !Environment.isStandardDirectory(directoryName))) {
398 return null;
399 }
Felipe Leme34a9d522016-02-17 10:12:04 -0800400 final Intent intent = new Intent(ACTION_OPEN_EXTERNAL_DIRECTORY);
401 intent.putExtra(EXTRA_STORAGE_VOLUME, this);
402 intent.putExtra(EXTRA_DIRECTORY_NAME, directoryName);
Felipe Leme04a5d402016-02-08 16:44:06 -0800403 return intent;
404 }
405
Amin Shaikh305e87e2018-11-19 12:33:27 -0500406 /**
407 * Builds an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} to allow the user to grant access to any
408 * directory subtree (or entire volume) from the {@link android.provider.DocumentsProvider}s
409 * available on the device. The initial location of the document navigation will be the root of
410 * this {@link StorageVolume}.
411 *
412 * Note that the returned {@link Intent} simply suggests that the user picks this {@link
413 * StorageVolume} by default, but the user may select a different location. Callers must respect
414 * the user's chosen location, even if it is different from the originally requested location.
415 *
416 * @return intent to {@link Intent#ACTION_OPEN_DOCUMENT_TREE} initially showing the contents
417 * of this {@link StorageVolume}
418 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
419 */
420 @NonNull public Intent createOpenDocumentTreeIntent() {
421 final String rootId = isEmulated()
422 ? DocumentsContract.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID
423 : mFsUuid;
424 final Uri rootUri = DocumentsContract.buildRootUri(
425 DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, rootId);
426 final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
427 .putExtra(DocumentsContract.EXTRA_INITIAL_URI, rootUri)
428 .putExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, true);
429 return intent;
430 }
431
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700432 @Override
433 public boolean equals(Object obj) {
434 if (obj instanceof StorageVolume && mPath != null) {
435 StorageVolume volume = (StorageVolume)obj;
436 return (mPath.equals(volume.mPath));
437 }
438 return false;
439 }
440
441 @Override
442 public int hashCode() {
443 return mPath.hashCode();
444 }
445
446 @Override
447 public String toString() {
Felipe Leme04a5d402016-02-08 16:44:06 -0800448 final StringBuilder buffer = new StringBuilder("StorageVolume: ").append(mDescription);
449 if (mFsUuid != null) {
450 buffer.append(" (").append(mFsUuid).append(")");
451 }
452 return buffer.toString();
453 }
454
455 /** {@hide} */
Felipe Leme104b9322017-03-14 08:40:06 -0700456 // TODO: find out where toString() is called internally and replace these calls by dump().
Felipe Leme04a5d402016-02-08 16:44:06 -0800457 public String dump() {
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700458 final CharArrayWriter writer = new CharArrayWriter();
459 dump(new IndentingPrintWriter(writer, " ", 80));
460 return writer.toString();
461 }
462
Felipe Leme04a5d402016-02-08 16:44:06 -0800463 /** {@hide} */
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700464 public void dump(IndentingPrintWriter pw) {
465 pw.println("StorageVolume:");
466 pw.increaseIndent();
Jeff Sharkey48877892015-03-18 11:27:19 -0700467 pw.printPair("mId", mId);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700468 pw.printPair("mPath", mPath);
Jerry Zhang71938e12018-05-10 18:28:29 -0700469 pw.printPair("mInternalPath", mInternalPath);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700470 pw.printPair("mDescription", mDescription);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700471 pw.printPair("mPrimary", mPrimary);
472 pw.printPair("mRemovable", mRemovable);
473 pw.printPair("mEmulated", mEmulated);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700474 pw.printPair("mAllowMassStorage", mAllowMassStorage);
475 pw.printPair("mMaxFileSize", mMaxFileSize);
476 pw.printPair("mOwner", mOwner);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700477 pw.printPair("mFsUuid", mFsUuid);
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700478 pw.printPair("mState", mState);
Jeff Sharkey5aca2b82013-10-16 16:21:54 -0700479 pw.decreaseIndent();
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700480 }
481
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700482 public static final @android.annotation.NonNull Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700483 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700484 public StorageVolume createFromParcel(Parcel in) {
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700485 return new StorageVolume(in);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700486 }
487
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700488 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700489 public StorageVolume[] newArray(int size) {
490 return new StorageVolume[size];
491 }
492 };
493
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700494 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700495 public int describeContents() {
496 return 0;
497 }
498
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700499 @Override
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700500 public void writeToParcel(Parcel parcel, int flags) {
Jeff Sharkey48877892015-03-18 11:27:19 -0700501 parcel.writeString(mId);
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700502 parcel.writeString(mPath.toString());
Jerry Zhang71938e12018-05-10 18:28:29 -0700503 parcel.writeString(mInternalPath.toString());
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700504 parcel.writeString(mDescription);
Jeff Sharkey9545dc022012-09-06 22:46:30 -0700505 parcel.writeInt(mPrimary ? 1 : 0);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700506 parcel.writeInt(mRemovable ? 1 : 0);
507 parcel.writeInt(mEmulated ? 1 : 0);
Mike Lockwood8e8b2802011-06-07 08:03:33 -0700508 parcel.writeInt(mAllowMassStorage ? 1 : 0);
Mike Lockwood7a59dd22011-07-11 09:18:03 -0400509 parcel.writeLong(mMaxFileSize);
Jeff Sharkeyb049e212012-09-07 23:16:01 -0700510 parcel.writeParcelable(mOwner, flags);
Jeff Sharkey1b8ef7e2015-04-03 17:14:45 -0700511 parcel.writeString(mFsUuid);
Jeff Sharkey1f706c62013-10-17 10:52:17 -0700512 parcel.writeString(mState);
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700513 }
Mike Lockwood2f6a3882011-05-09 19:08:06 -0700514}