• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
DocumentStack()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      */
DocumentStack(RootInfo root, DocumentInfo... docs)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      */
DocumentStack(RootInfo root, List<DocumentInfo> docs)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      */
DocumentStack(DocumentStack src, DocumentInfo... docs)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 
isInitialized()98     public boolean isInitialized() {
99         return mRoot != null;
100     }
101 
getRoot()102     public @Nullable RootInfo getRoot() {
103         return mRoot;
104     }
105 
isEmpty()106     public boolean isEmpty() {
107         return mList.isEmpty();
108     }
109 
size()110     public int size() {
111         return mList.size();
112     }
113 
peek()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      */
get(int index)121     public DocumentInfo get(int index) {
122         return mList.get(index);
123     }
124 
push(DocumentInfo info)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 
pop()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 
popToRootDocument()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 
changeRoot(RootInfo root)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      */
hasLocationChanged()160     public boolean hasLocationChanged() {
161         return mStackTouched;
162     }
163 
getTitle()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 
isRecents()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      */
reset(DocumentStack stack)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
toString()191     public String toString() {
192         return "DocumentStack{"
193                 + "root=" + mRoot
194                 + ", docStack=" + mList
195                 + ", stackTouched=" + mStackTouched
196                 + "}";
197     }
198 
199     @Override
reset()200     public void reset() {
201         mList.clear();
202         mRoot = null;
203     }
204 
updateRoot(Collection<RootInfo> matchingRoots)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      */
updateDocuments(ContentResolver resolver)221     private void updateDocuments(ContentResolver resolver) throws FileNotFoundException {
222         for (DocumentInfo info : mList) {
223             info.updateSelf(resolver);
224         }
225     }
226 
fromLastAccessedCursor( Cursor cursor, Collection<RootInfo> matchingRoots, ContentResolver resolver)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
equals(Object o)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
hashCode()262     public int hashCode() {
263         return Objects.hash(mRoot, mList);
264     }
265 
266     @Override
read(DataInputStream in)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
write(DataOutputStream out)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
describeContents()309     public int describeContents() {
310         return 0;
311     }
312 
313     @Override
writeToParcel(Parcel dest, int flags)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