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