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