DocumentsProvider.java revision e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372
1aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey/* 2aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Copyright (C) 2013 The Android Open Source Project 3aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 4aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License"); 5aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * you may not use this file except in compliance with the License. 6aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * You may obtain a copy of the License at 7aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 8aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * http://www.apache.org/licenses/LICENSE-2.0 9aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 10aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Unless required by applicable law or agreed to in writing, software 11aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS, 12aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * See the License for the specific language governing permissions and 14aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * limitations under the License. 15aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 16aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 17aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeypackage android.provider; 18aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 19aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; 20aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; 21aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 22ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getDocumentId; 23ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getRootId; 24ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getSearchDocumentsQuery; 25aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 26aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.ContentProvider; 27e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkeyimport android.content.ContentResolver; 28aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.ContentValues; 29aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.Context; 30aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.Intent; 31aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.UriMatcher; 32e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkeyimport android.content.pm.PackageManager; 33aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.pm.ProviderInfo; 34aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.res.AssetFileDescriptor; 35aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.database.Cursor; 36aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.graphics.Point; 37aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.net.Uri; 38aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.Bundle; 39aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.CancellationSignal; 40aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.ParcelFileDescriptor; 41aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.ParcelFileDescriptor.OnCloseListener; 42ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport android.provider.DocumentsContract.Document; 43e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkeyimport android.provider.DocumentsContract.Root; 44aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.util.Log; 45aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 46aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport libcore.io.IoUtils; 47aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 48aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport java.io.FileNotFoundException; 49aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 50aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey/** 51e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Base class for a document provider. A document provider offers read and write 52e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * access to durable files, such as files stored on a local disk, or files in a 53e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cloud storage service. To create a document provider, extend this class, 54e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * implement the abstract methods, and add it to your manifest like this: 55e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * 56e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <pre class="prettyprint"><manifest> 57e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * ... 58e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <application> 59e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * ... 60e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <provider 61e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android:name="com.example.MyCloudProvider" 62e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android:authorities="com.example.mycloudprovider" 63e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android:exported="true" 64e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android:grantUriPermissions="true" 65e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android:permission="android.permission.MANAGE_DOCUMENTS"> 66e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <intent-filter> 67e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 68e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </intent-filter> 69e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </provider> 70e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * ... 71e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </application> 72e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *</manifest></pre> 73aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p> 74e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * When defining your provider, you must protect it with 75e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission 76e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * only the system can obtain. Applications cannot use a documents provider 77e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or 78e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively 79e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * navigate and select documents. When a user selects documents through that 80e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * UI, the system issues narrow URI permission grants to the requesting 81e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * application. 82e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p> 83e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <h3>Documents</h3> 84aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p> 85e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * A document can be either an openable stream (with a specific MIME type), or a 86aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * directory containing additional documents (with the 87e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top 88e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * of a subtree containing zero or more documents, which can recursively contain 89e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * even more documents and directories. 90e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p> 91e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 92e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Each document can have different capabilities, as described by 93e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented 94e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * as a thumbnail, a provider can set {@link Document#FLAG_SUPPORTS_THUMBNAIL} 95e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * and implement 96e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return 97e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * that thumbnail. 98e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p> 99e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 100e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Each document under a provider is uniquely referenced by its 101e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A 102e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * single document can be included in multiple directories when responding to 103e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link #queryChildDocuments(String, String[], String)}. For example, a 104e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * provider might surface a single photo in multiple locations: once in a 105e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * directory of locations, and again in a directory of dates. 106e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p> 107e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <h3>Roots</h3> 108aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p> 109e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * All documents are surfaced through one or more "roots." Each root represents 110e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * the top of a document tree that a user can navigate. For example, a root 111e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * could represent an account or a physical storage device. Similar to 112e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * documents, each root can have capabilities expressed through 113e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Root#COLUMN_FLAGS}. 114e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p> 115aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 116aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see Intent#ACTION_OPEN_DOCUMENT 117aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see Intent#ACTION_CREATE_DOCUMENT 118aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 119aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeypublic abstract class DocumentsProvider extends ContentProvider { 120aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey private static final String TAG = "DocumentsProvider"; 121aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 122a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey private static final int MATCH_ROOTS = 1; 123a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey private static final int MATCH_ROOT = 2; 124a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey private static final int MATCH_RECENT = 3; 1253e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey private static final int MATCH_SEARCH = 4; 1263e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey private static final int MATCH_DOCUMENT = 5; 1273e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey private static final int MATCH_CHILDREN = 6; 128aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 129aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey private String mAuthority; 130aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 131aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey private UriMatcher mMatcher; 132aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 133ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 134ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. 135ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 136aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 137aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public void attachInfo(Context context, ProviderInfo info) { 138aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey mAuthority = info.authority; 139aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 140aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 141a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey mMatcher.addURI(mAuthority, "root", MATCH_ROOTS); 142a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT); 143ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); 1443e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH); 145ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); 146ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); 147aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 148aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey // Sanity check our setup 149aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (!info.exported) { 150aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new SecurityException("Provider must be exported"); 151aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 152aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (!info.grantUriPermissions) { 153aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new SecurityException("Provider must grantUriPermissions"); 154aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 155aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission) 156aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) { 157aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS"); 158aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 159aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 160aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey super.attachInfo(context, info); 161aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 162aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 163aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 164e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Create a new document and return its newly generated 165e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_DOCUMENT_ID}. A provider must allocate a new 166e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must 167e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * not change once returned. 168aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 169e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param parentDocumentId the parent directory to create the new document 170e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * under. 171e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param mimeType the concrete MIME type associated with the new document. 172e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * If the MIME type is not supported, the provider must throw. 173e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param displayName the display name of the new document. The provider may 174e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * alter this name to meet any internal constraints, such as 175e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * conflicting names. 176aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 177aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @SuppressWarnings("unused") 178e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey public String createDocument(String parentDocumentId, String mimeType, String displayName) 179aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throws FileNotFoundException { 180aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Create not supported"); 181aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 182aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 183aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 184e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Delete the requested document. Upon returning, any URI permission grants 185e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * for the requested document will be revoked. If additional documents were 186e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * deleted as a side effect of this call, such as documents inside a 187e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * directory, the implementor is responsible for revoking those permissions. 188aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 189ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @param documentId the document to delete. 190aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 191aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @SuppressWarnings("unused") 192ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public void deleteDocument(String documentId) throws FileNotFoundException { 193ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throw new UnsupportedOperationException("Delete not supported"); 194aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 195aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 196e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey /** 197e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return all roots currently provided. A provider must define at least one 198e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * root to display to users, and it should avoid making network requests to 199e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * keep this request fast. 200e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 201e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Each root is defined by the metadata columns described in {@link Root}, 202e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory 203e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * representing a tree of documents to display under that root. 204e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 205e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri, 206e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android.database.ContentObserver)} to notify the system. 207e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * 208e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param projection list of {@link Root} columns to put into the cursor. If 209e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@code null} all supported columns should be included. 210e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey */ 211ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 212ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey 213e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey /** 214e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return recently modified documents under the requested root. This will 215e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * only be called for roots that advertise 216e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be 217e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and 218e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * limited to only return the 64 most recently modified documents. 219e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * 220e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param projection list of {@link Document} columns to put into the 221e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cursor. If {@code null} all supported columns should be 222e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * included. 223e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_LOADING 224e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey */ 225aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @SuppressWarnings("unused") 226ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public Cursor queryRecentDocuments(String rootId, String[] projection) 227ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throws FileNotFoundException { 228ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throw new UnsupportedOperationException("Recent not supported"); 229aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 230aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 231aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 232e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return metadata for the single requested document. A provider should 233e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * avoid making network requests to keep this request fast. 234aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 235ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @param documentId the document to return. 236e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param projection list of {@link Document} columns to put into the 237e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cursor. If {@code null} all supported columns should be 238e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * included. 239aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 240ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public abstract Cursor queryDocument(String documentId, String[] projection) 241ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throws FileNotFoundException; 242aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 243aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 244e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return the children documents contained in the requested directory. This 245e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * must only return immediate descendants, as additional queries will be 246e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * issued to recursively explore the tree. 247e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 248e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * If your provider is cloud-based, and you have some data cached or pinned 249e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * locally, you may return the local data immediately, setting 250e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 251e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * your provider is still fetching additional data. Then, when the network 252e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * data is available, you can call {@link ContentResolver#notifyChange(Uri, 253e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * android.database.ContentObserver)} to trigger a requery and return the 254e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * complete contents. 255aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 256ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @param parentDocumentId the directory to return children for. 257e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param projection list of {@link Document} columns to put into the 258e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cursor. If {@code null} all supported columns should be 259e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * included. 260e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param sortOrder how to order the rows, formatted as an SQL 261e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@code ORDER BY} clause (excluding the ORDER BY itself). 262e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Passing {@code null} will use the default sort order, which 263e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * may be unordered. This ordering is a hint that can be used to 264e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * prioritize how data is fetched from the network, but UI may 265e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * always enforce a specific ordering. 266e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_LOADING 267e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_INFO 268e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_ERROR 269aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 270ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public abstract Cursor queryChildDocuments( 271ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey String parentDocumentId, String[] projection, String sortOrder) 272ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throws FileNotFoundException; 273aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 2744ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey /** {@hide} */ 2754ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey @SuppressWarnings("unused") 2764ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey public Cursor queryChildDocumentsForManage( 2774ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey String parentDocumentId, String[] projection, String sortOrder) 2784ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey throws FileNotFoundException { 2794ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey throw new UnsupportedOperationException("Manage not supported"); 2804ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey } 2814ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey 282aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 283e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return documents that that match the given query under the requested 284e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * root. The returned documents should be sorted by relevance in descending 285e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * order. How documents are matched against the query string is an 286e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * implementation detail left to each provider, but it's suggested that at 287e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a 288e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * case-insensitive fashion. 289e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 290e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Only documents may be returned; directories are not supported in search 291e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * results. 292aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 2933e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey * @param rootId the root to search under. 294e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param query string to match documents against. 295e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param projection list of {@link Document} columns to put into the 296e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cursor. If {@code null} all supported columns should be 297e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * included. 298e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_LOADING 299e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_INFO 300e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see DocumentsContract#EXTRA_ERROR 301aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 302aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @SuppressWarnings("unused") 3033e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey public Cursor querySearchDocuments(String rootId, String query, String[] projection) 304ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey throws FileNotFoundException { 305aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Search not supported"); 306aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 307aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 308aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 309e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Return concrete MIME type of the requested document. Must match the value 310e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * of {@link Document#COLUMN_MIME_TYPE} for this document. The default 311e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * implementation queries {@link #queryDocument(String, String[])}, so 312e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * providers may choose to override this as an optimization. 313aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 314ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public String getDocumentType(String documentId) throws FileNotFoundException { 315ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final Cursor cursor = queryDocument(documentId, null); 316aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey try { 317aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (cursor.moveToFirst()) { 318ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 319aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } else { 320aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return null; 321aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 322aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } finally { 323aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey IoUtils.closeQuietly(cursor); 324aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 325aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 326aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 327aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 328e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Open and return the requested document. 329e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 330e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * A provider should return a reliable {@link ParcelFileDescriptor} to 331e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * detect when the remote caller has finished reading or writing the 332e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * document. A provider may return a pipe or socket pair if the mode is 333e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * exclusively {@link ParcelFileDescriptor#MODE_READ_ONLY} or 334aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * {@link ParcelFileDescriptor#MODE_WRITE_ONLY}, but complex modes like 335aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * {@link ParcelFileDescriptor#MODE_READ_WRITE} require a normal file on 336e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * disk. 337e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 338e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * If a provider blocks while downloading content, it should periodically 339e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * check {@link CancellationSignal#isCanceled()} to abort abandoned open 340e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * requests. 341aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 342e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param documentId the document to return. 343aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @param mode the mode to open with, such as 'r', 'w', or 'rw'. 344aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @param signal used by the caller to signal if the request should be 345aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * cancelled. 346aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 347aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * OnCloseListener) 348aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see ParcelFileDescriptor#createReliablePipe() 349aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see ParcelFileDescriptor#createReliableSocketPair() 350e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @see ParcelFileDescriptor#parseMode(String) 351aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 352aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public abstract ParcelFileDescriptor openDocument( 353e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey String documentId, String mode, CancellationSignal signal) throws FileNotFoundException; 354aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 355aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey /** 356e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Open and return a thumbnail of the requested document. 357e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 358e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * A provider should return a thumbnail closely matching the hinted size, 359e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * attempting to serve from a local cache if possible. A provider should 360e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * never return images more than double the hinted size. 361e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p> 362e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * If a provider performs expensive operations to download or generate a 363e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * thumbnail, it should periodically check 364e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link CancellationSignal#isCanceled()} to abort abandoned thumbnail 365e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * requests. 366aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * 367e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * @param documentId the document to return. 368aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @param sizeHint hint of the optimal thumbnail dimensions. 369aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @param signal used by the caller to signal if the request should be 370aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * cancelled. 371ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see Document#FLAG_SUPPORTS_THUMBNAIL 372aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */ 373aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @SuppressWarnings("unused") 374aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public AssetFileDescriptor openDocumentThumbnail( 375e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey String documentId, Point sizeHint, CancellationSignal signal) 376e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey throws FileNotFoundException { 377aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Thumbnails not supported"); 378aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 379aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 380ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 381ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 382ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 383ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #queryRoots(String[]) 384ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #queryRecentDocuments(String, String[]) 385ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #queryDocument(String, String[]) 386ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #queryChildDocuments(String, String[], String) 387ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #querySearchDocuments(String, String, String[]) 388ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 389aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 390ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey public final Cursor query(Uri uri, String[] projection, String selection, 391ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey String[] selectionArgs, String sortOrder) { 392aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey try { 393aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey switch (mMatcher.match(uri)) { 394a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey case MATCH_ROOTS: 395ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return queryRoots(projection); 396ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey case MATCH_RECENT: 397ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return queryRecentDocuments(getRootId(uri), projection); 3983e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey case MATCH_SEARCH: 3993e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey return querySearchDocuments( 4003e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey getRootId(uri), getSearchDocumentsQuery(uri), projection); 401aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey case MATCH_DOCUMENT: 402ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return queryDocument(getDocumentId(uri), projection); 403aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey case MATCH_CHILDREN: 4044ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey if (DocumentsContract.isManageMode(uri)) { 4054ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey return queryChildDocumentsForManage( 4064ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey getDocumentId(uri), projection, sortOrder); 4074ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey } else { 4084ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey return queryChildDocuments(getDocumentId(uri), projection, sortOrder); 4094ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey } 410aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey default: 411aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Unsupported Uri " + uri); 412aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 413aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } catch (FileNotFoundException e) { 414aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey Log.w(TAG, "Failed during query", e); 415aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return null; 416aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 417aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 418aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 419ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 420ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 421ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 422ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #getDocumentType(String) 423ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 424aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 425aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final String getType(Uri uri) { 426aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey try { 427aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey switch (mMatcher.match(uri)) { 428a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey case MATCH_ROOT: 429a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey return DocumentsContract.Root.MIME_TYPE_ITEM; 430aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey case MATCH_DOCUMENT: 431ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return getDocumentType(getDocumentId(uri)); 432aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey default: 433aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return null; 434aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 435aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } catch (FileNotFoundException e) { 436aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey Log.w(TAG, "Failed during getType", e); 437aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return null; 438aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 439aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 440aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 441ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 442ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. Throws by default, and 443ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * cannot be overriden. 444ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 445ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #createDocument(String, String, String) 446ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 447aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 448aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final Uri insert(Uri uri, ContentValues values) { 449aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Insert not supported"); 450aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 451aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 452ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 453ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. Throws by default, and 454ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * cannot be overriden. 455ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 456ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #deleteDocument(String) 457ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 458aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 459aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final int delete(Uri uri, String selection, String[] selectionArgs) { 460aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Delete not supported"); 461aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 462aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 463ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 464ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * Implementation is provided by the parent class. Throws by default, and 465ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * cannot be overriden. 466ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 467aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 468aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final int update( 469aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey Uri uri, ContentValues values, String selection, String[] selectionArgs) { 470aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Update not supported"); 471aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 472aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 473911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey /** 474911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * Implementation is provided by the parent class. Can be overridden to 475911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * provide additional functionality, but subclasses <em>must</em> always 476911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * call the superclass. If the superclass returns {@code null}, the subclass 477911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * may implement custom behavior. 478911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * 479911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * @see #openDocument(String, String, CancellationSignal) 480911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey * @see #deleteDocument(String) 481911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey */ 482aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 483911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey public Bundle call(String method, String arg, Bundle extras) { 484e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey final Context context = getContext(); 485e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey 486aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (!method.startsWith("android:")) { 487aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey // Let non-platform methods pass through 488911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey return super.call(method, arg, extras); 489aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 490aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 491ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); 492ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); 493e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey 494e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey // Require that caller can manage requested document 495e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey final boolean callerHasManage = 496e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey context.checkCallingOrSelfPermission(android.Manifest.permission.MANAGE_DOCUMENTS) 497e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey == PackageManager.PERMISSION_GRANTED; 498e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey if (!callerHasManage) { 499e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey getContext().enforceCallingOrSelfUriPermission( 500e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); 501e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey } 502aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 503aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey final Bundle out = new Bundle(); 504aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey try { 505ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey if (METHOD_CREATE_DOCUMENT.equals(method)) { 506ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); 507ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 508aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 509ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey final String newDocumentId = createDocument(documentId, mimeType, displayName); 510ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); 511aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 512e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey // Extend permission grant towards caller if needed 513e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey if (!callerHasManage) { 514e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey final Uri newDocumentUri = DocumentsContract.buildDocumentUri( 515e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey mAuthority, newDocumentId); 516911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey context.grantUriPermission(getCallingPackage(), newDocumentUri, 517e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey Intent.FLAG_GRANT_READ_URI_PERMISSION 518e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 519e66c1778f80f4b18e29e018eca3a338f125f23b9Jeff Sharkey | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 520e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey } 521e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey 522aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 523e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey deleteDocument(documentId); 524e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey 525e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey // Document no longer exists, clean up any grants 526e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey context.revokeUriPermission(documentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION 527e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey | Intent.FLAG_GRANT_WRITE_URI_PERMISSION 528e66c1778f80f4b18e29e018eca3a338f125f23b9Jeff Sharkey | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); 529aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 530aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } else { 531aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new UnsupportedOperationException("Method not supported " + method); 532aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 533aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } catch (FileNotFoundException e) { 534aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throw new IllegalStateException("Failed call " + method, e); 535aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 536aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return out; 537aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 538aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 539ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 540e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 541ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 542ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #openDocument(String, String, CancellationSignal) 543ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 544aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 545aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 546ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return openDocument(getDocumentId(uri), mode, null); 547aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 548aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 549ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 550e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 551ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 552ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #openDocument(String, String, CancellationSignal) 553ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 554aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 555aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 556aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throws FileNotFoundException { 557ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return openDocument(getDocumentId(uri), mode, signal); 558aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 559aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 560ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 561e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 562ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 563ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #openDocumentThumbnail(String, Point, CancellationSignal) 564ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 565aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 566aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 567aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throws FileNotFoundException { 568aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 569aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 570ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); 571aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } else { 572aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return super.openTypedAssetFile(uri, mimeTypeFilter, opts); 573aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 574aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 575aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey 576ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey /** 577e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Implementation is provided by the parent class. Cannot be overriden. 578ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * 579ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey * @see #openDocumentThumbnail(String, Point, CancellationSignal) 580ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey */ 581aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey @Override 582aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey public final AssetFileDescriptor openTypedAssetFile( 583aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 584aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey throws FileNotFoundException { 585aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { 586aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); 587ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); 588aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } else { 589aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); 590aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 591aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey } 592aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey} 593