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.model;
18 
19 import android.content.ContentProviderClient;
20 import android.content.ContentResolver;
21 import android.database.Cursor;
22 import android.net.Uri;
23 import android.os.Parcel;
24 import android.os.Parcelable;
25 import android.provider.DocumentsContract;
26 import android.provider.DocumentsContract.Document;
27 import android.provider.DocumentsProvider;
28 import android.support.annotation.VisibleForTesting;
29 
30 import com.android.documentsui.DocumentsApplication;
31 import com.android.documentsui.RootCursorWrapper;
32 
33 import libcore.io.IoUtils;
34 
35 import java.io.DataInputStream;
36 import java.io.DataOutputStream;
37 import java.io.FileNotFoundException;
38 import java.io.IOException;
39 import java.net.ProtocolException;
40 import java.util.Objects;
41 
42 /**
43  * Representation of a {@link Document}.
44  */
45 public class DocumentInfo implements Durable, Parcelable {
46     private static final int VERSION_INIT = 1;
47     private static final int VERSION_SPLIT_URI = 2;
48 
49     public String authority;
50     public String documentId;
51     public String mimeType;
52     public String displayName;
53     public long lastModified;
54     public int flags;
55     public String summary;
56     public long size;
57     public int icon;
58 
59     /** Derived fields that aren't persisted */
60     public Uri derivedUri;
61 
DocumentInfo()62     public DocumentInfo() {
63         reset();
64     }
65 
66     @Override
reset()67     public void reset() {
68         authority = null;
69         documentId = null;
70         mimeType = null;
71         displayName = null;
72         lastModified = -1;
73         flags = 0;
74         summary = null;
75         size = -1;
76         icon = 0;
77         derivedUri = null;
78     }
79 
80     @Override
read(DataInputStream in)81     public void read(DataInputStream in) throws IOException {
82         final int version = in.readInt();
83         switch (version) {
84             case VERSION_INIT:
85                 throw new ProtocolException("Ignored upgrade");
86             case VERSION_SPLIT_URI:
87                 authority = DurableUtils.readNullableString(in);
88                 documentId = DurableUtils.readNullableString(in);
89                 mimeType = DurableUtils.readNullableString(in);
90                 displayName = DurableUtils.readNullableString(in);
91                 lastModified = in.readLong();
92                 flags = in.readInt();
93                 summary = DurableUtils.readNullableString(in);
94                 size = in.readLong();
95                 icon = in.readInt();
96                 deriveFields();
97                 break;
98             default:
99                 throw new ProtocolException("Unknown version " + version);
100         }
101     }
102 
103     @Override
write(DataOutputStream out)104     public void write(DataOutputStream out) throws IOException {
105         out.writeInt(VERSION_SPLIT_URI);
106         DurableUtils.writeNullableString(out, authority);
107         DurableUtils.writeNullableString(out, documentId);
108         DurableUtils.writeNullableString(out, mimeType);
109         DurableUtils.writeNullableString(out, displayName);
110         out.writeLong(lastModified);
111         out.writeInt(flags);
112         DurableUtils.writeNullableString(out, summary);
113         out.writeLong(size);
114         out.writeInt(icon);
115     }
116 
117     @Override
describeContents()118     public int describeContents() {
119         return 0;
120     }
121 
122     @Override
writeToParcel(Parcel dest, int flags)123     public void writeToParcel(Parcel dest, int flags) {
124         DurableUtils.writeToParcel(dest, this);
125     }
126 
127     public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() {
128         @Override
129         public DocumentInfo createFromParcel(Parcel in) {
130             final DocumentInfo doc = new DocumentInfo();
131             DurableUtils.readFromParcel(in, doc);
132             return doc;
133         }
134 
135         @Override
136         public DocumentInfo[] newArray(int size) {
137             return new DocumentInfo[size];
138         }
139     };
140 
fromDirectoryCursor(Cursor cursor)141     public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
142         assert(cursor != null);
143         final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
144         return fromCursor(cursor, authority);
145     }
146 
fromCursor(Cursor cursor, String authority)147     public static DocumentInfo fromCursor(Cursor cursor, String authority) {
148         assert(cursor != null);
149         final DocumentInfo info = new DocumentInfo();
150         info.updateFromCursor(cursor, authority);
151         return info;
152     }
153 
updateFromCursor(Cursor cursor, String authority)154     public void updateFromCursor(Cursor cursor, String authority) {
155         this.authority = authority;
156         this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
157         this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
158         this.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
159         this.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
160         this.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
161         this.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
162         this.size = getCursorLong(cursor, Document.COLUMN_SIZE);
163         this.icon = getCursorInt(cursor, Document.COLUMN_ICON);
164         this.deriveFields();
165     }
166 
fromUri(ContentResolver resolver, Uri uri)167     public static DocumentInfo fromUri(ContentResolver resolver, Uri uri)
168             throws FileNotFoundException {
169         final DocumentInfo info = new DocumentInfo();
170         info.updateFromUri(resolver, uri);
171         return info;
172     }
173 
174     /**
175      * Update a possibly stale restored document against a live
176      * {@link DocumentsProvider}.
177      */
updateSelf(ContentResolver resolver)178     public void updateSelf(ContentResolver resolver) throws FileNotFoundException {
179         updateFromUri(resolver, derivedUri);
180     }
181 
updateFromUri(ContentResolver resolver, Uri uri)182     public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
183         ContentProviderClient client = null;
184         Cursor cursor = null;
185         try {
186             client = DocumentsApplication.acquireUnstableProviderOrThrow(
187                     resolver, uri.getAuthority());
188             cursor = client.query(uri, null, null, null, null);
189             if (!cursor.moveToFirst()) {
190                 throw new FileNotFoundException("Missing details for " + uri);
191             }
192             updateFromCursor(cursor, uri.getAuthority());
193         } catch (Throwable t) {
194             throw asFileNotFoundException(t);
195         } finally {
196             IoUtils.closeQuietly(cursor);
197             ContentProviderClient.releaseQuietly(client);
198         }
199     }
200 
201     @VisibleForTesting
deriveFields()202     void deriveFields() {
203         derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
204     }
205 
206     @Override
toString()207     public String toString() {
208         return "Document{"
209                 + "docId=" + documentId
210                 + ", name=" + displayName
211                 + ", isContainer=" + isContainer()
212                 + ", isDirectory=" + isDirectory()
213                 + ", isArchive=" + isArchive()
214                 + ", isPartial=" + isPartial()
215                 + ", isVirtualDocument=" + isVirtualDocument()
216                 + ", isDeleteSupported=" + isDeleteSupported()
217                 + ", isCreateSupported=" + isCreateSupported()
218                 + ", isRenameSupported=" + isRenameSupported()
219                 + "}";
220     }
221 
isCreateSupported()222     public boolean isCreateSupported() {
223         return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
224     }
225 
isThumbnailSupported()226     public boolean isThumbnailSupported() {
227         return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
228     }
229 
isDirectory()230     public boolean isDirectory() {
231         return Document.MIME_TYPE_DIR.equals(mimeType);
232     }
233 
isGridPreferred()234     public boolean isGridPreferred() {
235         return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
236     }
237 
isDeleteSupported()238     public boolean isDeleteSupported() {
239         return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
240     }
241 
isRemoveSupported()242     public boolean isRemoveSupported() {
243         return (flags & Document.FLAG_SUPPORTS_REMOVE) != 0;
244     }
245 
isRenameSupported()246     public boolean isRenameSupported() {
247         return (flags & Document.FLAG_SUPPORTS_RENAME) != 0;
248     }
249 
isArchive()250     public boolean isArchive() {
251         return (flags & Document.FLAG_ARCHIVE) != 0;
252     }
253 
isPartial()254     public boolean isPartial() {
255         return (flags & Document.FLAG_PARTIAL) != 0;
256     }
257 
isContainer()258     public boolean isContainer() {
259         return isDirectory() || isArchive();
260     }
261 
isVirtualDocument()262     public boolean isVirtualDocument() {
263         return (flags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
264     }
265 
hashCode()266     public int hashCode() {
267         return derivedUri.hashCode() + mimeType.hashCode();
268     }
269 
equals(Object o)270     public boolean equals(Object o) {
271         if (o == null) {
272             return false;
273         }
274 
275         if (this == o) {
276             return true;
277         }
278 
279         if (o instanceof DocumentInfo) {
280             DocumentInfo other = (DocumentInfo) o;
281             // Uri + mime type should be totally unique.
282             return Objects.equals(derivedUri, other.derivedUri)
283                     && Objects.equals(mimeType, other.mimeType);
284         }
285 
286         return false;
287     }
288 
getCursorString(Cursor cursor, String columnName)289     public static String getCursorString(Cursor cursor, String columnName) {
290         final int index = cursor.getColumnIndex(columnName);
291         return (index != -1) ? cursor.getString(index) : null;
292     }
293 
294     /**
295      * Missing or null values are returned as -1.
296      */
getCursorLong(Cursor cursor, String columnName)297     public static long getCursorLong(Cursor cursor, String columnName) {
298         final int index = cursor.getColumnIndex(columnName);
299         if (index == -1) return -1;
300         final String value = cursor.getString(index);
301         if (value == null) return -1;
302         try {
303             return Long.parseLong(value);
304         } catch (NumberFormatException e) {
305             return -1;
306         }
307     }
308 
309     /**
310      * Missing or null values are returned as 0.
311      */
getCursorInt(Cursor cursor, String columnName)312     public static int getCursorInt(Cursor cursor, String columnName) {
313         final int index = cursor.getColumnIndex(columnName);
314         return (index != -1) ? cursor.getInt(index) : 0;
315     }
316 
asFileNotFoundException(Throwable t)317     public static FileNotFoundException asFileNotFoundException(Throwable t)
318             throws FileNotFoundException {
319         if (t instanceof FileNotFoundException) {
320             throw (FileNotFoundException) t;
321         }
322         final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage());
323         fnfe.initCause(t);
324         throw fnfe;
325     }
326 }
327