RootInfo.java revision a5588b65d55bc1b8b5ba943f8b660db26a7eac5c
1/* 2 * Copyright (C) 2013 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 com.android.documentsui.model; 18 19import static com.android.documentsui.Shared.DEBUG; 20import static com.android.documentsui.Shared.compareToIgnoreCaseNullable; 21import static com.android.documentsui.model.DocumentInfo.getCursorInt; 22import static com.android.documentsui.model.DocumentInfo.getCursorLong; 23import static com.android.documentsui.model.DocumentInfo.getCursorString; 24 25import android.annotation.IntDef; 26import android.annotation.Nullable; 27import android.content.Context; 28import android.database.Cursor; 29import android.graphics.drawable.Drawable; 30import android.net.Uri; 31import android.os.Parcel; 32import android.os.Parcelable; 33import android.provider.DocumentsContract; 34import android.provider.DocumentsContract.Root; 35import android.text.TextUtils; 36import android.util.Log; 37 38import com.android.documentsui.IconUtils; 39import com.android.documentsui.R; 40 41import java.io.DataInputStream; 42import java.io.DataOutputStream; 43import java.io.FileNotFoundException; 44import java.io.IOException; 45import java.lang.annotation.Retention; 46import java.lang.annotation.RetentionPolicy; 47import java.net.ProtocolException; 48import java.util.Objects; 49 50/** 51 * Representation of a {@link Root}. 52 */ 53public class RootInfo implements Durable, Parcelable, Comparable<RootInfo> { 54 55 private static final String TAG = "RootInfo"; 56 private static final int VERSION_INIT = 1; 57 private static final int VERSION_DROP_TYPE = 2; 58 59 // The values of these constants determine the sort order of various roots in the RootsFragment. 60 @IntDef(flag = false, value = { 61 TYPE_IMAGES, 62 TYPE_VIDEO, 63 TYPE_AUDIO, 64 TYPE_RECENTS, 65 TYPE_DOWNLOADS, 66 TYPE_LOCAL, 67 TYPE_MTP, 68 TYPE_SD, 69 TYPE_USB, 70 TYPE_OTHER 71 }) 72 @Retention(RetentionPolicy.SOURCE) 73 public @interface RootType {} 74 public static final int TYPE_IMAGES = 1; 75 public static final int TYPE_VIDEO = 2; 76 public static final int TYPE_AUDIO = 3; 77 public static final int TYPE_RECENTS = 4; 78 public static final int TYPE_DOWNLOADS = 5; 79 public static final int TYPE_LOCAL = 6; 80 public static final int TYPE_MTP = 7; 81 public static final int TYPE_SD = 8; 82 public static final int TYPE_USB = 9; 83 public static final int TYPE_OTHER = 10; 84 85 public String authority; 86 public String rootId; 87 public int flags; 88 public int icon; 89 public String title; 90 public String summary; 91 public String documentId; 92 public long availableBytes; 93 public String mimeTypes; 94 95 /** Derived fields that aren't persisted */ 96 public String[] derivedMimeTypes; 97 public int derivedIcon; 98 public @RootType int derivedType; 99 // Currently, we are not persisting this and we should be asking Provider whether a Root 100 // is in the process of eject. Provider does not have this available yet. 101 public transient boolean ejecting; 102 103 public RootInfo() { 104 reset(); 105 } 106 107 @Override 108 public void reset() { 109 authority = null; 110 rootId = null; 111 flags = 0; 112 icon = 0; 113 title = null; 114 summary = null; 115 documentId = null; 116 availableBytes = -1; 117 mimeTypes = null; 118 ejecting = false; 119 120 derivedMimeTypes = null; 121 derivedIcon = 0; 122 derivedType = 0; 123 } 124 125 @Override 126 public void read(DataInputStream in) throws IOException { 127 final int version = in.readInt(); 128 switch (version) { 129 case VERSION_DROP_TYPE: 130 authority = DurableUtils.readNullableString(in); 131 rootId = DurableUtils.readNullableString(in); 132 flags = in.readInt(); 133 icon = in.readInt(); 134 title = DurableUtils.readNullableString(in); 135 summary = DurableUtils.readNullableString(in); 136 documentId = DurableUtils.readNullableString(in); 137 availableBytes = in.readLong(); 138 mimeTypes = DurableUtils.readNullableString(in); 139 deriveFields(); 140 break; 141 default: 142 throw new ProtocolException("Unknown version " + version); 143 } 144 } 145 146 @Override 147 public void write(DataOutputStream out) throws IOException { 148 out.writeInt(VERSION_DROP_TYPE); 149 DurableUtils.writeNullableString(out, authority); 150 DurableUtils.writeNullableString(out, rootId); 151 out.writeInt(flags); 152 out.writeInt(icon); 153 DurableUtils.writeNullableString(out, title); 154 DurableUtils.writeNullableString(out, summary); 155 DurableUtils.writeNullableString(out, documentId); 156 out.writeLong(availableBytes); 157 DurableUtils.writeNullableString(out, mimeTypes); 158 } 159 160 @Override 161 public int describeContents() { 162 return 0; 163 } 164 165 @Override 166 public void writeToParcel(Parcel dest, int flags) { 167 DurableUtils.writeToParcel(dest, this); 168 } 169 170 public static final Creator<RootInfo> CREATOR = new Creator<RootInfo>() { 171 @Override 172 public RootInfo createFromParcel(Parcel in) { 173 final RootInfo root = new RootInfo(); 174 DurableUtils.readFromParcel(in, root); 175 return root; 176 } 177 178 @Override 179 public RootInfo[] newArray(int size) { 180 return new RootInfo[size]; 181 } 182 }; 183 184 public static RootInfo fromRootsCursor(String authority, Cursor cursor) { 185 final RootInfo root = new RootInfo(); 186 root.authority = authority; 187 root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID); 188 root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS); 189 root.icon = getCursorInt(cursor, Root.COLUMN_ICON); 190 root.title = getCursorString(cursor, Root.COLUMN_TITLE); 191 root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY); 192 root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID); 193 root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES); 194 root.mimeTypes = getCursorString(cursor, Root.COLUMN_MIME_TYPES); 195 root.deriveFields(); 196 return root; 197 } 198 199 private void deriveFields() { 200 derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null; 201 202 if (isHome()) { 203 derivedType = TYPE_LOCAL; 204 derivedIcon = R.drawable.ic_root_documents; 205 } else if (isMtp()) { 206 derivedType = TYPE_MTP; 207 derivedIcon = R.drawable.ic_usb_storage; 208 } else if (isUsb()) { 209 derivedType = TYPE_USB; 210 derivedIcon = R.drawable.ic_usb_storage; 211 } else if (isSd()) { 212 derivedType = TYPE_SD; 213 derivedIcon = R.drawable.ic_sd_storage; 214 } else if (isExternalStorage()) { 215 derivedType = TYPE_LOCAL; 216 derivedIcon = R.drawable.ic_root_smartphone; 217 } else if (isDownloads()) { 218 derivedType = TYPE_DOWNLOADS; 219 derivedIcon = R.drawable.ic_root_download; 220 } else if (isImages()) { 221 derivedType = TYPE_IMAGES; 222 derivedIcon = com.android.internal.R.drawable.ic_doc_image; 223 } else if (isVideos()) { 224 derivedType = TYPE_VIDEO; 225 derivedIcon = com.android.internal.R.drawable.ic_doc_video; 226 } else if (isAudio()) { 227 derivedType = TYPE_AUDIO; 228 derivedIcon = com.android.internal.R.drawable.ic_doc_audio; 229 } else if (isRecents()) { 230 derivedType = TYPE_RECENTS; 231 } else { 232 derivedType = TYPE_OTHER; 233 } 234 235 if (DEBUG) Log.d(TAG, "Finished deriving fields: " + this); 236 } 237 238 public Uri getUri() { 239 return DocumentsContract.buildRootUri(authority, rootId); 240 } 241 242 public boolean isRecents() { 243 return authority == null && rootId == null; 244 } 245 246 public boolean isHome() { 247 // Note that "home" is the expected root id for the auto-created 248 // user home directory on external storage. The "home" value should 249 // match ExternalStorageProvider.ROOT_ID_HOME. 250 return isExternalStorage() && "home".equals(rootId); 251 } 252 253 public boolean isExternalStorage() { 254 return "com.android.externalstorage.documents".equals(authority); 255 } 256 257 public boolean isDownloads() { 258 return "com.android.providers.downloads.documents".equals(authority); 259 } 260 261 public boolean isImages() { 262 return "com.android.providers.media.documents".equals(authority) 263 && "images_root".equals(rootId); 264 } 265 266 public boolean isVideos() { 267 return "com.android.providers.media.documents".equals(authority) 268 && "videos_root".equals(rootId); 269 } 270 271 public boolean isAudio() { 272 return "com.android.providers.media.documents".equals(authority) 273 && "audio_root".equals(rootId); 274 } 275 276 public boolean isMtp() { 277 return "com.android.mtp.documents".equals(authority); 278 } 279 280 public boolean isLibrary() { 281 return derivedType == TYPE_IMAGES 282 || derivedType == TYPE_VIDEO 283 || derivedType == TYPE_AUDIO 284 || derivedType == TYPE_RECENTS; 285 } 286 287 public boolean hasSettings() { 288 return (flags & Root.FLAG_HAS_SETTINGS) != 0; 289 } 290 291 public boolean supportsChildren() { 292 return (flags & Root.FLAG_SUPPORTS_IS_CHILD) != 0; 293 } 294 295 public boolean supportsCreate() { 296 return (flags & Root.FLAG_SUPPORTS_CREATE) != 0; 297 } 298 299 public boolean supportsRecents() { 300 return (flags & Root.FLAG_SUPPORTS_RECENTS) != 0; 301 } 302 303 public boolean supportsSearch() { 304 return (flags & Root.FLAG_SUPPORTS_SEARCH) != 0; 305 } 306 307 public boolean supportsEject() { 308 return (flags & Root.FLAG_SUPPORTS_EJECT) != 0; 309 } 310 311 public boolean isAdvanced() { 312 return (flags & Root.FLAG_ADVANCED) != 0; 313 } 314 315 public boolean isLocalOnly() { 316 return (flags & Root.FLAG_LOCAL_ONLY) != 0; 317 } 318 319 public boolean isEmpty() { 320 return (flags & Root.FLAG_EMPTY) != 0; 321 } 322 323 public boolean isSd() { 324 return (flags & Root.FLAG_REMOVABLE_SD) != 0; 325 } 326 327 public boolean isUsb() { 328 return (flags & Root.FLAG_REMOVABLE_USB) != 0; 329 } 330 331 public Drawable loadIcon(Context context) { 332 if (derivedIcon != 0) { 333 return context.getDrawable(derivedIcon); 334 } else { 335 return IconUtils.loadPackageIcon(context, authority, icon); 336 } 337 } 338 339 public Drawable loadDrawerIcon(Context context) { 340 if (derivedIcon != 0) { 341 return IconUtils.applyTintColor(context, derivedIcon, R.color.item_root_icon); 342 } else { 343 return IconUtils.loadPackageIcon(context, authority, icon); 344 } 345 } 346 347 public Drawable loadEjectIcon(Context context) { 348 return IconUtils.applyTintColor(context, R.drawable.ic_eject, R.color.item_eject_icon); 349 } 350 351 /** 352 * Gets the {@link DocumentInfo} of the root folder of this root. 353 */ 354 public @Nullable DocumentInfo getRootDocumentBlocking(Context context) { 355 try { 356 final Uri uri = DocumentsContract.buildDocumentUri(authority, documentId); 357 return DocumentInfo.fromUri(context.getContentResolver(), uri); 358 } catch (FileNotFoundException e) { 359 Log.w(TAG, "Failed to find root", e); 360 return null; 361 } 362 } 363 364 @Override 365 public boolean equals(Object o) { 366 if (o == null) { 367 return false; 368 } 369 370 if (this == o) { 371 return true; 372 } 373 374 if (o instanceof RootInfo) { 375 RootInfo other = (RootInfo) o; 376 return Objects.equals(authority, other.authority) 377 && Objects.equals(rootId, other.rootId); 378 } 379 380 return false; 381 } 382 383 @Override 384 public int hashCode() { 385 return Objects.hash(authority, rootId); 386 } 387 388 @Override 389 public int compareTo(RootInfo other) { 390 // Sort by root type, then title, then summary. 391 int score = derivedType - other.derivedType; 392 if (score != 0) { 393 return score; 394 } 395 396 score = compareToIgnoreCaseNullable(title, other.title); 397 if (score != 0) { 398 return score; 399 } 400 401 return compareToIgnoreCaseNullable(summary, other.summary); 402 } 403 404 @Override 405 public String toString() { 406 return "Root{" 407 + "authority=" + authority 408 + ", rootId=" + rootId 409 + ", title=" + title 410 + ", isUsb=" + isUsb() 411 + ", isSd=" + isSd() 412 + ", isMtp=" + isMtp() 413 + "}"; 414 } 415 416 public String getDirectoryString() { 417 return !TextUtils.isEmpty(summary) ? summary : title; 418 } 419} 420