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