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.SharedMinimal.DEBUG;
     20 import static com.android.internal.util.Preconditions.checkArgument;
     21 
     22 import android.content.ContentResolver;
     23 import android.database.Cursor;
     24 import android.os.Parcel;
     25 import android.os.Parcelable;
     26 import android.provider.DocumentsProvider;
     27 import android.util.Log;
     28 
     29 import com.android.documentsui.picker.LastAccessedProvider;
     30 
     31 import java.io.DataInputStream;
     32 import java.io.DataOutputStream;
     33 import java.io.FileNotFoundException;
     34 import java.io.IOException;
     35 import java.net.ProtocolException;
     36 import java.util.Collection;
     37 import java.util.LinkedList;
     38 import java.util.List;
     39 import java.util.Objects;
     40 
     41 import javax.annotation.Nullable;
     42 
     43 /**
     44  * Representation of a stack of {@link DocumentInfo}, usually the result of a
     45  * user-driven traversal.
     46  */
     47 public class DocumentStack implements Durable, Parcelable {
     48 
     49     private static final String TAG = "DocumentStack";
     50 
     51     private static final int VERSION_INIT = 1;
     52     private static final int VERSION_ADD_ROOT = 2;
     53 
     54     private LinkedList<DocumentInfo> mList;
     55     private @Nullable RootInfo mRoot;
     56 
     57     private boolean mStackTouched;
     58 
     59     public DocumentStack() {
     60         mList = new LinkedList<>();
     61     }
     62 
     63     /**
     64      * Creates an instance, and pushes all docs to it in the same order as they're passed as
     65      * parameters, i.e. the last document will be at the top of the stack.
     66      */
     67     public DocumentStack(RootInfo root, DocumentInfo... docs) {
     68         mList = new LinkedList<>();
     69         for (int i = 0; i < docs.length; ++i) {
     70             mList.add(docs[i]);
     71         }
     72 
     73         mRoot = root;
     74     }
     75 
     76     /**
     77      * Same as {@link #DocumentStack(DocumentStack, DocumentInfo...)} except it takes a {@link List}
     78      * instead of an array.
     79      */
     80     public DocumentStack(RootInfo root, List<DocumentInfo> docs) {
     81         mList = new LinkedList<>(docs);
     82         mRoot = root;
     83     }
     84 
     85     /**
     86      * Makes a new copy, and pushes all docs to the new copy in the same order as they're
     87      * passed as parameters, i.e. the last document will be at the top of the stack.
     88      */
     89     public DocumentStack(DocumentStack src, DocumentInfo... docs) {
     90         mList = new LinkedList<>(src.mList);
     91         for (DocumentInfo doc : docs) {
     92             push(doc);
     93         }
     94 
     95         mStackTouched = false;
     96         mRoot = src.mRoot;
     97     }
     98 
     99     public boolean isInitialized() {
    100         return mRoot != null;
    101     }
    102 
    103     public @Nullable RootInfo getRoot() {
    104         return mRoot;
    105     }
    106 
    107     public boolean isEmpty() {
    108         return mList.isEmpty();
    109     }
    110 
    111     public int size() {
    112         return mList.size();
    113     }
    114 
    115     public DocumentInfo peek() {
    116         return mList.peekLast();
    117     }
    118 
    119     /**
    120      * Returns {@link DocumentInfo} at index counted from the bottom of this stack.
    121      */
    122     public DocumentInfo get(int index) {
    123         return mList.get(index);
    124     }
    125 
    126     public void push(DocumentInfo info) {
    127         checkArgument(!mList.contains(info));
    128         if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info);
    129         mList.addLast(info);
    130         mStackTouched = true;
    131     }
    132 
    133     public DocumentInfo pop() {
    134         if (DEBUG) Log.d(TAG, "Popping doc off stack.");
    135         final DocumentInfo result = mList.removeLast();
    136         mStackTouched = true;
    137 
    138         return result;
    139     }
    140 
    141     public void popToRootDocument() {
    142         if (DEBUG) Log.d(TAG, "Popping docs to root folder.");
    143         while (mList.size() > 1) {
    144             mList.removeLast();
    145         }
    146         mStackTouched = true;
    147     }
    148 
    149     public void changeRoot(RootInfo root) {
    150         if (DEBUG) Log.d(TAG, "Root changed to: " + root);
    151         reset();
    152         mRoot = root;
    153     }
    154 
    155     /** This will return true even when the initial location is set.
    156      * To get a read on if the user has changed something, use {@link #hasInitialLocationChanged()}.
    157      */
    158     public boolean hasLocationChanged() {
    159         return mStackTouched;
    160     }
    161 
    162     public String getTitle() {
    163         if (mList.size() == 1 && mRoot != null) {
    164             return mRoot.title;
    165         } else if (mList.size() > 1) {
    166             return peek().displayName;
    167         } else {
    168             return null;
    169         }
    170     }
    171 
    172     public boolean isRecents() {
    173         return mRoot != null && mRoot.isRecents();
    174     }
    175 
    176     /**
    177      * Resets this stack to the given stack. It takes the reference of {@link #mList} and
    178      * {@link #mRoot} instead of making a copy.
    179      */
    180     public void reset(DocumentStack stack) {
    181         if (DEBUG) Log.d(TAG, "Resetting the whole darn stack to: " + stack);
    182 
    183         mList = stack.mList;
    184         mRoot = stack.mRoot;
    185         mStackTouched = true;
    186     }
    187 
    188     @Override
    189     public String toString() {
    190         return "DocumentStack{"
    191                 + "root=" + mRoot
    192                 + ", docStack=" + mList
    193                 + ", stackTouched=" + mStackTouched
    194                 + "}";
    195     }
    196 
    197     @Override
    198     public void reset() {
    199         mList.clear();
    200         mRoot = null;
    201     }
    202 
    203     private void updateRoot(Collection<RootInfo> matchingRoots) throws FileNotFoundException {
    204         for (RootInfo root : matchingRoots) {
    205             // RootInfo's equals() only checks authority and rootId, so this will update RootInfo if
    206             // its flag has changed.
    207             if (root.equals(this.mRoot)) {
    208                 this.mRoot = root;
    209                 return;
    210             }
    211         }
    212         throw new FileNotFoundException("Failed to find matching mRoot for " + mRoot);
    213     }
    214 
    215     /**
    216      * Update a possibly stale restored stack against a live
    217      * {@link DocumentsProvider}.
    218      */
    219     private void updateDocuments(ContentResolver resolver) throws FileNotFoundException {
    220         for (DocumentInfo info : mList) {
    221             info.updateSelf(resolver);
    222         }
    223     }
    224 
    225     public static @Nullable DocumentStack fromLastAccessedCursor(
    226             Cursor cursor, Collection<RootInfo> matchingRoots, ContentResolver resolver)
    227             throws IOException {
    228 
    229         if (cursor.moveToFirst()) {
    230             DocumentStack stack = new DocumentStack();
    231             final byte[] rawStack = cursor.getBlob(
    232                     cursor.getColumnIndex(LastAccessedProvider.Columns.STACK));
    233             DurableUtils.readFromArray(rawStack, stack);
    234 
    235             stack.updateRoot(matchingRoots);
    236             stack.updateDocuments(resolver);
    237 
    238             return stack;
    239         }
    240 
    241         return null;
    242     }
    243 
    244     @Override
    245     public boolean equals(Object o) {
    246         if (this == o) {
    247             return true;
    248         }
    249 
    250         if (!(o instanceof DocumentStack)) {
    251             return false;
    252         }
    253 
    254         DocumentStack other = (DocumentStack) o;
    255         return Objects.equals(mRoot, other.mRoot)
    256                 && mList.equals(other.mList);
    257     }
    258 
    259     @Override
    260     public int hashCode() {
    261         return Objects.hash(mRoot, mList);
    262     }
    263 
    264     @Override
    265     public void read(DataInputStream in) throws IOException {
    266         final int version = in.readInt();
    267         switch (version) {
    268             case VERSION_INIT:
    269                 throw new ProtocolException("Ignored upgrade");
    270             case VERSION_ADD_ROOT:
    271                 if (in.readBoolean()) {
    272                     mRoot = new RootInfo();
    273                     mRoot.read(in);
    274                 }
    275                 final int size = in.readInt();
    276                 for (int i = 0; i < size; i++) {
    277                     final DocumentInfo doc = new DocumentInfo();
    278                     doc.read(in);
    279                     mList.add(doc);
    280                 }
    281                 mStackTouched = in.readInt() != 0;
    282                 break;
    283             default:
    284                 throw new ProtocolException("Unknown version " + version);
    285         }
    286     }
    287 
    288     @Override
    289     public void write(DataOutputStream out) throws IOException {
    290         out.writeInt(VERSION_ADD_ROOT);
    291         if (mRoot != null) {
    292             out.writeBoolean(true);
    293             mRoot.write(out);
    294         } else {
    295             out.writeBoolean(false);
    296         }
    297         final int size = mList.size();
    298         out.writeInt(size);
    299         for (int i = 0; i < size; i++) {
    300             final DocumentInfo doc = mList.get(i);
    301             doc.write(out);
    302         }
    303         out.writeInt(mStackTouched ? 1 : 0);
    304     }
    305 
    306     @Override
    307     public int describeContents() {
    308         return 0;
    309     }
    310 
    311     @Override
    312     public void writeToParcel(Parcel dest, int flags) {
    313         DurableUtils.writeToParcel(dest, this);
    314     }
    315 
    316     public static final Creator<DocumentStack> CREATOR = new Creator<DocumentStack>() {
    317         @Override
    318         public DocumentStack createFromParcel(Parcel in) {
    319             final DocumentStack stack = new DocumentStack();
    320             DurableUtils.readFromParcel(in, stack);
    321             return stack;
    322         }
    323 
    324         @Override
    325         public DocumentStack[] newArray(int size) {
    326             return new DocumentStack[size];
    327         }
    328     };
    329 }
    330