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.DocumentsContract.Root; 28 import android.provider.DocumentsProvider; 29 import android.text.TextUtils; 30 31 import com.android.documentsui.DocumentsApplication; 32 import com.android.documentsui.RootCursorWrapper; 33 34 import libcore.io.IoUtils; 35 36 import java.io.DataInputStream; 37 import java.io.DataOutputStream; 38 import java.io.FileNotFoundException; 39 import java.io.IOException; 40 import java.net.ProtocolException; 41 import java.text.Collator; 42 43 /** 44 * Representation of a {@link Document}. 45 */ 46 public class DocumentInfo implements Durable, Parcelable { 47 private static final int VERSION_INIT = 1; 48 private static final int VERSION_SPLIT_URI = 2; 49 50 private static final Collator sCollator; 51 52 static { 53 sCollator = Collator.getInstance(); 54 sCollator.setStrength(Collator.SECONDARY); 55 } 56 57 public String authority; 58 public String documentId; 59 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; 66 67 /** Derived fields that aren't persisted */ 68 public Uri derivedUri; 69 DocumentInfo()70 public DocumentInfo() { 71 reset(); 72 } 73 74 @Override reset()75 public void reset() { 76 authority = null; 77 documentId = null; 78 mimeType = null; 79 displayName = null; 80 lastModified = -1; 81 flags = 0; 82 summary = null; 83 size = -1; 84 icon = 0; 85 86 derivedUri = null; 87 } 88 89 @Override read(DataInputStream in)90 public void read(DataInputStream in) throws IOException { 91 final int version = in.readInt(); 92 switch (version) { 93 case VERSION_INIT: 94 throw new ProtocolException("Ignored upgrade"); 95 case VERSION_SPLIT_URI: 96 authority = DurableUtils.readNullableString(in); 97 documentId = DurableUtils.readNullableString(in); 98 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(); 105 deriveFields(); 106 break; 107 default: 108 throw new ProtocolException("Unknown version " + version); 109 } 110 } 111 112 @Override write(DataOutputStream out)113 public void write(DataOutputStream out) throws IOException { 114 out.writeInt(VERSION_SPLIT_URI); 115 DurableUtils.writeNullableString(out, authority); 116 DurableUtils.writeNullableString(out, documentId); 117 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 126 @Override describeContents()127 public int describeContents() { 128 return 0; 129 } 130 131 @Override writeToParcel(Parcel dest, int flags)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 fromDirectoryCursor(Cursor cursor)150 public static DocumentInfo fromDirectoryCursor(Cursor cursor) { 151 final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY); 152 return fromCursor(cursor, authority); 153 } 154 fromCursor(Cursor cursor, String authority)155 public static DocumentInfo fromCursor(Cursor cursor, String authority) { 156 final DocumentInfo info = new DocumentInfo(); 157 info.updateFromCursor(cursor, authority); 158 return info; 159 } 160 updateFromCursor(Cursor cursor, String authority)161 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); 165 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 fromUri(ContentResolver resolver, Uri uri)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 */ updateSelf(ContentResolver resolver)185 public void updateSelf(ContentResolver resolver) throws FileNotFoundException { 186 updateFromUri(resolver, derivedUri); 187 } 188 updateFromUri(ContentResolver resolver, Uri uri)189 public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { 190 ContentProviderClient client = null; 191 Cursor cursor = null; 192 try { 193 client = DocumentsApplication.acquireUnstableProviderOrThrow( 194 resolver, uri.getAuthority()); 195 cursor = client.query(uri, null, null, null, null); 196 if (!cursor.moveToFirst()) { 197 throw new FileNotFoundException("Missing details for " + uri); 198 } 199 updateFromCursor(cursor, uri.getAuthority()); 200 } catch (Throwable t) { 201 throw asFileNotFoundException(t); 202 } finally { 203 IoUtils.closeQuietly(cursor); 204 ContentProviderClient.releaseQuietly(client); 205 } 206 } 207 deriveFields()208 private void deriveFields() { 209 derivedUri = DocumentsContract.buildDocumentUri(authority, documentId); 210 } 211 212 @Override toString()213 public String toString() { 214 return "Document{docId=" + documentId + ", name=" + displayName + "}"; 215 } 216 isCreateSupported()217 public boolean isCreateSupported() { 218 return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0; 219 } 220 isThumbnailSupported()221 public boolean isThumbnailSupported() { 222 return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; 223 } 224 isDirectory()225 public boolean isDirectory() { 226 return Document.MIME_TYPE_DIR.equals(mimeType); 227 } 228 isGridPreferred()229 public boolean isGridPreferred() { 230 return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0; 231 } 232 isDeleteSupported()233 public boolean isDeleteSupported() { 234 return (flags & Document.FLAG_SUPPORTS_DELETE) != 0; 235 } 236 isGridTitlesHidden()237 public boolean isGridTitlesHidden() { 238 return (flags & Document.FLAG_DIR_HIDE_GRID_TITLES) != 0; 239 } 240 getCursorString(Cursor cursor, String columnName)241 public static String getCursorString(Cursor cursor, String columnName) { 242 final int index = cursor.getColumnIndex(columnName); 243 return (index != -1) ? cursor.getString(index) : null; 244 } 245 246 /** 247 * Missing or null values are returned as -1. 248 */ getCursorLong(Cursor cursor, String columnName)249 public static long getCursorLong(Cursor cursor, String columnName) { 250 final int index = cursor.getColumnIndex(columnName); 251 if (index == -1) return -1; 252 final String value = cursor.getString(index); 253 if (value == null) return -1; 254 try { 255 return Long.parseLong(value); 256 } catch (NumberFormatException e) { 257 return -1; 258 } 259 } 260 261 /** 262 * Missing or null values are returned as 0. 263 */ getCursorInt(Cursor cursor, String columnName)264 public static int getCursorInt(Cursor cursor, String columnName) { 265 final int index = cursor.getColumnIndex(columnName); 266 return (index != -1) ? cursor.getInt(index) : 0; 267 } 268 asFileNotFoundException(Throwable t)269 public static FileNotFoundException asFileNotFoundException(Throwable t) 270 throws FileNotFoundException { 271 if (t instanceof FileNotFoundException) { 272 throw (FileNotFoundException) t; 273 } 274 final FileNotFoundException fnfe = new FileNotFoundException(t.getMessage()); 275 fnfe.initCause(t); 276 throw fnfe; 277 } 278 279 /** 280 * String prefix used to indicate the document is a directory. 281 */ 282 public static final char DIR_PREFIX = '\001'; 283 284 /** 285 * Compare two strings against each other using system default collator in a 286 * case-insensitive mode. Clusters strings prefixed with {@link #DIR_PREFIX} 287 * before other items. 288 */ compareToIgnoreCaseNullable(String lhs, String rhs)289 public static int compareToIgnoreCaseNullable(String lhs, String rhs) { 290 final boolean leftEmpty = TextUtils.isEmpty(lhs); 291 final boolean rightEmpty = TextUtils.isEmpty(rhs); 292 293 if (leftEmpty && rightEmpty) return 0; 294 if (leftEmpty) return -1; 295 if (rightEmpty) return 1; 296 297 final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX); 298 final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX); 299 300 if (leftDir && !rightDir) return -1; 301 if (rightDir && !leftDir) return 1; 302 303 return sCollator.compare(lhs, rhs); 304 } 305 } 306