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