1 /*
2  * Copyright (C) 2016 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.roots;
18 
19 import static com.android.documentsui.base.SharedMinimal.DEBUG;
20 import static com.android.documentsui.base.SharedMinimal.VERBOSE;
21 
22 import android.util.Log;
23 
24 import androidx.annotation.Nullable;
25 
26 import com.android.documentsui.base.MimeTypes;
27 import com.android.documentsui.base.RootInfo;
28 import com.android.documentsui.base.State;
29 import com.android.documentsui.base.UserId;
30 
31 import java.util.ArrayList;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.List;
35 
36 /**
37  * Provides testable access to key {@link ProvidersCache} methods.
38  */
39 public interface ProvidersAccess {
40 
41     String BROADCAST_ACTION = "com.android.documentsui.action.ROOT_CHANGED";
42 
43     /**
44      * Return the requested {@link RootInfo}, but only loading the roots for the
45      * requested user and authority. This is useful when we want to load fast without
46      * waiting for all the other roots to come back.
47      */
getRootOneshot(UserId userId, String authority, String rootId)48     RootInfo getRootOneshot(UserId userId, String authority, String rootId);
49 
getMatchingRootsBlocking(State state)50     Collection<RootInfo> getMatchingRootsBlocking(State state);
51 
getRootsBlocking()52     Collection<RootInfo> getRootsBlocking();
53 
getDefaultRootBlocking(State state)54     RootInfo getDefaultRootBlocking(State state);
55 
getRecentsRoot(UserId userId)56     RootInfo getRecentsRoot(UserId userId);
57 
getApplicationName(UserId userId, String authority)58     String getApplicationName(UserId userId, String authority);
59 
getPackageName(UserId userId, String authority)60     String getPackageName(UserId userId, String authority);
61 
62     /**
63      * Returns a list of roots for the specified user and authority. If not found, then
64      * an empty list is returned.
65      */
getRootsForAuthorityBlocking(UserId userId, String authority)66     Collection<RootInfo> getRootsForAuthorityBlocking(UserId userId, String authority);
67 
getMatchingRoots(Collection<RootInfo> roots, State state)68     public static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
69 
70         final String tag = "ProvidersAccess";
71 
72         final List<RootInfo> matching = new ArrayList<>();
73         for (RootInfo root : roots) {
74 
75             if (VERBOSE) Log.v(tag, "Evaluationg root: " + root);
76 
77             if (state.action == State.ACTION_CREATE && !root.supportsCreate()) {
78                 if (VERBOSE) Log.v(tag, "Excluding read-only root because: ACTION_CREATE.");
79                 continue;
80             }
81 
82             if (state.action == State.ACTION_PICK_COPY_DESTINATION
83                     && !root.supportsCreate()) {
84                 if (VERBOSE) Log.v(
85                         tag, "Excluding read-only root because: ACTION_PICK_COPY_DESTINATION.");
86                 continue;
87             }
88 
89             if (state.action == State.ACTION_OPEN_TREE && !root.supportsChildren()) {
90                 if (VERBOSE) Log.v(
91                         tag, "Excluding root !supportsChildren because: ACTION_OPEN_TREE.");
92                 continue;
93             }
94 
95             if (state.action == State.ACTION_OPEN_TREE && root.isRecents()) {
96                 if (VERBOSE) Log.v(
97                         tag, "Excluding recent root because: ACTION_OPEN_TREE.");
98                 continue;
99             }
100 
101             if (state.localOnly && !root.isLocalOnly()) {
102                 if (VERBOSE) Log.v(tag, "Excluding root because: unwanted non-local device.");
103                 continue;
104             }
105 
106             if (state.action == State.ACTION_OPEN && root.isEmpty()) {
107                 if (VERBOSE) Log.v(tag, "Excluding empty root because: ACTION_OPEN.");
108                 continue;
109             }
110 
111             if (state.action == State.ACTION_GET_CONTENT && root.isEmpty()) {
112                 if (VERBOSE) Log.v(tag, "Excluding empty root because: ACTION_GET_CONTENT.");
113                 continue;
114             }
115 
116             if (!UserId.CURRENT_USER.equals(root.userId) && !state.supportsCrossProfile()) {
117                 if (VERBOSE) {
118                     Log.v(tag, "Excluding root because: action does not support cross profile.");
119                 }
120                 continue;
121             }
122 
123             final boolean overlap =
124                     MimeTypes.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
125                     MimeTypes.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
126             if (!overlap) {
127                 if (VERBOSE) Log.v(
128                         tag, "Excluding root because: unsupported content types > "
129                         + Arrays.toString(state.acceptMimes));
130                 continue;
131             }
132 
133             if (state.excludedAuthorities.contains(root.authority)) {
134                 if (VERBOSE) Log.v(tag, "Excluding root because: owned by calling package.");
135                 continue;
136             }
137 
138             matching.add(root);
139         }
140 
141         if (DEBUG) {
142             Log.d(tag, "Matched roots: " + matching);
143         }
144         return matching;
145     }
146 
147     /**
148      * Returns the root should default show on current state.
149      */
getDefaultRoot(Collection<RootInfo> roots, State state)150     static @Nullable RootInfo getDefaultRoot(Collection<RootInfo> roots, State state) {
151         for (RootInfo root : ProvidersAccess.getMatchingRoots(roots, state)) {
152             if (root.isExternalStorage() && state.action == State.ACTION_OPEN_TREE) {
153                 return root;
154             }
155             if (root.isDownloads()) {
156                 return root;
157             }
158         }
159         return null;
160     }
161 
162 }
163