• 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.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.text.TextUtils;
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.text.Collator;
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     private static final Collator sCollator;
50 
51     static {
52         sCollator = Collator.getInstance();
53         sCollator.setStrength(Collator.SECONDARY);
54     }
55 
56     public String authority;
57     public String documentId;
58     public String mimeType;
59     public String displayName;
60     public long lastModified;
61     public int flags;
62     public String summary;
63     public long size;
64     public int icon;
65 
66     /** Derived fields that aren't persisted */
67     public Uri derivedUri;
68 
DocumentInfo()69     public DocumentInfo() {
70         reset();
71     }
72 
73     @Override
reset()74     public void reset() {
75         authority = null;
76         documentId = null;
77         mimeType = null;
78         displayName = null;
79         lastModified = -1;
80         flags = 0;
81         summary = null;
82         size = -1;
83         icon = 0;
84 
85         derivedUri = null;
86     }
87 
88     @Override
read(DataInputStream in)89     public void read(DataInputStream in) throws IOException {
90         final int version = in.readInt();
91         switch (version) {
92             case VERSION_INIT:
93                 throw new ProtocolException("Ignored upgrade");
94             case VERSION_SPLIT_URI:
95                 authority = DurableUtils.readNullableString(in);
96                 documentId = DurableUtils.readNullableString(in);
97                 mimeType = DurableUtils.readNullableString(in);
98                 displayName = DurableUtils.readNullableString(in);
99                 lastModified = in.readLong();
100                 flags = in.readInt();
101                 summary = DurableUtils.readNullableString(in);
102                 size = in.readLong();
103                 icon = in.readInt();
104                 deriveFields();
105                 break;
106             default:
107                 throw new ProtocolException("Unknown version " + version);
108         }
109     }
110 
111     @Override
write(DataOutputStream out)112     public void write(DataOutputStream out) throws IOException {
113         out.writeInt(VERSION_SPLIT_URI);
114         DurableUtils.writeNullableString(out, authority);
115         DurableUtils.writeNullableString(out, documentId);
116         DurableUtils.writeNullableString(out, mimeType);
117         DurableUtils.writeNullableString(out, displayName);
118         out.writeLong(lastModified);
119         out.writeInt(flags);
120         DurableUtils.writeNullableString(out, summary);
121         out.writeLong(size);
122         out.writeInt(icon);
123     }
124 
125     @Override
describeContents()126     public int describeContents() {
127         return 0;
128     }
129 
130     @Override
writeToParcel(Parcel dest, int flags)131     public void writeToParcel(Parcel dest, int flags) {
132         DurableUtils.writeToParcel(dest, this);
133     }
134 
135     public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() {
136         @Override
137         public DocumentInfo createFromParcel(Parcel in) {
138             final DocumentInfo doc = new DocumentInfo();
139             DurableUtils.readFromParcel(in, doc);
140             return doc;
141         }
142 
143         @Override
144         public DocumentInfo[] newArray(int size) {
145             return new DocumentInfo[size];
146         }
147     };
148 
fromDirectoryCursor(Cursor cursor)149     public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
150         final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
151         return fromCursor(cursor, authority);
152     }
153 
fromCursor(Cursor cursor, String authority)154     public static DocumentInfo fromCursor(Cursor cursor, String authority) {
155         final DocumentInfo info = new DocumentInfo();
156         info.updateFromCursor(cursor, authority);
157         return info;
158     }
159 
updateFromCursor(Cursor cursor, String authority)160     public void updateFromCursor(Cursor cursor, String authority) {
161         this.authority = authority;
162         this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
163         this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
164         this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
165         this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
166         this.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
167         this.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
168         this.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
169         this.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
170         this.size = getCursorLong(cursor, Document.COLUMN_SIZE);
171         this.icon = getCursorInt(cursor, Document.COLUMN_ICON);
172         this.deriveFields();
173     }
174 
fromUri(ContentResolver resolver, Uri uri)175     public static DocumentInfo fromUri(ContentResolver resolver, Uri uri)
176             throws FileNotFoundException {
177         final DocumentInfo info = new DocumentInfo();
178         info.updateFromUri(resolver, uri);
179         return info;
180     }
181 
182     /**
183      * Update a possibly stale restored document against a live
184      * {@link DocumentsProvider}.
185      */
updateSelf(ContentResolver resolver)186     public void updateSelf(ContentResolver resolver) throws FileNotFoundException {
187         updateFromUri(resolver, derivedUri);
188     }
189 
updateFromUri(ContentResolver resolver, Uri uri)190     public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
191         ContentProviderClient client = null;
192         Cursor cursor = null;
193         try {
194             client = DocumentsApplication.acquireUnstableProviderOrThrow(
195                     resolver, uri.getAuthority());
196             cursor = client.query(uri, null, null, null, null);
197             if (!cursor.moveToFirst()) {
198                 throw new FileNotFoundException("Missing details for " + uri);
199             }
200             updateFromCursor(cursor, uri.getAuthority());
201         } catch (Throwable t) {
202             throw asFileNotFoundException(t);
203         } finally {
204             IoUtils.closeQuietly(cursor);
205             ContentProviderClient.releaseQuietly(client);
206         }
207     }
208 
deriveFields()209     private void deriveFields() {
210         derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
211     }
212 
213     @Override
toString()214     public String toString() {
215         return "Document{docId=" + documentId + ", name=" + displayName + "}";
216     }
217 
isCreateSupported()218     public boolean isCreateSupported() {
219         return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
220     }
221 
isThumbnailSupported()222     public boolean isThumbnailSupported() {
223         return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
224     }
225 
isDirectory()226     public boolean isDirectory() {
227         return Document.MIME_TYPE_DIR.equals(mimeType);
228     }
229 
isGridPreferred()230     public boolean isGridPreferred() {
231         return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
232     }
233 
isDeleteSupported()234     public boolean isDeleteSupported() {
235         return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
236     }
237 
isGridTitlesHidden()238     public boolean isGridTitlesHidden() {
239         return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
240     }
241 
getCursorString(Cursor cursor, String columnName)242     public static String getCursorString(Cursor cursor, String columnName) {
243         final int index = cursor.getColumnIndex(columnName);
244         return (index != -1) ? cursor.getString(index) : null;
245     }
246 
247     /**
248      * Missing or null values are returned as -1.
249      */
getCursorLong(Cursor cursor, String columnName)250     public static long getCursorLong(Cursor cursor, String columnName) {
251         final int index = cursor.getColumnIndex(columnName);
252         if (index == -1) return -1;
253         final String value = cursor.getString(index);
254         if (value == null) return -1;
255         try {
256             return Long.parseLong(value);
257         } catch (NumberFormatException e) {
258             return -1;
259         }
260     }
261 
262     /**
263      * Missing or null values are returned as 0.
264      */
getCursorInt(Cursor cursor, String columnName)265     public static int getCursorInt(Cursor cursor, String columnName) {
266         final int index = cursor.getColumnIndex(columnName);
267         return (index != -1) ? cursor.getInt(index) : 0;
268     }
269 
asFileNotFoundException(Throwable t)270     public static FileNotFoundException asFileNotFoundException(Throwable t)
271             throws FileNotFoundException {
272         if (t instanceof FileNotFoundException) {
273             throw (FileNotFoundException) t;
274         }
275         final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage());
276         fnfe.initCause(t);
277         throw fnfe;
278     }
279 
280     /**
281      * String prefix used to indicate the document is a directory.
282      */
283     public static final char DIR_PREFIX = '\001';
284 
285     /**
286      * Compare two strings against each other using system default collator in a
287      * case-insensitive mode. Clusters strings prefixed with {@link #DIR_PREFIX}
288      * before other items.
289      */
compareToIgnoreCaseNullable(String lhs, String rhs)290     public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
291         final boolean leftEmpty = TextUtils.isEmpty(lhs);
292         final boolean rightEmpty = TextUtils.isEmpty(rhs);
293 
294         if (leftEmpty && rightEmpty) return 0;
295         if (leftEmpty) return -1;
296         if (rightEmpty) return 1;
297 
298         final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX);
299         final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX);
300 
301         if (leftDir && !rightDir) return -1;
302         if (rightDir && !leftDir) return 1;
303 
304         return sCollator.compare(lhs, rhs);
305     }
306 }
307