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