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 private final Context mContext; 73 private final State mState; 74 RuntimeDocumentAccess(Context context, State state)75 private RuntimeDocumentAccess(Context context, State state) { 76 mContext = context; 77 mState = state; 78 } 79 80 @Override 81 @Nullable getRootDocument(RootInfo root)82 public DocumentInfo getRootDocument(RootInfo root) { 83 return getDocument(DocumentsContract.buildDocumentUri(root.authority, root.documentId), 84 root.userId); 85 } 86 87 @Override getDocument(Uri uri, UserId userId)88 public @Nullable DocumentInfo getDocument(Uri uri, UserId userId) { 89 try { 90 if (mState.canInteractWith(userId)) { 91 return DocumentInfo.fromUri(userId.getContentResolver(mContext), uri, userId); 92 } 93 } catch (FileNotFoundException e) { 94 Log.w(TAG, "Couldn't create DocumentInfo for uri: " + uri); 95 } 96 97 return null; 98 } 99 100 @Override getDocuments(UserId userId, String authority, List<String> docIds)101 public List<DocumentInfo> getDocuments(UserId userId, String authority, List<String> docIds) 102 throws RemoteException, CrossProfileNoPermissionException { 103 if (!mState.canInteractWith(userId)) { 104 throw new CrossProfileNoPermissionException(); 105 } 106 try (ContentProviderClient client = DocumentsApplication.acquireUnstableProviderOrThrow( 107 userId.getContentResolver(mContext), authority)) { 108 109 List<DocumentInfo> result = new ArrayList<>(docIds.size()); 110 for (String docId : docIds) { 111 final Uri uri = DocumentsContract.buildDocumentUri(authority, docId); 112 try (final Cursor cursor = client.query(uri, null, null, null, null)) { 113 if (!cursor.moveToNext()) { 114 Log.e(TAG, "Couldn't create DocumentInfo for Uri: " + uri); 115 throw new RemoteException("Failed to move cursor."); 116 } 117 118 result.add(DocumentInfo.fromCursor(cursor, userId, authority)); 119 } 120 } 121 122 return result; 123 } 124 } 125 126 @Override getArchiveDocument(Uri uri, UserId userId)127 public DocumentInfo getArchiveDocument(Uri uri, UserId userId) { 128 return getDocument( 129 ArchivesProvider.buildUriForArchive(uri, ParcelFileDescriptor.MODE_READ_ONLY), 130 userId); 131 } 132 133 @Override isDocumentUri(Uri uri)134 public boolean isDocumentUri(Uri uri) { 135 return DocumentsContract.isDocumentUri(mContext, uri); 136 } 137 138 @Override findDocumentPath(Uri docUri, UserId userId)139 public Path findDocumentPath(Uri docUri, UserId userId) 140 throws RemoteException, FileNotFoundException, CrossProfileNoPermissionException { 141 if (!mState.canInteractWith(userId)) { 142 throw new CrossProfileNoPermissionException(); 143 } 144 final ContentResolver resolver = userId.getContentResolver(mContext); 145 try (final ContentProviderClient client = DocumentsApplication 146 .acquireUnstableProviderOrThrow(resolver, docUri.getAuthority())) { 147 return DocumentsContract.findDocumentPath(wrap(client), docUri); 148 } 149 } 150 151 @Override createDocument(DocumentInfo parentDoc, String mimeType, String displayName)152 public Uri createDocument(DocumentInfo parentDoc, String mimeType, String displayName) { 153 final ContentResolver resolver = parentDoc.userId.getContentResolver(mContext); 154 try (ContentProviderClient client = DocumentsApplication.acquireUnstableProviderOrThrow( 155 resolver, parentDoc.derivedUri.getAuthority())) { 156 Uri createUri = DocumentsContract.createDocument( 157 wrap(client), parentDoc.derivedUri, mimeType, displayName); 158 // If the document info's user is the current user, we can simply return the uri. 159 // Otherwise, we need to create document with the content resolver from the other 160 // user. The uri returned from that content resolver does not contain the user 161 // info. Hence we need to append the other user info to the uri otherwise an app 162 // will think the uri is from the current user. 163 // The way to append a userInfo is to use the authority which contains user info 164 // obtained from the parentDoc.getDocumentUri(). 165 return UserId.CURRENT_USER.equals(parentDoc.userId) 166 ? createUri : appendEncodedParentAuthority(parentDoc, createUri); 167 } catch (Exception e) { 168 Log.w(TAG, "Failed to create document", e); 169 return null; 170 } 171 } 172 appendEncodedParentAuthority(DocumentInfo parentDoc, Uri uri)173 private Uri appendEncodedParentAuthority(DocumentInfo parentDoc, Uri uri) { 174 return uri.buildUpon().encodedAuthority( 175 parentDoc.getDocumentUri().getAuthority()).build(); 176 } 177 } 178 } 179