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