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