blob: 69b574dd85e5bb604882149741650b3292f80549 [file] [log] [blame]
Jeff Sharkeyef7184a2013-08-05 17:56:48 -07001/*
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
17package com.android.documentsui.model;
18
Jeff Sharkeyded77182013-09-03 14:17:06 -070019import android.content.ContentProviderClient;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070020import android.content.ContentResolver;
21import android.database.Cursor;
22import android.net.Uri;
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070023import android.os.Parcel;
24import android.os.Parcelable;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070025import android.provider.DocumentsContract;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070026import android.provider.DocumentsContract.Document;
Jeff Sharkey7aa76012013-09-30 14:26:27 -070027import android.provider.DocumentsProvider;
Steve McKayd3afdee2015-11-19 17:27:12 -080028import android.support.annotation.VisibleForTesting;
Jeff Sharkeyfa5ec772014-08-05 17:55:16 -070029import android.text.TextUtils;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070030
Jeff Sharkey7aa76012013-09-30 14:26:27 -070031import com.android.documentsui.DocumentsApplication;
Jeff Sharkey251097b2013-09-02 15:07:28 -070032import com.android.documentsui.RootCursorWrapper;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070033
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -070034import libcore.io.IoUtils;
35
Jeff Sharkeyb5133112013-09-01 18:41:04 -070036import java.io.DataInputStream;
37import java.io.DataOutputStream;
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -070038import java.io.FileNotFoundException;
Jeff Sharkeyb5133112013-09-01 18:41:04 -070039import java.io.IOException;
40import java.net.ProtocolException;
Jeff Sharkeyfa5ec772014-08-05 17:55:16 -070041import java.text.Collator;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070042
43/**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070044 * Representation of a {@link Document}.
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070045 */
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070046public class DocumentInfo implements Durable, Parcelable {
Jeff Sharkeyb5133112013-09-01 18:41:04 -070047 private static final int VERSION_INIT = 1;
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070048 private static final int VERSION_SPLIT_URI = 2;
Jeff Sharkeyb5133112013-09-01 18:41:04 -070049
Jeff Sharkeyfa5ec772014-08-05 17:55:16 -070050 private static final Collator sCollator;
51
52 static {
53 sCollator = Collator.getInstance();
54 sCollator.setStrength(Collator.SECONDARY);
55 }
56
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070057 public String authority;
58 public String documentId;
Jeff Sharkey5dfb3452013-08-31 21:27:44 -070059 public String mimeType;
60 public String displayName;
61 public long lastModified;
62 public int flags;
63 public String summary;
64 public long size;
65 public int icon;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -070066
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070067 /** Derived fields that aren't persisted */
68 public Uri derivedUri;
69
Jeff Sharkeyb5133112013-09-01 18:41:04 -070070 public DocumentInfo() {
71 reset();
72 }
73
74 @Override
75 public void reset() {
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070076 authority = null;
77 documentId = null;
Jeff Sharkeyb5133112013-09-01 18:41:04 -070078 mimeType = null;
79 displayName = null;
80 lastModified = -1;
81 flags = 0;
82 summary = null;
83 size = -1;
84 icon = 0;
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070085
86 derivedUri = null;
Jeff Sharkeyb5133112013-09-01 18:41:04 -070087 }
88
89 @Override
90 public void read(DataInputStream in) throws IOException {
91 final int version = in.readInt();
92 switch (version) {
93 case VERSION_INIT:
Jeff Sharkeyd182bb62013-09-07 14:45:03 -070094 throw new ProtocolException("Ignored upgrade");
95 case VERSION_SPLIT_URI:
96 authority = DurableUtils.readNullableString(in);
97 documentId = DurableUtils.readNullableString(in);
Jeff Sharkeyb5133112013-09-01 18:41:04 -070098 mimeType = DurableUtils.readNullableString(in);
99 displayName = DurableUtils.readNullableString(in);
100 lastModified = in.readLong();
101 flags = in.readInt();
102 summary = DurableUtils.readNullableString(in);
103 size = in.readLong();
104 icon = in.readInt();
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700105 deriveFields();
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700106 break;
107 default:
108 throw new ProtocolException("Unknown version " + version);
109 }
110 }
111
112 @Override
113 public void write(DataOutputStream out) throws IOException {
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700114 out.writeInt(VERSION_SPLIT_URI);
115 DurableUtils.writeNullableString(out, authority);
116 DurableUtils.writeNullableString(out, documentId);
Jeff Sharkeyb5133112013-09-01 18:41:04 -0700117 DurableUtils.writeNullableString(out, mimeType);
118 DurableUtils.writeNullableString(out, displayName);
119 out.writeLong(lastModified);
120 out.writeInt(flags);
121 DurableUtils.writeNullableString(out, summary);
122 out.writeLong(size);
123 out.writeInt(icon);
124 }
125
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700126 @Override
127 public int describeContents() {
128 return 0;
129 }
130
131 @Override
132 public void writeToParcel(Parcel dest, int flags) {
133 DurableUtils.writeToParcel(dest, this);
134 }
135
136 public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() {
137 @Override
138 public DocumentInfo createFromParcel(Parcel in) {
139 final DocumentInfo doc = new DocumentInfo();
140 DurableUtils.readFromParcel(in, doc);
141 return doc;
142 }
143
144 @Override
145 public DocumentInfo[] newArray(int size) {
146 return new DocumentInfo[size];
147 }
148 };
149
Jeff Sharkey251097b2013-09-02 15:07:28 -0700150 public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
Jeff Sharkey251097b2013-09-02 15:07:28 -0700151 final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700152 return fromCursor(cursor, authority);
153 }
154
155 public static DocumentInfo fromCursor(Cursor cursor, String authority) {
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700156 final DocumentInfo info = new DocumentInfo();
157 info.updateFromCursor(cursor, authority);
158 return info;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700159 }
160
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700161 public void updateFromCursor(Cursor cursor, String authority) {
162 this.authority = authority;
163 this.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
164 this.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700165 this.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
166 this.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
167 this.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
168 this.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
169 this.size = getCursorLong(cursor, Document.COLUMN_SIZE);
170 this.icon = getCursorInt(cursor, Document.COLUMN_ICON);
171 this.deriveFields();
172 }
173
174 public static DocumentInfo fromUri(ContentResolver resolver, Uri uri)
175 throws FileNotFoundException {
176 final DocumentInfo info = new DocumentInfo();
177 info.updateFromUri(resolver, uri);
178 return info;
179 }
180
181 /**
182 * Update a possibly stale restored document against a live
183 * {@link DocumentsProvider}.
184 */
185 public void updateSelf(ContentResolver resolver) throws FileNotFoundException {
186 updateFromUri(resolver, derivedUri);
187 }
188
189 public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700190 ContentProviderClient client = null;
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -0700191 Cursor cursor = null;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700192 try {
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700193 client = DocumentsApplication.acquireUnstableProviderOrThrow(
194 resolver, uri.getAuthority());
Jeff Sharkeyded77182013-09-03 14:17:06 -0700195 cursor = client.query(uri, null, null, null, null);
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700196 if (!cursor.moveToFirst()) {
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -0700197 throw new FileNotFoundException("Missing details for " + uri);
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700198 }
Jeff Sharkeydeffade2013-09-24 12:07:12 -0700199 updateFromCursor(cursor, uri.getAuthority());
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -0700200 } catch (Throwable t) {
201 throw asFileNotFoundException(t);
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700202 } finally {
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -0700203 IoUtils.closeQuietly(cursor);
Jeff Sharkey7aa76012013-09-30 14:26:27 -0700204 ContentProviderClient.releaseQuietly(client);
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700205 }
206 }
207
Steve McKayd3afdee2015-11-19 17:27:12 -0800208 @VisibleForTesting
209 void deriveFields() {
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700210 derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
211 }
212
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700213 @Override
214 public String toString() {
Steve McKayd3afdee2015-11-19 17:27:12 -0800215 return "Document{"
216 + "docId=" + documentId
217 + ", name=" + displayName
218 + ", isDirectory=" + isDirectory()
219 + "}";
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700220 }
221
222 public boolean isCreateSupported() {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700223 return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700224 }
225
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700226 public boolean isThumbnailSupported() {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700227 return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700228 }
229
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700230 public boolean isDirectory() {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700231 return Document.MIME_TYPE_DIR.equals(mimeType);
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700232 }
233
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700234 public boolean isGridPreferred() {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700235 return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0;
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700236 }
237
238 public boolean isDeleteSupported() {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700239 return (flags & Document.FLAG_SUPPORTS_DELETE) != 0;
Jeff Sharkey4eb407a2013-08-18 17:38:20 -0700240 }
241
Jeff Sharkeyf6db1542013-09-13 13:42:19 -0700242 public boolean isGridTitlesHidden() {
243 return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0;
244 }
245
Tomasz Mikolajewski39acff52015-11-25 13:01:18 +0900246 public boolean isArchive() {
247 return (flags & Document.FLAG_ARCHIVE) != 0;
248 }
249
250 public boolean isContainer() {
251 return isDirectory() || isArchive();
252 }
253
Steve McKayd3afdee2015-11-19 17:27:12 -0800254 public int hashCode() {
255 return derivedUri.hashCode() + mimeType.hashCode();
256 }
257
258 public boolean equals(Object other) {
259 if (this == other) {
260 return true;
261 } else if (!(other instanceof DocumentInfo)) {
262 return false;
263 }
264
265 DocumentInfo that = (DocumentInfo) other;
266 // Uri + mime type should be totally unique.
267 return derivedUri.equals(that.derivedUri) && mimeType.equals(that.mimeType);
268 }
269
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700270 public static String getCursorString(Cursor cursor, String columnName) {
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700271 final int index = cursor.getColumnIndex(columnName);
272 return (index != -1) ? cursor.getString(index) : null;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700273 }
274
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700275 /**
276 * Missing or null values are returned as -1.
277 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700278 public static long getCursorLong(Cursor cursor, String columnName) {
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700279 final int index = cursor.getColumnIndex(columnName);
Jeff Sharkeya5599ef2013-08-15 16:17:41 -0700280 if (index == -1) return -1;
281 final String value = cursor.getString(index);
282 if (value == null) return -1;
283 try {
284 return Long.parseLong(value);
285 } catch (NumberFormatException e) {
286 return -1;
287 }
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700288 }
289
Jeff Sharkeyd182bb62013-09-07 14:45:03 -0700290 /**
291 * Missing or null values are returned as 0.
292 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700293 public static int getCursorInt(Cursor cursor, String columnName) {
Jeff Sharkeyb156f4b2013-08-06 16:26:14 -0700294 final int index = cursor.getColumnIndex(columnName);
295 return (index != -1) ? cursor.getInt(index) : 0;
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700296 }
297
Jeff Sharkey0c2d31b2013-08-07 18:33:33 -0700298 public static FileNotFoundException asFileNotFoundException(Throwable t)
299 throws FileNotFoundException {
300 if (t instanceof FileNotFoundException) {
301 throw (FileNotFoundException) t;
302 }
303 final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage());
304 fnfe.initCause(t);
305 throw fnfe;
306 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700307
Jeff Sharkeyfa5ec772014-08-05 17:55:16 -0700308 /**
309 * String prefix used to indicate the document is a directory.
310 */
311 public static final char DIR_PREFIX = '\001';
312
313 /**
314 * Compare two strings against each other using system default collator in a
315 * case-insensitive mode. Clusters strings prefixed with {@link #DIR_PREFIX}
316 * before other items.
317 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700318 public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
Jeff Sharkeyfa5ec772014-08-05 17:55:16 -0700319 final boolean leftEmpty = TextUtils.isEmpty(lhs);
320 final boolean rightEmpty = TextUtils.isEmpty(rhs);
321
322 if (leftEmpty && rightEmpty) return 0;
323 if (leftEmpty) return -1;
324 if (rightEmpty) return 1;
325
326 final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX);
327 final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX);
328
329 if (leftDir && !rightDir) return -1;
330 if (rightDir && !leftDir) return 1;
331
332 return sCollator.compare(lhs, rhs);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700333 }
Jeff Sharkeyef7184a2013-08-05 17:56:48 -0700334}