VolumeInfo.java revision 56bd3129138b525b0f2eba52bd4fa140f23e792c
1/* 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 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.content.Context; 22import android.content.Intent; 23import android.content.res.Resources; 24import android.mtp.MtpStorage; 25import android.net.Uri; 26import android.os.Environment; 27import android.os.Parcel; 28import android.os.Parcelable; 29import android.os.UserHandle; 30import android.provider.DocumentsContract; 31import android.text.TextUtils; 32import android.util.ArrayMap; 33import android.util.DebugUtils; 34import android.util.SparseArray; 35 36import com.android.internal.util.IndentingPrintWriter; 37import com.android.internal.util.Preconditions; 38 39import java.io.CharArrayWriter; 40import java.io.File; 41 42/** 43 * Information about a storage volume that may be mounted. A volume may be a 44 * partition on a physical {@link DiskInfo}, an emulated volume above some other 45 * storage medium, or a standalone container like an ASEC or OBB. 46 * 47 * @hide 48 */ 49public class VolumeInfo implements Parcelable { 50 public static final String EXTRA_VOLUME_ID = "android.os.storage.extra.VOLUME_ID"; 51 52 /** Stub volume representing internal private storage */ 53 public static final String ID_PRIVATE_INTERNAL = "private"; 54 /** Real volume representing internal emulated storage */ 55 public static final String ID_EMULATED_INTERNAL = "emulated"; 56 57 public static final int TYPE_PUBLIC = 0; 58 public static final int TYPE_PRIVATE = 1; 59 public static final int TYPE_EMULATED = 2; 60 public static final int TYPE_ASEC = 3; 61 public static final int TYPE_OBB = 4; 62 63 public static final int STATE_UNMOUNTED = 0; 64 public static final int STATE_MOUNTING = 1; 65 public static final int STATE_MOUNTED = 2; 66 public static final int STATE_FORMATTING = 3; 67 public static final int STATE_UNMOUNTING = 4; 68 public static final int STATE_UNMOUNTABLE = 5; 69 public static final int STATE_REMOVED = 6; 70 71 public static final int FLAG_PRIMARY = 1 << 0; 72 public static final int FLAG_VISIBLE = 1 << 1; 73 74 private static SparseArray<String> sStateToEnvironment = new SparseArray<>(); 75 private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>(); 76 77 static { 78 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED); 79 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTING, Environment.MEDIA_CHECKING); 80 sStateToEnvironment.put(VolumeInfo.STATE_MOUNTED, Environment.MEDIA_MOUNTED); 81 sStateToEnvironment.put(VolumeInfo.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED); 82 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTING, Environment.MEDIA_EJECTING); 83 sStateToEnvironment.put(VolumeInfo.STATE_UNMOUNTABLE, Environment.MEDIA_UNMOUNTABLE); 84 sStateToEnvironment.put(VolumeInfo.STATE_REMOVED, Environment.MEDIA_REMOVED); 85 86 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED); 87 sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING); 88 sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED); 89 sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT); 90 sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTABLE, Intent.ACTION_MEDIA_UNMOUNTABLE); 91 sEnvironmentToBroadcast.put(Environment.MEDIA_REMOVED, Intent.ACTION_MEDIA_REMOVED); 92 } 93 94 /** vold state */ 95 public final String id; 96 public final int type; 97 public int flags = 0; 98 public int userId = -1; 99 public int state = STATE_UNMOUNTED; 100 public String fsType; 101 public String fsUuid; 102 public String fsLabel; 103 public String path; 104 105 /** Framework state */ 106 public final int mtpIndex; 107 public String nickname; 108 public String diskId; 109 110 public VolumeInfo(String id, int type, int mtpIndex) { 111 this.id = Preconditions.checkNotNull(id); 112 this.type = type; 113 this.mtpIndex = mtpIndex; 114 } 115 116 public VolumeInfo(Parcel parcel) { 117 id = parcel.readString(); 118 type = parcel.readInt(); 119 flags = parcel.readInt(); 120 userId = parcel.readInt(); 121 state = parcel.readInt(); 122 fsType = parcel.readString(); 123 fsUuid = parcel.readString(); 124 fsLabel = parcel.readString(); 125 path = parcel.readString(); 126 mtpIndex = parcel.readInt(); 127 nickname = parcel.readString(); 128 diskId = parcel.readString(); 129 } 130 131 public static @NonNull String getEnvironmentForState(int state) { 132 final String envState = sStateToEnvironment.get(state); 133 if (envState != null) { 134 return envState; 135 } else { 136 return Environment.MEDIA_UNKNOWN; 137 } 138 } 139 140 public static @Nullable String getBroadcastForEnvironment(String envState) { 141 return sEnvironmentToBroadcast.get(envState); 142 } 143 144 public static @Nullable String getBroadcastForState(int state) { 145 return getBroadcastForEnvironment(getEnvironmentForState(state)); 146 } 147 148 public @Nullable String getDescription() { 149 if (ID_PRIVATE_INTERNAL.equals(id)) { 150 return Resources.getSystem().getString(com.android.internal.R.string.storage_internal); 151 } else if (!TextUtils.isEmpty(nickname)) { 152 return nickname; 153 } else if (!TextUtils.isEmpty(fsLabel)) { 154 return fsLabel; 155 } else { 156 return null; 157 } 158 } 159 160 public boolean isPrimary() { 161 return (flags & FLAG_PRIMARY) != 0; 162 } 163 164 public boolean isVisible() { 165 return (flags & FLAG_VISIBLE) != 0; 166 } 167 168 public boolean isVisibleToUser(int userId) { 169 if (type == TYPE_PUBLIC && userId == this.userId) { 170 return isVisible(); 171 } else if (type == TYPE_EMULATED) { 172 return isVisible(); 173 } else { 174 return false; 175 } 176 } 177 178 public File getPathForUser(int userId) { 179 if (path == null) { 180 return null; 181 } else if (type == TYPE_PUBLIC && userId == this.userId) { 182 return new File(path); 183 } else if (type == TYPE_EMULATED) { 184 return new File(path, Integer.toString(userId)); 185 } else { 186 return null; 187 } 188 } 189 190 public StorageVolume buildStorageVolume(Context context, int userId) { 191 final boolean removable; 192 final boolean emulated; 193 final boolean allowMassStorage = false; 194 final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex); 195 final String envState = getEnvironmentForState(state); 196 197 File userPath = getPathForUser(userId); 198 if (userPath == null) { 199 userPath = new File("/dev/null"); 200 } 201 202 String description = getDescription(); 203 if (description == null) { 204 description = context.getString(android.R.string.unknownName); 205 } 206 207 long mtpReserveSize = 0; 208 long maxFileSize = 0; 209 210 if (type == TYPE_EMULATED) { 211 emulated = true; 212 mtpReserveSize = StorageManager.from(context).getStorageLowBytes(userPath); 213 214 if (ID_EMULATED_INTERNAL.equals(id)) { 215 removable = false; 216 } else { 217 removable = true; 218 } 219 220 } else if (type == TYPE_PUBLIC) { 221 emulated = false; 222 removable = true; 223 224 if ("vfat".equals(fsType)) { 225 maxFileSize = 4294967295L; 226 } 227 228 } else { 229 throw new IllegalStateException("Unexpected volume type " + type); 230 } 231 232 return new StorageVolume(id, mtpStorageId, userPath, description, isPrimary(), removable, 233 emulated, mtpReserveSize, allowMassStorage, maxFileSize, new UserHandle(userId), 234 fsUuid, envState); 235 } 236 237 // TODO: avoid this layering violation 238 private static final String DOCUMENT_AUTHORITY = "com.android.externalstorage.documents"; 239 private static final String DOCUMENT_ROOT_PRIMARY_EMULATED = "primary"; 240 241 /** 242 * Build an intent to browse the contents of this volume. Only valid for 243 * {@link #TYPE_EMULATED} or {@link #TYPE_PUBLIC}. 244 */ 245 public Intent buildBrowseIntent() { 246 final Uri uri; 247 if (type == VolumeInfo.TYPE_PUBLIC) { 248 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, fsUuid); 249 } else if (VolumeInfo.ID_EMULATED_INTERNAL.equals(id)) { 250 uri = DocumentsContract.buildRootUri(DOCUMENT_AUTHORITY, 251 DOCUMENT_ROOT_PRIMARY_EMULATED); 252 } else if (type == VolumeInfo.TYPE_EMULATED) { 253 // TODO: build intent once supported 254 uri = null; 255 } else { 256 throw new IllegalArgumentException(); 257 } 258 259 final Intent intent = new Intent(DocumentsContract.ACTION_BROWSE_DOCUMENT_ROOT); 260 intent.addCategory(Intent.CATEGORY_DEFAULT); 261 intent.setData(uri); 262 return intent; 263 } 264 265 @Override 266 public String toString() { 267 final CharArrayWriter writer = new CharArrayWriter(); 268 dump(new IndentingPrintWriter(writer, " ", 80)); 269 return writer.toString(); 270 } 271 272 public void dump(IndentingPrintWriter pw) { 273 pw.println("VolumeInfo:"); 274 pw.increaseIndent(); 275 pw.printPair("id", id); 276 pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type)); 277 pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags)); 278 pw.printPair("userId", userId); 279 pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state)); 280 pw.println(); 281 pw.printPair("fsType", fsType); 282 pw.printPair("fsUuid", fsUuid); 283 pw.printPair("fsLabel", fsLabel); 284 pw.println(); 285 pw.printPair("path", path); 286 pw.printPair("mtpIndex", mtpIndex); 287 pw.printPair("nickname", nickname); 288 pw.printPair("diskId", diskId); 289 pw.decreaseIndent(); 290 pw.println(); 291 } 292 293 @Override 294 public VolumeInfo clone() { 295 final Parcel temp = Parcel.obtain(); 296 try { 297 writeToParcel(temp, 0); 298 temp.setDataPosition(0); 299 return CREATOR.createFromParcel(temp); 300 } finally { 301 temp.recycle(); 302 } 303 } 304 305 public static final Creator<VolumeInfo> CREATOR = new Creator<VolumeInfo>() { 306 @Override 307 public VolumeInfo createFromParcel(Parcel in) { 308 return new VolumeInfo(in); 309 } 310 311 @Override 312 public VolumeInfo[] newArray(int size) { 313 return new VolumeInfo[size]; 314 } 315 }; 316 317 @Override 318 public int describeContents() { 319 return 0; 320 } 321 322 @Override 323 public void writeToParcel(Parcel parcel, int flags) { 324 parcel.writeString(id); 325 parcel.writeInt(type); 326 parcel.writeInt(this.flags); 327 parcel.writeInt(userId); 328 parcel.writeInt(state); 329 parcel.writeString(fsType); 330 parcel.writeString(fsUuid); 331 parcel.writeString(fsLabel); 332 parcel.writeString(path); 333 parcel.writeInt(mtpIndex); 334 parcel.writeString(nickname); 335 parcel.writeString(diskId); 336 } 337} 338