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