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