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