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; 18 19 import static android.content.ContentResolver.wrap; 20 21 import android.content.ContentProviderClient; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.database.Cursor; 25 import android.net.Uri; 26 import android.os.ParcelFileDescriptor; 27 import android.os.RemoteException; 28 import android.provider.DocumentsContract; 29 import android.provider.DocumentsContract.Path; 30 import android.util.Log; 31 32 import androidx.annotation.Nullable; 33 34 import com.android.documentsui.archives.ArchivesProvider; 35 import com.android.documentsui.base.DocumentInfo; 36 import com.android.documentsui.base.RootInfo; 37 import com.android.documentsui.base.State; 38 import com.android.documentsui.base.UserId; 39 40 import java.io.FileNotFoundException; 41 import java.util.ArrayList; 42 import java.util.List; 43 44 /** 45 * Provides synchronous access to {@link DocumentInfo} instances given some identifying information 46 * and some documents API. 47 */ 48 public interface DocumentsAccess { 49 getRootDocument(RootInfo root)50 @Nullable DocumentInfo getRootDocument(RootInfo root); getDocument(Uri uri, UserId userId)51 @Nullable DocumentInfo getDocument(Uri uri, UserId userId); getArchiveDocument(Uri uri, UserId userId)52 @Nullable DocumentInfo getArchiveDocument(Uri uri, UserId userId); 53 isDocumentUri(Uri uri)54 boolean isDocumentUri(Uri uri); 55 56 @Nullable findDocumentPath(Uri uri, UserId userId)57 Path findDocumentPath(Uri uri, UserId userId) 58 throws RemoteException, FileNotFoundException, CrossProfileNoPermissionException; 59 getDocuments(UserId userId, String authority, List<String> docIds)60 List<DocumentInfo> getDocuments(UserId userId, String authority, List<String> docIds) 61 throws RemoteException, CrossProfileNoPermissionException; 62 createDocument(DocumentInfo parentDoc, String mimeType, String displayName)63 @Nullable Uri createDocument(DocumentInfo parentDoc, String mimeType, String displayName); 64 create(Context context, State state)65 public static DocumentsAccess create(Context context, State state) { 66 return new RuntimeDocumentAccess(context, state); 67 } 68 69 public final class RuntimeDocumentAccess implements DocumentsAccess { 70 71 private static final String TAG = "DocumentAccess"; 72 73 private final Context mContext; 74 private final State mState; 75 RuntimeDocumentAccess(Context context, State state)76 private RuntimeDocumentAccess(Context context, State state) { 77 mContext = context; 78 mState = state; 79 } 80 81 @Override 82 @Nullable getRootDocument(RootInfo root)83 public DocumentInfo getRootDocument(RootInfo root) { 84 return getDocument(DocumentsContract.buildDocumentUri(root.authority, root.documentId), 85 root.userId); 86 } 87 88 @Override getDocument(Uri uri, UserId userId)89 public @Nullable DocumentInfo getDocument(Uri uri, UserId userId) { 90 try { 91 if (mState.canInteractWith(userId)) { 92 return DocumentInfo.fromUri(userId.getContentResolver(mContext), uri, userId); 93 } 94 } catch (FileNotFoundException e) { 95 Log.w(TAG, "Couldn't create DocumentInfo for uri: " + uri); 96 } 97 98 return null; 99 } 100 101 @Override getDocuments(UserId userId, String authority, List<String> docIds)102 public List<DocumentInfo> getDocuments(UserId userId, String authority, List<String> docIds) 103 throws RemoteException, CrossProfileNoPermissionException { 104 if (!mState.canInteractWith(userId)) { 105 throw new CrossProfileNoPermissionException(); 106 } 107 try (ContentProviderClient client = DocumentsApplication.acquireUnstableProviderOrThrow( 108 userId.getContentResolver(mContext), authority)) { 109 110 List<DocumentInfo> result = new ArrayList<>(docIds.size()); 111 for (String docId : docIds) { 112 final Uri uri = DocumentsContract.buildDocumentUri(authority, docId); 113 try (final Cursor cursor = client.query(uri, null, null, null, null)) { 114 if (!cursor.moveToNext()) { 115 Log.e(TAG, "Couldn't create DocumentInfo for Uri: " + uri); 116 throw new RemoteException("Failed to move cursor."); 117 } 118 119 result.add(DocumentInfo.fromCursor(cursor, userId, authority)); 120 } 121 } 122 123 return result; 124 } 125 } 126 127 @Override getArchiveDocument(Uri uri, UserId userId)128 public DocumentInfo getArchiveDocument(Uri uri, UserId userId) { 129 return getDocument( 130 ArchivesProvider.buildUriForArchive(uri, ParcelFileDescriptor.MODE_READ_ONLY), 131 userId); 132 } 133 134 @Override isDocumentUri(Uri uri)135 public boolean isDocumentUri(Uri uri) { 136 return DocumentsContract.isDocumentUri(mContext, uri); 137 } 138 139 @Override findDocumentPath(Uri docUri, UserId userId)140 public Path findDocumentPath(Uri docUri, UserId userId) 141 throws RemoteException, FileNotFoundException, CrossProfileNoPermissionException { 142 if (!mState.canInteractWith(userId)) { 143 throw new CrossProfileNoPermissionException(); 144 } 145 final ContentResolver resolver = userId.getContentResolver(mContext); 146 try (final ContentProviderClient client = DocumentsApplication 147 .acquireUnstableProviderOrThrow(resolver, docUri.getAuthority())) { 148 return DocumentsContract.findDocumentPath(wrap(client), docUri); 149 } 150 } 151 152 @Override createDocument(DocumentInfo parentDoc, String mimeType, String displayName)153 public Uri createDocument(DocumentInfo parentDoc, String mimeType, String displayName) { 154 final ContentResolver resolver = parentDoc.userId.getContentResolver(mContext); 155 try (ContentProviderClient client = DocumentsApplication.acquireUnstableProviderOrThrow( 156 resolver, parentDoc.derivedUri.getAuthority())) { 157 Uri createUri = DocumentsContract.createDocument( 158 wrap(client), parentDoc.derivedUri, mimeType, displayName); 159 // If the document info's user is the current user, we can simply return the uri. 160 // Otherwise, we need to create document with the content resolver from the other 161 // user. The uri returned from that content resolver does not contain the user 162 // info. Hence we need to append the other user info to the uri otherwise an app 163 // will think the uri is from the current user. 164 // The way to append a userInfo is to use the authority which contains user info 165 // obtained from the parentDoc.getDocumentUri(). 166 return UserId.CURRENT_USER.equals(parentDoc.userId) 167 ? createUri : appendEncodedParentAuthority(parentDoc, createUri); 168 } catch (Exception e) { 169 Log.w(TAG, "Failed to create document", e); 170 return null; 171 } 172 } 173 appendEncodedParentAuthority(DocumentInfo parentDoc, Uri uri)174 private Uri appendEncodedParentAuthority(DocumentInfo parentDoc, Uri uri) { 175 return uri.buildUpon().encodedAuthority( 176 parentDoc.getDocumentUri().getAuthority()).build(); 177 } 178 } 179 } 180