diff options
19 files changed, 809 insertions, 129 deletions
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index f08294ecf..d075651a7 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -74,6 +74,7 @@ import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.sidebar.RootsFragment; import com.android.documentsui.sorting.SortController; import com.android.documentsui.sorting.SortModel; +import com.android.documentsui.util.FeatureFlagUtils; import com.google.android.material.appbar.AppBarLayout; @@ -91,6 +92,7 @@ public abstract class BaseActivity protected SearchViewManager mSearchManager; protected AppsRowManager mAppsRowManager; protected UserIdManager mUserIdManager; + protected UserManagerState mUserManagerState; protected State mState; @Injected @@ -122,8 +124,10 @@ public abstract class BaseActivity } protected abstract void refreshDirectory(int anim); + /** Allows sub-classes to include information in a newly created State instance. */ protected abstract void includeState(State initialState); + protected abstract void onDirectoryCreated(DocumentInfo doc); public abstract Injector<?> getInjector(); @@ -161,12 +165,11 @@ public abstract class BaseActivity setSupportActionBar(toolbar); Breadcrumb breadcrumb = findViewById(R.id.horizontal_breadcrumb); - assert(breadcrumb != null); + assert (breadcrumb != null); View profileTabsContainer = findViewById(R.id.tabs_container); assert (profileTabsContainer != null); - mNavigator = new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, - profileTabsContainer, DocumentsApplication.getUserIdManager(this)); + mNavigator = getNavigationViewManager(breadcrumb, profileTabsContainer); AppBarLayout appBarLayout = findViewById(R.id.app_bar); if (appBarLayout != null) { appBarLayout.addOnOffsetChangedListener(mNavigator); @@ -264,6 +267,7 @@ public abstract class BaseActivity ViewGroup chipGroup = findViewById(R.id.search_chip_group); mUserIdManager = DocumentsApplication.getUserIdManager(this); + mUserManagerState = DocumentsApplication.getUserManagerState(this); mSearchManager = new SearchViewManager(searchListener, queryInterceptor, chipGroup, savedInstanceState); // initialize the chip sets by accept mime types @@ -329,6 +333,16 @@ public abstract class BaseActivity setResult(AppCompatActivity.RESULT_CANCELED); } + private NavigationViewManager getNavigationViewManager(Breadcrumb breadcrumb, + View profileTabsContainer) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, + profileTabsContainer, DocumentsApplication.getUserManagerState(this)); + } + return new NavigationViewManager(this, mDrawer, mState, this, breadcrumb, + profileTabsContainer, DocumentsApplication.getUserIdManager(this)); + } + public void onPreferenceChanged(String pref) { // For now, we only work with prefs that we backup. This // just limits the scope of what we expect to come flowing @@ -386,6 +400,7 @@ public abstract class BaseActivity mRootsMonitor.stop(); mPreferencesMonitor.stop(); mSortController.destroy(); + DocumentsApplication.invalidateUserManagerState(this); super.onDestroy(); } @@ -544,7 +559,6 @@ public abstract class BaseActivity /** * Returns true if a directory can be created in the current location. - * @return */ protected boolean canCreateDirectory() { final RootInfo root = getCurrentRoot(); @@ -582,7 +596,6 @@ public abstract class BaseActivity /** * Refreshes the content of the director and the menu/action bar. * The current directory name and selection will get updated. - * @param anim */ @Override public final void refreshCurrentRootAndDirectory(int anim) { @@ -632,7 +645,7 @@ public abstract class BaseActivity try { PackageInfo pkgInfo = getPackageManager().getPackageInfo(packageName, PackageManager.GET_PROVIDERS); - for (ProviderInfo provider: pkgInfo.providers) { + for (ProviderInfo provider : pkgInfo.providers) { authorities.add(provider.authority); } } catch (PackageManager.NameNotFoundException e) { @@ -708,9 +721,14 @@ public abstract class BaseActivity } } - public void updateHeader(boolean shouldHideHeader){ + /** + * Updates headerContainer by setting its visibility + * + * @param shouldHideHeader whether to hide header container or not + */ + public void updateHeader(boolean shouldHideHeader) { View headerContainer = findViewById(R.id.header_container); - if(headerContainer == null){ + if (headerContainer == null) { updateHeaderTitle(); return; } @@ -779,7 +797,7 @@ public abstract class BaseActivity private String getHeaderDownloadsTitle() { return getString(mState.isPhotoPicking() - ? R.string.root_info_header_image_downloads : R.string.root_info_header_downloads); + ? R.string.root_info_header_image_downloads : R.string.root_info_header_downloads); } private String getHeaderStorageTitle(String rootTitle) { @@ -809,6 +827,7 @@ public abstract class BaseActivity /** * Get title string equal to the string action bar displayed. + * * @return current directory title name */ public String getCurrentTitle() { diff --git a/src/com/android/documentsui/DirectoryLoader.java b/src/com/android/documentsui/DirectoryLoader.java index 816144758..982dd097b 100644 --- a/src/com/android/documentsui/DirectoryLoader.java +++ b/src/com/android/documentsui/DirectoryLoader.java @@ -48,6 +48,7 @@ import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.sorting.SortModel; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.List; @@ -129,8 +130,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { if (mSearchMode) { queryArgs.putAll(mQueryArgs); if (shouldSearchAcrossProfile()) { - for (UserId userId : DocumentsApplication.getUserIdManager( - getContext()).getUserIds()) { + for (UserId userId : getUserIds()) { if (mState.canInteractWith(userId)) { userIds.add(userId); } @@ -330,4 +330,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } return false; } + + private List<UserId> getUserIds() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return DocumentsApplication.getUserManagerState(getContext()).getUserIds(); + } + return DocumentsApplication.getUserIdManager(getContext()).getUserIds(); + } } diff --git a/src/com/android/documentsui/DocumentsAccess.java b/src/com/android/documentsui/DocumentsAccess.java index e3b6abde3..fcd3471fc 100644 --- a/src/com/android/documentsui/DocumentsAccess.java +++ b/src/com/android/documentsui/DocumentsAccess.java @@ -69,7 +69,6 @@ public interface DocumentsAccess { public final class RuntimeDocumentAccess implements DocumentsAccess { private static final String TAG = "DocumentAccess"; - private final Context mContext; private final State mState; diff --git a/src/com/android/documentsui/DocumentsApplication.java b/src/com/android/documentsui/DocumentsApplication.java index c576851b7..beff84da9 100644 --- a/src/com/android/documentsui/DocumentsApplication.java +++ b/src/com/android/documentsui/DocumentsApplication.java @@ -41,10 +41,13 @@ import com.android.documentsui.clipping.DocumentClipper; import com.android.documentsui.queries.SearchHistoryManager; import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.theme.ThemeOverlayManager; +import com.android.documentsui.util.FeatureFlagUtils; +import com.android.modules.utils.build.SdkLevel; import com.google.common.collect.Lists; import java.util.List; +import java.util.Objects; public class DocumentsApplication extends Application { private static final String TAG = "DocumentsApplication"; @@ -57,7 +60,7 @@ public class DocumentsApplication extends Application { Intent.ACTION_PACKAGE_DATA_CLEARED ); - private static final List<String> MANAGED_PROFILE_FILTER_ACTIONS = Lists.newArrayList( + private static final List<String> PROFILE_FILTER_ACTIONS = Lists.newArrayList( Intent.ACTION_MANAGED_PROFILE_ADDED, Intent.ACTION_MANAGED_PROFILE_REMOVED, Intent.ACTION_MANAGED_PROFILE_UNLOCKED, @@ -70,6 +73,7 @@ public class DocumentsApplication extends Application { private DocumentClipper mClipper; private DragAndDropManager mDragAndDropManager; private UserIdManager mUserIdManager; + private UserManagerState mUserManagerState; private Lookup<String, String> mFileTypeLookup; public static ProvidersCache getProvidersCache(Context context) { @@ -104,6 +108,16 @@ public class DocumentsApplication extends Application { return ((DocumentsApplication) context.getApplicationContext()).mUserIdManager; } + /** + * UserManagerState class is used to maintain the list of userIds and other details like + * cross profile access, label and badge associated with these userIds. + */ + public static UserManagerState getUserManagerState(Context context) { + return Objects.requireNonNullElseGet( + ((DocumentsApplication) context.getApplicationContext()).mUserManagerState, + () -> UserManagerState.create(context)); + } + public static DragAndDropManager getDragAndDropManager(Context context) { return ((DocumentsApplication) context.getApplicationContext()).mDragAndDropManager; } @@ -112,6 +126,14 @@ public class DocumentsApplication extends Application { return ((DocumentsApplication) context.getApplicationContext()).mFileTypeLookup; } + /** + * Set mUserManagerState as null onDestroy of BaseActivity so that new session uses new instance + * of mUserManagerState + */ + public static void invalidateUserManagerState(Context context) { + ((DocumentsApplication) context.getApplicationContext()).mUserManagerState = null; + } + private void onApplyOverlayFinish(boolean result) { Log.d(TAG, "OverlayManager.setEnabled() result: " + result); } @@ -132,9 +154,16 @@ public class DocumentsApplication extends Application { Log.w(TAG, "Can't obtain OverlayManager from System Service!"); } - mUserIdManager = UserIdManager.create(this); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mUserManagerState = UserManagerState.create(this); + mUserIdManager = null; + mProviders = new ProvidersCache(this, mUserManagerState); + } else { + mUserManagerState = null; + mUserIdManager = UserIdManager.create(this); + mProviders = new ProvidersCache(this, mUserIdManager); + } - mProviders = new ProvidersCache(this, mUserIdManager); mProviders.updateAsync(/* forceRefreshAll= */ false, /* callback= */ null); mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4); @@ -159,11 +188,19 @@ public class DocumentsApplication extends Application { localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED); registerReceiver(mCacheReceiver, localeFilter); - final IntentFilter managedProfileFilter = new IntentFilter(); - for (String managedProfileAction : MANAGED_PROFILE_FILTER_ACTIONS) { - managedProfileFilter.addAction(managedProfileAction); + if (SdkLevel.isAtLeastV()) { + PROFILE_FILTER_ACTIONS.addAll(Lists.newArrayList( + Intent.ACTION_PROFILE_ADDED, + Intent.ACTION_PROFILE_REMOVED, + Intent.ACTION_PROFILE_AVAILABLE, + Intent.ACTION_PROFILE_UNAVAILABLE + )); + } + final IntentFilter profileFilter = new IntentFilter(); + for (String profileAction : PROFILE_FILTER_ACTIONS) { + profileFilter.addAction(profileAction); } - registerReceiver(mCacheReceiver, managedProfileFilter); + registerReceiver(mCacheReceiver, profileFilter); SearchHistoryManager.getInstance(getApplicationContext()); } @@ -183,7 +220,7 @@ public class DocumentsApplication extends Application { if (PACKAGE_FILTER_ACTIONS.contains(action) && data != null) { final String packageName = data.getSchemeSpecificPart(); mProviders.updatePackageAsync(UserId.DEFAULT_USER, packageName); - } else if (MANAGED_PROFILE_FILTER_ACTIONS.contains(action)) { + } else if (PROFILE_FILTER_ACTIONS.contains(action)) { // After we have reloaded roots. Resend the broadcast locally so the other // components can reload properly after roots are updated. mProviders.updateAsync(/* forceRefreshAll= */ true, diff --git a/src/com/android/documentsui/NavigationViewManager.java b/src/com/android/documentsui/NavigationViewManager.java index a9dec9e5b..d499aa7b2 100644 --- a/src/com/android/documentsui/NavigationViewManager.java +++ b/src/com/android/documentsui/NavigationViewManager.java @@ -39,6 +39,7 @@ import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.dirlist.AnimationView; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; import com.android.modules.utils.build.SdkLevel; @@ -80,6 +81,29 @@ public class NavigationViewManager implements AppBarLayout.OnOffsetChangedListen Breadcrumb breadcrumb, View tabLayoutContainer, UserIdManager userIdManager) { + this(activity, drawer, state, env, breadcrumb, tabLayoutContainer, userIdManager, null); + } + + public NavigationViewManager( + BaseActivity activity, + DrawerController drawer, + State state, + NavigationViewManager.Environment env, + Breadcrumb breadcrumb, + View tabLayoutContainer, + UserManagerState userManagerState) { + this(activity, drawer, state, env, breadcrumb, tabLayoutContainer, null, userManagerState); + } + + public NavigationViewManager( + BaseActivity activity, + DrawerController drawer, + State state, + NavigationViewManager.Environment env, + Breadcrumb breadcrumb, + View tabLayoutContainer, + UserIdManager userIdManager, + UserManagerState userManagerState) { mActivity = activity; mToolbar = activity.findViewById(R.id.toolbar); @@ -89,7 +113,8 @@ public class NavigationViewManager implements AppBarLayout.OnOffsetChangedListen mEnv = env; mBreadcrumb = breadcrumb; mBreadcrumb.setup(env, state, this::onNavigationItemSelected); - mProfileTabs = new ProfileTabs(tabLayoutContainer, mState, userIdManager, mEnv, activity); + mProfileTabs = + getProfileTabs(tabLayoutContainer, userIdManager, userManagerState, activity); mToolbar.setNavigationOnClickListener( new View.OnClickListener() { @@ -128,6 +153,13 @@ public class NavigationViewManager implements AppBarLayout.OnOffsetChangedListen }; } + private ProfileTabs getProfileTabs(View tabLayoutContainer, UserIdManager userIdManager, + UserManagerState userManagerState, BaseActivity activity) { + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? new ProfileTabs(tabLayoutContainer, mState, userManagerState, mEnv, activity) + : new ProfileTabs(tabLayoutContainer, mState, userIdManager, mEnv, activity); + } + @Override public void onOffsetChanged(AppBarLayout appBarLayout, int offset) { if (!VersionUtils.isAtLeastS()) { diff --git a/src/com/android/documentsui/ProfileTabs.java b/src/com/android/documentsui/ProfileTabs.java index 59a94e62b..ded78e031 100644 --- a/src/com/android/documentsui/ProfileTabs.java +++ b/src/com/android/documentsui/ProfileTabs.java @@ -32,6 +32,7 @@ import androidx.annotation.RequiresApi; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; import com.google.android.material.tabs.TabLayout; @@ -51,7 +52,10 @@ public class ProfileTabs implements ProfileTabsAddons { private final State mState; private final NavigationViewManager.Environment mEnv; private final AbstractActionHandler.CommonAddons mCommonAddons; + @Nullable private final UserIdManager mUserIdManager; + @Nullable + private final UserManagerState mUserManagerState; private List<UserId> mUserIds; @Nullable private Listener mListener; @@ -61,12 +65,30 @@ public class ProfileTabs implements ProfileTabsAddons { public ProfileTabs(View tabLayoutContainer, State state, UserIdManager userIdManager, NavigationViewManager.Environment env, AbstractActionHandler.CommonAddons commonAddons) { + this(tabLayoutContainer, state, userIdManager, null, env, commonAddons); + } + + public ProfileTabs(View tabLayoutContainer, State state, UserManagerState userManagerState, + NavigationViewManager.Environment env, + AbstractActionHandler.CommonAddons commonAddons) { + this(tabLayoutContainer, state, null, userManagerState, env, commonAddons); + } + + public ProfileTabs(View tabLayoutContainer, State state, @Nullable UserIdManager userIdManager, + @Nullable UserManagerState userManagerState, NavigationViewManager.Environment env, + AbstractActionHandler.CommonAddons commonAddons) { mTabsContainer = checkNotNull(tabLayoutContainer); mTabs = tabLayoutContainer.findViewById(R.id.tabs); mState = checkNotNull(state); mEnv = checkNotNull(env); mCommonAddons = checkNotNull(commonAddons); - mUserIdManager = checkNotNull(userIdManager); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mUserIdManager = userIdManager; + mUserManagerState = checkNotNull(userManagerState); + } else { + mUserIdManager = checkNotNull(userIdManager); + mUserManagerState = userManagerState; + } mTabs.removeAllTabs(); mUserIds = Collections.singletonList(UserId.CURRENT_USER); mTabSeparator = tabLayoutContainer.findViewById(R.id.tab_separator); @@ -110,13 +132,13 @@ public class ProfileTabs implements ProfileTabsAddons { // Material next changes apply only for version S or greater if (SdkLevel.isAtLeastS()) { mTabSeparator.setVisibility(View.GONE); - int tabContainerHeightInDp = (int)mTabsContainer.getContext().getResources(). - getDimension(R.dimen.tab_container_height); + int tabContainerHeightInDp = (int) mTabsContainer.getContext().getResources() + .getDimension(R.dimen.tab_container_height); mTabsContainer.getLayoutParams().height = tabContainerHeightInDp; ViewGroup.MarginLayoutParams tabContainerMarginLayoutParams = - (ViewGroup.MarginLayoutParams) mTabsContainer.getLayoutParams(); - int tabContainerMarginTop = (int)mTabsContainer.getContext().getResources(). - getDimension(R.dimen.profile_tab_margin_top); + (ViewGroup.MarginLayoutParams) mTabsContainer.getLayoutParams(); + int tabContainerMarginTop = (int) mTabsContainer.getContext().getResources() + .getDimension(R.dimen.profile_tab_margin_top); tabContainerMarginLayoutParams.setMargins(0, tabContainerMarginTop, 0, 0); mTabsContainer.requestLayout(); for (int i = 0; i < mTabs.getTabCount(); i++) { @@ -127,11 +149,11 @@ public class ProfileTabs implements ProfileTabsAddons { // Get individual tab to set the style ViewGroup.MarginLayoutParams marginLayoutParams = (ViewGroup.MarginLayoutParams) tab.getLayoutParams(); - int tabMarginSide = (int)mTabsContainer.getContext().getResources(). - getDimension(R.dimen.profile_tab_margin_side); + int tabMarginSide = (int) mTabsContainer.getContext().getResources() + .getDimension(R.dimen.profile_tab_margin_side); marginLayoutParams.setMargins(tabMarginSide, 0, tabMarginSide, 0); - int tabHeightInDp = (int)mTabsContainer.getContext().getResources(). - getDimension(R.dimen.tab_height); + int tabHeightInDp = (int) mTabsContainer.getContext().getResources() + .getDimension(R.dimen.tab_height); tab.getLayoutParams().height = tabHeightInDp; tab.requestLayout(); tab.setBackgroundResource(R.drawable.tab_border_rounded); @@ -145,7 +167,7 @@ public class ProfileTabs implements ProfileTabsAddons { } private void updateTabsIfNeeded() { - List<UserId> userIds = mUserIdManager.getUserIds(); + List<UserId> userIds = getUserIds(); // Add tabs if the userIds is not equals to cached mUserIds. // Given that mUserIds was initialized with only the current user, if getUserIds() // returns just the current user, we don't need to do anything on the tab layout. @@ -164,6 +186,15 @@ public class ProfileTabs implements ProfileTabsAddons { } } + private List<UserId> getUserIds() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + assert mUserManagerState != null; + return mUserManagerState.getUserIds(); + } + assert mUserIdManager != null; + return mUserIdManager.getUserIds(); + } + private String getEnterpriseString(String updatableStringId, int defaultStringId) { if (SdkLevel.isAtLeastT()) { return getUpdatableEnterpriseString(updatableStringId, defaultStringId); diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java index 62ea6d05f..b3cfa0180 100644 --- a/src/com/android/documentsui/RecentsLoader.java +++ b/src/com/android/documentsui/RecentsLoader.java @@ -35,6 +35,7 @@ import java.util.concurrent.Executor; public class RecentsLoader extends MultiRootDocumentsLoader { + private static final String TAG = "RecentsLoader"; /** Ignore documents older than this age. */ private static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS; diff --git a/src/com/android/documentsui/UserManagerState.java b/src/com/android/documentsui/UserManagerState.java index 4facfd9c6..90cf017b9 100644 --- a/src/com/android/documentsui/UserManagerState.java +++ b/src/com/android/documentsui/UserManagerState.java @@ -45,6 +45,7 @@ import androidx.annotation.VisibleForTesting; import com.android.documentsui.base.Features; import com.android.documentsui.base.UserId; +import com.android.documentsui.util.CrossProfileUtils; import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; import com.android.modules.utils.build.SdkLevel; @@ -61,7 +62,7 @@ public interface UserManagerState { /** * Returns the {@link UserId} of each profile which should be queried for documents. This will * always - * include {@link UserId.CURRENT_USER}. + * include {@link UserId#CURRENT_USER}. */ List<UserId> getUserIds(); @@ -78,6 +79,12 @@ public interface UserManagerState { Map<UserId, Drawable> getUserIdToBadgeMap(); /** + * Returns a map of {@link UserId} to boolean value indicating whether + * the {@link UserId}.CURRENT_USER can forward {@link Intent} to that {@link UserId} + */ + Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent); + + /** * Creates an implementation of {@link UserManagerState}. */ // TODO: b/314746383 Make this class a singleton @@ -111,6 +118,13 @@ public interface UserManagerState { */ @GuardedBy("mUserIdToBadgeMap") private final Map<UserId, Drawable> mUserIdToBadgeMap = new HashMap<>(); + /** + * Map containing {@link UserId}, other than that of the current user, as key and boolean + * denoting whether it is accessible by the current user or not as value + */ + @GuardedBy("mCanFrowardToProfileIdMap") + private final Map<UserId, Boolean> mCanFrowardToProfileIdMap = new HashMap<>(); + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @@ -124,6 +138,9 @@ public interface UserManagerState { synchronized (mUserIdToBadgeMap) { mUserIdToBadgeMap.clear(); } + synchronized (mCanFrowardToProfileIdMap) { + mCanFrowardToProfileIdMap.clear(); + } } }; @@ -180,6 +197,16 @@ public interface UserManagerState { } } + @Override + public Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent) { + synchronized (mCanFrowardToProfileIdMap) { + if (mCanFrowardToProfileIdMap.isEmpty()) { + getCanForwardToProfileIdMapInternal(intent); + } + return mCanFrowardToProfileIdMap; + } + } + private List<UserId> getUserIdsInternal() { final List<UserId> result = new ArrayList<>(); @@ -403,6 +430,117 @@ public interface UserManagerState { return drawable; } + private void getCanForwardToProfileIdMapInternal(Intent intent) { + // Versions less than V will not have the user properties required to determine whether + // cross profile check is delegated from parent or not + if (!SdkLevel.isAtLeastV()) { + getCanForwardToProfileIdMapPreV(intent); + return; + } + if (mUserManager == null) { + Log.e(TAG, "can not get user manager"); + return; + } + + List<UserId> parentOrDelegatedFromParent = new ArrayList<>(); + List<UserId> canForwardToProfileIds = new ArrayList<>(); + List<UserId> noDelegation = new ArrayList<>(); + + List<UserId> userIds = getUserIds(); + for (UserId userId : userIds) { + final UserHandle userHandle = UserHandle.of(userId.getIdentifier()); + // Parent (personal) profile and all the child profiles that delegate cross profile + // content sharing check to parent can share among each other + if (userId.getIdentifier() == ActivityManager.getCurrentUser() + || isCrossProfileContentSharingStrategyDelegatedFromParent(userHandle)) { + parentOrDelegatedFromParent.add(userId); + } else { + noDelegation.add(userId); + } + } + + if (noDelegation.size() > 1) { + Log.e(TAG, "There cannot be more than one profile delegating cross profile " + + "content sharing check from self."); + } + + /* + * Cross profile resolve info need to be checked in the following 2 cases: + * 1. current user is either parent or delegates check to parent and the target user + * does + * not delegate to parent + * 2. current user does not delegate check to the parent and the target user is the + * parent profile + */ + UserId needToCheck; + if (parentOrDelegatedFromParent.contains(mCurrentUser) + && !noDelegation.isEmpty()) { + needToCheck = noDelegation.get(0); + } else { + final UserHandle parentProfile = mUserManager.getProfileParent( + UserHandle.of(mCurrentUser.getIdentifier())); + needToCheck = UserId.of(parentProfile); + } + + if (needToCheck != null && CrossProfileUtils.getCrossProfileResolveInfo(mCurrentUser, + mContext.getPackageManager(), intent, mContext) != null) { + if (parentOrDelegatedFromParent.contains(needToCheck)) { + canForwardToProfileIds.addAll(parentOrDelegatedFromParent); + } else { + canForwardToProfileIds.add(needToCheck); + } + } + + if (parentOrDelegatedFromParent.contains(mCurrentUser)) { + canForwardToProfileIds.addAll(parentOrDelegatedFromParent); + } + + for (UserId userId : userIds) { + synchronized (mCanFrowardToProfileIdMap) { + if (userId.equals(mCurrentUser)) { + mCanFrowardToProfileIdMap.put(userId, true); + continue; + } + mCanFrowardToProfileIdMap.put(userId, canForwardToProfileIds.contains(userId)); + } + } + } + + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + private boolean isCrossProfileContentSharingStrategyDelegatedFromParent( + UserHandle userHandle) { + if (mUserManager == null) { + Log.e(TAG, "can not obtain user manager"); + return false; + } + UserProperties userProperties = mUserManager.getUserProperties(userHandle); + if (java.util.Objects.equals(userProperties, null)) { + Log.e(TAG, "can not obtain user properties"); + return false; + } + + return userProperties.getCrossProfileContentSharingStrategy() + == UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT; + } + + private void getCanForwardToProfileIdMapPreV(Intent intent) { + // There only two profiles pre V + List<UserId> userIds = getUserIds(); + for (UserId userId : userIds) { + synchronized (mCanFrowardToProfileIdMap) { + if (mCurrentUser.equals(userId)) { + mCanFrowardToProfileIdMap.put(userId, true); + } else { + mCanFrowardToProfileIdMap.put(userId, + CrossProfileUtils.getCrossProfileResolveInfo( + mCurrentUser, mContext.getPackageManager(), intent, + mContext) + != null); + } + } + } + } + private static boolean isDeviceSupported(Context context) { // The feature requires Android R DocumentsContract APIs and INTERACT_ACROSS_USERS_FULL // permission. diff --git a/src/com/android/documentsui/base/State.java b/src/com/android/documentsui/base/State.java index cff5ce480..ad008c57d 100644 --- a/src/com/android/documentsui/base/State.java +++ b/src/com/android/documentsui/base/State.java @@ -26,6 +26,7 @@ import androidx.annotation.IntDef; import com.android.documentsui.services.FileOperationService; import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.sorting.SortModel; +import com.android.documentsui.util.FeatureFlagUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -33,6 +34,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Map; public class State implements android.os.Parcelable { @@ -100,10 +102,19 @@ public class State implements android.os.Parcelable { * Returns true if we are allowed to interact with the user. */ public boolean canInteractWith(UserId userId) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return canForwardToProfileIdMap.getOrDefault(userId, false); + } return canShareAcrossProfile || UserId.CURRENT_USER.equals(userId); } /** + * Represents whether the intent can be forwarded to the {@link UserId} in the map + */ + public Map<UserId, Boolean> canForwardToProfileIdMap = new HashMap<>(); + + + /** * This is basically a sub-type for the copy operation. It can be either COPY, * COMPRESS, EXTRACT or MOVE. * The only legal values, if set, are: OPERATION_COPY, OPERATION_COMPRESS, diff --git a/src/com/android/documentsui/dirlist/AppsRowManager.java b/src/com/android/documentsui/dirlist/AppsRowManager.java index 60138f69e..3252d153d 100644 --- a/src/com/android/documentsui/dirlist/AppsRowManager.java +++ b/src/com/android/documentsui/dirlist/AppsRowManager.java @@ -16,7 +16,6 @@ package com.android.documentsui.dirlist; -import android.os.UserManager; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -28,6 +27,7 @@ import com.android.documentsui.ActionHandler; import com.android.documentsui.BaseActivity; import com.android.documentsui.R; import com.android.documentsui.UserIdManager; +import com.android.documentsui.UserManagerState; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.dirlist.AppsRowItemData.AppData; @@ -35,6 +35,7 @@ import com.android.documentsui.dirlist.AppsRowItemData.RootData; import com.android.documentsui.sidebar.AppItem; import com.android.documentsui.sidebar.Item; import com.android.documentsui.sidebar.RootItem; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.HashMap; @@ -50,6 +51,7 @@ public class AppsRowManager { private final List<AppsRowItemData> mDataList; private final boolean mMaybeShowBadge; private final UserIdManager mUserIdManager; + private final UserManagerState mUserManagerState; public AppsRowManager(ActionHandler handler, boolean maybeShowBadge, UserIdManager userIdManager) { @@ -57,6 +59,16 @@ public class AppsRowManager { mActionHandler = handler; mMaybeShowBadge = maybeShowBadge; mUserIdManager = userIdManager; + mUserManagerState = null; + } + + public AppsRowManager(ActionHandler handler, boolean maybeShowBadge, + UserManagerState userManagerState) { + mDataList = new ArrayList<>(); + mActionHandler = handler; + mMaybeShowBadge = maybeShowBadge; + mUserIdManager = null; + mUserManagerState = userManagerState; } public List<AppsRowItemData> updateList(List<Item> itemList) { @@ -89,7 +101,7 @@ public class AppsRowManager { boolean isHiddenAction = state.action == State.ACTION_CREATE || state.action == State.ACTION_OPEN_TREE || state.action == State.ACTION_PICK_COPY_DESTINATION; - boolean isSearchExpandedAcrossProfile = mUserIdManager.getUserIds().size() > 1 + boolean isSearchExpandedAcrossProfile = getUserIds().size() > 1 && state.supportsCrossProfile() && isSearchExpanded; @@ -134,4 +146,11 @@ public class AppsRowManager { summary.setVisibility(data.getSummary() != null ? View.VISIBLE : View.GONE); view.setOnClickListener(v -> data.onClicked()); } + + private List<UserId> getUserIds() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return mUserManagerState.getUserIds(); + } + return mUserIdManager.getUserIds(); + } } diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java index 1fc35b360..cbb39fda3 100644 --- a/src/com/android/documentsui/picker/ActionHandler.java +++ b/src/com/android/documentsui/picker/ActionHandler.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.android.documentsui.picker; import static android.provider.DocumentsContract.isDocumentUri; @@ -53,7 +52,6 @@ import com.android.documentsui.DocumentsAccess; import com.android.documentsui.Injector; import com.android.documentsui.MetricConsts; import com.android.documentsui.Metrics; -import com.android.documentsui.UserIdManager; import com.android.documentsui.base.BooleanConsumer; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; @@ -100,8 +98,6 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH private final Features mFeatures; private final ActivityConfig mConfig; private final LastAccessedStorage mLastAccessed; - private final UserIdManager mUserIdManager; - private UpdatePickResultTask mUpdatePickResultTask; ActionHandler( @@ -112,21 +108,19 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH SearchViewManager searchMgr, Lookup<String, Executor> executors, Injector injector, - LastAccessedStorage lastAccessed, - UserIdManager userIdManager) { + LastAccessedStorage lastAccessed) { super(activity, state, providers, docs, searchMgr, executors, injector); mConfig = injector.config; mFeatures = injector.features; mLastAccessed = lastAccessed; mUpdatePickResultTask = new UpdatePickResultTask( - activity.getApplicationContext(), mInjector.pickResult); - mUserIdManager = userIdManager; + activity.getApplicationContext(), mInjector.pickResult); } @Override public void initLocation(Intent intent) { - assert(intent != null); + assert (intent != null); // stack is initialized if it's restored from bundle, which means we're restoring a // previously stored state. @@ -247,7 +241,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH final String docId = DocumentsContract.getDocumentId(uri); final String filePath; try { - filePath = FileUtils.getPathFromStorageDocId(docId); + filePath = FileUtils.getPathFromStorageDocId(docId); } catch (IOException e) { Log.w(TAG, "Could not get canonical file path from docId '" + docId + "'"); return true; @@ -431,7 +425,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH } void pickDocument(FragmentManager fm, DocumentInfo pickTarget) { - assert(pickTarget != null); + assert (pickTarget != null); mInjector.pickResult.increaseActionCount(); Uri result; switch (mState.action) { @@ -450,7 +444,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH void saveDocument( String mimeType, String displayName, BooleanConsumer inProgressStateListener) { - assert(mState.action == ACTION_CREATE); + assert (mState.action == ACTION_CREATE); mInjector.pickResult.increaseActionCount(); new CreatePickedDocumentTask( mActivity, @@ -467,9 +461,9 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH // User requested to overwrite a target. If confirmed by user #finishPicking() will be // called. void saveDocument(FragmentManager fm, DocumentInfo replaceTarget) { - assert(mState.action == ACTION_CREATE); + assert (mState.action == ACTION_CREATE); mInjector.pickResult.increaseActionCount(); - assert(replaceTarget != null); + assert (replaceTarget != null); // Adding a confirmation dialog breaks an inherited CTS test (testCreateExisting), so we // need to add a feature flag to bypass this feature in ARC++ environment. @@ -488,7 +482,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH () -> { onPickFinished(docs); } - ) .executeOnExecutor(getExecutorForCurrentDirectory()); + ).executeOnExecutor(getExecutorForCurrentDirectory()); } private void onPickFinished(Uri... uris) { @@ -509,7 +503,7 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH } updatePickResult( - intent, mSearchMgr.isSearching(), Metrics.sanitizeRoot(mState.stack.getRoot())); + intent, mSearchMgr.isSearching(), Metrics.sanitizeRoot(mState.stack.getRoot())); // TODO: Separate this piece of logic per action. // We don't instantiate different objects for different actions at the first place, so it's @@ -558,4 +552,4 @@ class ActionHandler<T extends FragmentActivity & Addons> extends AbstractActionH @VisibleForTesting void setResult(int resultCode, Intent result, int notUsed); } -} +}
\ No newline at end of file diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java index c2fbd50a0..13b8237de 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -65,6 +65,7 @@ import com.android.documentsui.sidebar.RootsFragment; import com.android.documentsui.ui.DialogController; import com.android.documentsui.ui.MessageBuilder; import com.android.documentsui.util.CrossProfileUtils; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; import java.util.Collection; @@ -85,8 +86,15 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { } // make these methods visible in this package to work around compiler bug http://b/62218600 - @Override protected boolean focusSidebar() { return super.focusSidebar(); } - @Override protected boolean popDir() { return super.popDir(); } + @Override + protected boolean focusSidebar() { + return super.focusSidebar(); + } + + @Override + protected boolean popDir() { + return super.popDir(); + } @Override public void onCreate(Bundle icicle) { @@ -99,7 +107,8 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { new MessageBuilder(this), DialogController.create(features, this), DocumentsApplication.getFileTypeLookup(this), - (Collection<RootInfo> roots) -> {}); + (Collection<RootInfo> roots) -> { + }); super.onCreate(icicle); @@ -138,15 +147,13 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { mSearchManager, ProviderExecutor::forAuthority, mInjector, - LastAccessedStorage.create(), - mUserIdManager); + LastAccessedStorage.create()); mInjector.searchManager = mSearchManager; Intent intent = getIntent(); - mAppsRowManager = new AppsRowManager(mInjector.actions, mState.supportsCrossProfile(), - mUserIdManager); + mAppsRowManager = getAppsRowManager(); mInjector.appsRowManager = mAppsRowManager; mSharedInputHandler = @@ -163,6 +170,14 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { Metrics.logPickerLaunchedFrom(Shared.getCallingPackageName(this)); } + private AppsRowManager getAppsRowManager() { + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? new AppsRowManager(mInjector.actions, mState.supportsCrossProfile(), + mUserManagerState) + : new AppsRowManager(mInjector.actions, mState.supportsCrossProfile(), + mUserIdManager); + } + @Override public void onBackPressed() { super.onBackPressed(); @@ -203,7 +218,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { final String title = intent.getStringExtra(Intent.EXTRA_TITLE); SaveFragment.show(getSupportFragmentManager(), mimeType, title); } else if (mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_PICK_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { PickFragment.show(getSupportFragmentManager()); } else { // If PickFragment or SaveFragment does not show, @@ -215,10 +230,14 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { final Intent moreApps = new Intent(intent); moreApps.setComponent(null); moreApps.setPackage(null); - if (mState.supportsCrossProfile() - && CrossProfileUtils.getCrossProfileResolveInfo( - getPackageManager(), moreApps) != null) { - mState.canShareAcrossProfile = true; + if (mState.supportsCrossProfile) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mState.canForwardToProfileIdMap = mUserManagerState.getCanForwardToProfileIdMap( + moreApps); + } else if (CrossProfileUtils.getCrossProfileResolveInfo(UserId.CURRENT_USER, + getPackageManager(), moreApps, getApplicationContext()) != null) { + mState.canShareAcrossProfile = true; + } } if (mState.action == ACTION_GET_CONTENT @@ -345,8 +364,8 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { MimeTypes.VISUAL_MIMES, mState.acceptMimes); mState.derivedMode = visualMimes ? State.MODE_GRID : State.MODE_LIST; } else { - // Normal boring directory - DirectoryFragment.showDirectory(fm, root, cwd, anim); + // Normal boring directory + DirectoryFragment.showDirectory(fm, root, cwd, anim); } // Forget any replacement target @@ -358,7 +377,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { } if (mState.action == ACTION_OPEN_TREE || - mState.action == ACTION_PICK_COPY_DESTINATION) { + mState.action == ACTION_PICK_COPY_DESTINATION) { final PickFragment pick = PickFragment.get(fm); if (pick != null) { pick.setPickTarget(mState.action, @@ -369,7 +388,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { @Override protected void onDirectoryCreated(DocumentInfo doc) { - assert(doc.isDirectory()); + assert (doc.isDirectory()); mInjector.actions.openContainerDocument(doc); } @@ -425,13 +444,20 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { private boolean canShare(List<DocumentInfo> docs) { for (DocumentInfo doc : docs) { - if (!mState.canInteractWith(doc.userId)) { + if (!canInteractWith(doc.userId)) { return false; } } return true; } + private boolean canInteractWith(UserId userId) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return mState.canForwardToProfileIdMap.getOrDefault(userId, false); + } + return mState.canInteractWith(userId); + } + @CallSuper @Override public boolean onKeyDown(int keyCode, KeyEvent event) { diff --git a/src/com/android/documentsui/roots/ProvidersCache.java b/src/com/android/documentsui/roots/ProvidersCache.java index e053b45a7..15f346bf4 100644 --- a/src/com/android/documentsui/roots/ProvidersCache.java +++ b/src/com/android/documentsui/roots/ProvidersCache.java @@ -52,6 +52,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.android.documentsui.DocumentsApplication; import com.android.documentsui.R; import com.android.documentsui.UserIdManager; +import com.android.documentsui.UserManagerState; import com.android.documentsui.UserPackage; import com.android.documentsui.archives.ArchivesProvider; import com.android.documentsui.base.LookupApplicationName; @@ -59,6 +60,7 @@ import com.android.documentsui.base.Providers; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; +import com.android.documentsui.util.FeatureFlagUtils; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; @@ -90,10 +92,10 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { // empty results we don't cache them...unless they're in this magical list // of beloved providers. private static final List<String> PERMIT_EMPTY_CACHE = List.of( - // MTP provider commonly returns no roots (if no devices are attached). - Providers.AUTHORITY_MTP, - // ArchivesProvider doesn't support any roots. - ArchivesProvider.AUTHORITY); + // MTP provider commonly returns no roots (if no devices are attached). + Providers.AUTHORITY_MTP, + // ArchivesProvider doesn't support any roots. + ArchivesProvider.AUTHORITY); private static final int FIRST_LOAD_TIMEOUT_MS = 5000; private final Context mContext; @@ -122,10 +124,18 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { private final Map<UserAuthority, PackageDetails> mObservedAuthoritiesDetails = new HashMap<>(); private final UserIdManager mUserIdManager; + private final UserManagerState mUserManagerState; public ProvidersCache(Context context, UserIdManager userIdManager) { mContext = context; mUserIdManager = userIdManager; + mUserManagerState = null; + } + + public ProvidersCache(Context context, UserManagerState userManagerState) { + mContext = context; + mUserIdManager = null; + mUserManagerState = userManagerState; } private RootInfo generateRecentsRoot(UserId rootUserId) { @@ -199,7 +209,8 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { // For that reason we update our RecentsRoot to reflect // the current language. final String title = mContext.getString(R.string.root_recent); - for (UserId userId : mUserIdManager.getUserIds()) { + List<UserId> userIds = getUserIds(); + for (UserId userId : userIds) { RootInfo recentRoot = createOrGetRecentsRoot(userId); recentRoot.title = title; // Nothing else about the root should ever change. @@ -509,11 +520,12 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { /** * Create task to update roots cache. * - * @param forceRefreshAll when true, all previously cached values for - * all packages should be ignored. + * @param forceRefreshAll when true, all previously cached values for + * all packages should be ignored. * @param forceRefreshUserPackage when non-null, all previously cached - * values for this specific user package should be ignored. - * @param callback when non-null, it will be invoked after the task is executed. + * values for this specific user package should be ignored. + * @param callback when non-null, it will be invoked after the task is + * executed. */ MultiProviderUpdateTask( boolean forceRefreshAll, @@ -536,7 +548,8 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { final long start = SystemClock.elapsedRealtime(); - for (UserId userId : mUserIdManager.getUserIds()) { + List<UserId> userIds = getUserIds(); + for (UserId userId : userIds) { final RootInfo recents = createOrGetRecentsRoot(userId); synchronized (mLock) { mLocalRoots.put(new UserAuthority(recents.userId, recents.authority), recents); @@ -544,7 +557,7 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { } List<SingleProviderUpdateTaskInfo> taskInfos = new ArrayList<>(); - for (UserId userId : mUserIdManager.getUserIds()) { + for (UserId userId : userIds) { final PackageManager pm = userId.getPackageManager(mContext); // Pick up provider with action string final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); @@ -561,7 +574,7 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { CountDownLatch updateTaskInternalCountDown = new CountDownLatch(taskInfos.size()); ExecutorService executor = MoreExecutors.getExitingExecutorService( (ThreadPoolExecutor) Executors.newCachedThreadPool()); - for (SingleProviderUpdateTaskInfo taskInfo: taskInfos) { + for (SingleProviderUpdateTaskInfo taskInfo : taskInfos) { executor.submit(() -> startSingleProviderUpdateTask( taskInfo.providerInfo, @@ -701,4 +714,11 @@ public class ProvidersCache implements ProvidersAccess, LookupApplicationName { packageName = pckgName; } } + + private List<UserId> getUserIds() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return mUserManagerState.getUserIds(); + } + return mUserIdManager.getUserIds(); + } } diff --git a/src/com/android/documentsui/sidebar/RootsFragment.java b/src/com/android/documentsui/sidebar/RootsFragment.java index 49710e18c..fb8d2fcb8 100644 --- a/src/com/android/documentsui/sidebar/RootsFragment.java +++ b/src/com/android/documentsui/sidebar/RootsFragment.java @@ -63,6 +63,7 @@ import com.android.documentsui.Injector; import com.android.documentsui.Injector.Injected; import com.android.documentsui.ItemDragListener; import com.android.documentsui.R; +import com.android.documentsui.UserManagerState; import com.android.documentsui.UserPackage; import com.android.documentsui.base.BooleanConsumer; import com.android.documentsui.base.DocumentInfo; @@ -77,6 +78,7 @@ import com.android.documentsui.roots.ProvidersAccess; import com.android.documentsui.roots.ProvidersCache; import com.android.documentsui.roots.RootsLoader; import com.android.documentsui.util.CrossProfileUtils; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.Collection; @@ -130,11 +132,12 @@ public class RootsFragment extends Fragment { /** * Shows the {@link RootsFragment}. - * @param fm the FragmentManager for interacting with fragments associated with this - * fragment's activity + * + * @param fm the FragmentManager for interacting with fragments associated with this + * fragment's activity * @param includeApps if {@code true}, query the intent from the system and include apps in * the {@RootsFragment}. - * @param intent the intent to query for package manager + * @param intent the intent to query for package manager */ public static RootsFragment show(FragmentManager fm, boolean includeApps, Intent intent) { final Bundle args = new Bundle(); @@ -184,8 +187,8 @@ public class RootsFragment extends Fragment { }); } return false; - } - }); + } + }); mList.setChoiceMode(ListView.CHOICE_MODE_SINGLE); mList.setSelector(new ColorDrawable(Color.TRANSPARENT)); return view; @@ -266,10 +269,19 @@ public class RootsFragment extends Fragment { // necessary. ResolveInfo crossProfileResolveInfo = null; if (state.supportsCrossProfile() && handlerAppIntent != null) { - crossProfileResolveInfo = CrossProfileUtils.getCrossProfileResolveInfo( - getContext().getPackageManager(), handlerAppIntent); - updateCrossProfileStateAndMaybeRefresh( - /* canShareAcrossProfile= */ crossProfileResolveInfo != null); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + UserManagerState userManagerState = + DocumentsApplication.getUserManagerState(getContext()); + Map<UserId, Boolean> canForwardToProfileIdMap = + userManagerState.getCanForwardToProfileIdMap(handlerAppIntent); + updateCrossProfileMapStateAndMaybeRefresh(canForwardToProfileIdMap); + } else { + crossProfileResolveInfo = CrossProfileUtils.getCrossProfileResolveInfo( + UserId.CURRENT_USER, getContext().getPackageManager(), + handlerAppIntent, getContext()); + updateCrossProfileStateAndMaybeRefresh( + /* canShareAcrossProfile= */ crossProfileResolveInfo != null); + } } List<Item> sortedItems = sortLoadResult( @@ -280,7 +292,7 @@ public class RootsFragment extends Fragment { shouldIncludeHandlerApp ? handlerAppIntent : null, DocumentsApplication.getProvidersCache(getContext()), getBaseActivity().getSelectedUser(), - DocumentsApplication.getUserIdManager(getContext()).getUserIds(), + getUserIds(), maybeShowBadge); // This will be removed when feature flag is removed. @@ -316,6 +328,13 @@ public class RootsFragment extends Fragment { onCurrentRootChanged(); } + private List<UserId> getUserIds() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return DocumentsApplication.getUserManagerState(getContext()).getUserIds(); + } + return DocumentsApplication.getUserIdManager(getContext()).getUserIds(); + } + @Override public void onLoaderReset(Loader<Collection<RootInfo>> loader) { mAdapter = null; @@ -338,12 +357,24 @@ public class RootsFragment extends Fragment { } } + private void updateCrossProfileMapStateAndMaybeRefresh( + Map<UserId, Boolean> canForwardToProfileIdMap) { + final State state = getBaseActivity().getDisplayState(); + if (!state.canForwardToProfileIdMap.equals(canForwardToProfileIdMap)) { + state.canForwardToProfileIdMap = canForwardToProfileIdMap; + if (!UserId.CURRENT_USER.equals(getBaseActivity().getSelectedUser())) { + mActionHandler.loadDocumentsForCurrentStack(); + } + } + } + /** * If the package name of other providers or apps capable of handling the original intent * include the preferred root source, it will have higher order than others. - * @param excludePackage Exclude activities from this given package + * + * @param excludePackage Exclude activities from this given package * @param handlerAppIntent When not null, apps capable of handling the original intent will - * be included in list of roots (in special section at bottom). + * be included in list of roots (in special section at bottom). */ @VisibleForTesting List<Item> sortLoadResult( @@ -649,8 +680,8 @@ public class RootsFragment extends Fragment { } static void ejectClicked(View ejectIcon, RootInfo root, ActionHandler actionHandler) { - assert(ejectIcon != null); - assert(!root.ejecting); + assert (ejectIcon != null); + assert (!root.ejecting); ejectIcon.setEnabled(false); root.ejecting = true; actionHandler.ejectRoot( diff --git a/src/com/android/documentsui/util/CrossProfileUtils.java b/src/com/android/documentsui/util/CrossProfileUtils.java index e60ffdddc..f52c7542e 100644 --- a/src/com/android/documentsui/util/CrossProfileUtils.java +++ b/src/com/android/documentsui/util/CrossProfileUtils.java @@ -16,16 +16,27 @@ package com.android.documentsui.util; +import android.annotation.TargetApi; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.UserProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; import androidx.annotation.Nullable; +import com.android.documentsui.base.UserId; +import com.android.modules.utils.build.SdkLevel; + /** * A utility class for cross-profile usage. */ public class CrossProfileUtils { + + private static final String TAG = "CrossProfileUtils"; private static final String PROFILE_TARGET_ACTIVITY = "com.android.internal.app.IntentForwarderActivity"; @@ -46,11 +57,54 @@ public class CrossProfileUtils { } /** - * Returns the {@ResolveInfo} if this intent is a cross-profile intent or {@code null} + * Returns the {@link ResolveInfo} if this intent is a cross-profile intent or {@code null} * otherwise. */ @Nullable - public static ResolveInfo getCrossProfileResolveInfo(PackageManager packageManager, + public static ResolveInfo getCrossProfileResolveInfo(UserId currentUser, + PackageManager packageManager, Intent intent, Context context) { + if (FeatureFlagUtils.isPrivateSpaceEnabled() && SdkLevel.isAtLeastV()) { + return getCrossProfileResolveInfoPostV(currentUser, packageManager, + intent, context); + } + return getCrossProfileResolveInfoPreV(packageManager, intent); + } + + @Nullable + @TargetApi(35) + private static ResolveInfo getCrossProfileResolveInfoPostV( + UserId currentUser, PackageManager packageManager, + Intent intent, Context context) { + UserManager userManager = context.getSystemService(UserManager.class); + UserHandle queringUser = UserHandle.of(currentUser.getIdentifier()); + if (userManager == null) { + Log.e(TAG, "cannot obtain user manager"); + return null; + } + + UserProperties userProperties = userManager.getUserProperties(queringUser); + if (userProperties == null) { + Log.e(TAG, "cannot obtain user properties"); + return null; + } + + if (userProperties.getCrossProfileContentSharingStrategy() + == UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) { + queringUser = userManager.getProfileParent(queringUser); + } + + for (ResolveInfo info : packageManager.queryIntentActivitiesAsUser(intent, + PackageManager.MATCH_DEFAULT_ONLY, queringUser)) { + if (isCrossProfileIntentForwarderActivity(info)) { + return info; + } + } + return null; + } + + @Nullable + private static ResolveInfo getCrossProfileResolveInfoPreV( + PackageManager packageManager, Intent intent) { for (ResolveInfo info : packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)) { diff --git a/tests/common/com/android/documentsui/TestUserManagerState.java b/tests/common/com/android/documentsui/TestUserManagerState.java index 0306e5325..0591770fc 100644 --- a/tests/common/com/android/documentsui/TestUserManagerState.java +++ b/tests/common/com/android/documentsui/TestUserManagerState.java @@ -16,6 +16,7 @@ package com.android.documentsui; +import android.content.Intent; import android.graphics.drawable.Drawable; import com.android.documentsui.base.UserId; @@ -28,7 +29,8 @@ import java.util.Map; public class TestUserManagerState implements UserManagerState { public List<UserId> userIds = new ArrayList<>(); - public Map<UserId, String> userIdLabelMap = new HashMap<>(); + public Map<UserId, String> userIdToLabelMap = new HashMap<>(); + public Map<UserId, Boolean> canFrowardToProfileIdMap = new HashMap<>(); public Map<UserId, Drawable> userIdToBadgeMap = new HashMap<>(); @@ -39,11 +41,16 @@ public class TestUserManagerState implements UserManagerState { @Override public Map<UserId, String> getUserIdToLabelMap() { - return userIdLabelMap; + return userIdToLabelMap; } @Override public Map<UserId, Drawable> getUserIdToBadgeMap() { return userIdToBadgeMap; } + + @Override + public Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent) { + return canFrowardToProfileIdMap; + } } diff --git a/tests/unit/com/android/documentsui/RecentsLoaderTests.java b/tests/unit/com/android/documentsui/RecentsLoaderTests.java index 915a3bd24..8007f365a 100644 --- a/tests/unit/com/android/documentsui/RecentsLoaderTests.java +++ b/tests/unit/com/android/documentsui/RecentsLoaderTests.java @@ -29,7 +29,6 @@ import android.database.Cursor; import android.provider.DocumentsContract.Document; import androidx.test.filters.MediumTest; -import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.State; @@ -41,15 +40,21 @@ import com.android.documentsui.testing.TestFileTypeLookup; import com.android.documentsui.testing.TestImmediateExecutor; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.UserManagers; +import com.android.documentsui.util.FeatureFlagUtils; + +import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -@RunWith(AndroidJUnit4.class) +@RunWith(Parameterized.class) @MediumTest public class RecentsLoaderTests { @@ -57,6 +62,18 @@ public class RecentsLoaderTests { private TestActivity mActivity; private RecentsLoader mLoader; + @Parameter(0) + public boolean isPrivateSpaceEnabled; + + /** + * Parameterized test to run all the tests in this class twice, once with private space enabled + * and once with private space disabled. + */ + @Parameters(name = "privateSpaceEnabled={0}") + public static Iterable<?> data() { + return Lists.newArrayList(true, false); + } + @Before public void setUp() { mEnv = TestEnv.create(); @@ -65,8 +82,13 @@ public class RecentsLoaderTests { mActivity.userManager = UserManagers.create(); mEnv.state.action = State.ACTION_BROWSE; - mEnv.state.acceptMimes = new String[] { "*/*" }; - mEnv.state.canShareAcrossProfile = true; + mEnv.state.acceptMimes = new String[]{"*/*"}; + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.USER_ID, true); + mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.OtherUser.USER_ID, true); + } else { + mEnv.state.canShareAcrossProfile = true; + } mLoader = new RecentsLoader(mActivity, mEnv.providers, mEnv.state, TestImmediateExecutor.createLookup(), new TestFileTypeLookup(), @@ -113,7 +135,7 @@ public class RecentsLoaderTests { mEnv.mockProviders.get(TestProvidersAccess.HOME.authority) .setNextRecentDocumentsReturns(doc1, doc2); - assertEquals(false, mLoader.mState.showHiddenFiles); + assertFalse(mLoader.mState.showHiddenFiles); DirectoryResult result = mLoader.loadInBackground(); assertEquals(0, result.getCursor().getCount()); @@ -172,7 +194,11 @@ public class RecentsLoaderTests { @Test public void testLoaderOnUserWithoutPermission() { - mEnv.state.canShareAcrossProfile = false; + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.OtherUser.USER_ID, false); + } else { + mEnv.state.canShareAcrossProfile = false; + } mLoader = new RecentsLoader(mActivity, mEnv.providers, mEnv.state, TestImmediateExecutor.createLookup(), new TestFileTypeLookup(), TestProvidersAccess.OtherUser.USER_ID); diff --git a/tests/unit/com/android/documentsui/UserManagerStateTest.java b/tests/unit/com/android/documentsui/UserManagerStateTest.java index e9bb011ed..670cb1da2 100644 --- a/tests/unit/com/android/documentsui/UserManagerStateTest.java +++ b/tests/unit/com/android/documentsui/UserManagerStateTest.java @@ -23,7 +23,9 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.content.Context; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.pm.UserProperties; import android.os.UserHandle; import android.os.UserManager; @@ -32,6 +34,7 @@ import androidx.test.filters.SmallTest; import com.android.documentsui.base.UserId; import com.android.documentsui.testing.UserManagers; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; import com.android.modules.utils.build.SdkLevel; @@ -40,7 +43,9 @@ import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; +import java.util.HashMap; import java.util.List; +import java.util.Map; @SmallTest public class UserManagerStateTest { @@ -50,13 +55,19 @@ public class UserManagerStateTest { private static final int SHOW_IN_SHARING_SURFACES_NO = 2; private static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; private static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; + private final UserHandle mSystemUser = UserHandle.SYSTEM; private final UserHandle mManagedUser = UserHandle.of(100); private final UserHandle mPrivateUser = UserHandle.of(101); private final UserHandle mOtherUser = UserHandle.of(102); private final UserHandle mNormalUser = UserHandle.of(103); + private final ResolveInfo mMockInfo1 = mock(ResolveInfo.class); + private final ResolveInfo mMockInfo2 = mock(ResolveInfo.class); + private final ResolveInfo mMockInfo3 = mock(ResolveInfo.class); + private final Context mMockContext = mock(Context.class); + private final Intent mMockIntent = mock(Intent.class); private final UserManager mMockUserManager = UserManagers.create(); private final PackageManager mMockPackageManager = mock(PackageManager.class); private UserManagerState mUserManagerState; @@ -111,6 +122,12 @@ public class UserManagerStateTest { when(mMockUserManager.getProfileParent(mOtherUser)).thenReturn(mSystemUser); when(mMockUserManager.getProfileParent(mNormalUser)).thenReturn(null); + if (SdkLevel.isAtLeastR()) { + when(mMockInfo1.isCrossProfileIntentForwarderActivity()).thenReturn(true); + when(mMockInfo2.isCrossProfileIntentForwarderActivity()).thenReturn(false); + when(mMockInfo3.isCrossProfileIntentForwarderActivity()).thenReturn(false); + } + when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager); when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn("mMockUserManager"); when(mMockContext.getSystemService(UserManager.class)).thenReturn(mMockUserManager); @@ -289,9 +306,7 @@ public class UserManagerStateTest { @Test public void testGetUserIds_otherAndManagedUserCurrentUserOtherPostV_returnsManagedUser() { - // When there is no system user, returns the current user. - // This is a case theoretically can happen but we don't expect. So we return the current - // user only. + // Only the users with show in sharing surfaces separate are eligible to be returned if (!SdkLevel.isAtLeastV()) return; UserId currentUser = UserId.of(mOtherUser); initializeUserManagerState(currentUser, Lists.newArrayList(mOtherUser, mManagedUser)); @@ -337,6 +352,216 @@ public class UserManagerStateTest { .isSameInstanceAs(mUserManagerState.getUserIds()); } + @Test + public void testGetCanForwardToProfileIdMap_systemUserCanForwardToAll() { + UserId currentUser = UserId.of(mSystemUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + } else { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser)); + when(mMockPackageManager.queryIntentActivities(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY)).thenReturn(mMockResolveInfoList); + } + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), true); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + } + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_systemUserCanForwardToManaged() { + UserId currentUser = UserId.of(mSystemUser); + initializeUserManagerState(currentUser, Lists.newArrayList(mSystemUser, mManagedUser)); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + } else { + when(mMockPackageManager.queryIntentActivities(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY)).thenReturn(mMockResolveInfoList); + } + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), true); + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_systemUserCanAlwaysForwardToPrivate() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mSystemUser); + initializeUserManagerState(currentUser, Lists.newArrayList(mSystemUser, mPrivateUser)); + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_systemUserCanNotForwardToManagedUser() { + UserId currentUser = UserId.of(mSystemUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo2, mMockInfo3); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + } else { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser)); + when(mMockPackageManager.queryIntentActivities(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY)).thenReturn(mMockResolveInfoList); + } + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), false); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + } + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_managedCanForwardToAll() { + UserId currentUser = UserId.of(mManagedUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mManagedUser)).thenReturn( + mMockResolveInfoList); + } else { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser)); + when(mMockPackageManager.queryIntentActivities(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY)).thenReturn(mMockResolveInfoList); + } + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), true); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + } + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_managedCanNotForwardToAll() { + UserId currentUser = UserId.of(mManagedUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo2, mMockInfo3); + + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + } else { + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser)); + when(mMockPackageManager.queryIntentActivities(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY)).thenReturn(mMockResolveInfoList); + } + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), false); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), true); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), false); + } + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_privateCanForwardToAll() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mPrivateUser); + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn(mMockResolveInfoList); + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_privateCanNotForwardToManagedUser() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mPrivateUser); + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo2, mMockInfo3); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn(mMockResolveInfoList); + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mManagedUser), false); + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + + @Test + public void testGetCanForwardToProfileIdMap_privateCanAlwaysForwardToSystemUser() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mPrivateUser); + initializeUserManagerState(currentUser, Lists.newArrayList(mSystemUser, mPrivateUser)); + + Map<UserId, Boolean> expectedCanForwardToProfileIdMap = new HashMap<>(); + expectedCanForwardToProfileIdMap.put(UserId.of(mSystemUser), true); + expectedCanForwardToProfileIdMap.put(UserId.of(mPrivateUser), true); + + assertWithMessage("getCanForwardToProfileIdMap returns incorrect mappings") + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)) + .isEqualTo(expectedCanForwardToProfileIdMap); + } + private void initializeUserManagerState(UserId current, List<UserHandle> usersOnDevice) { when(mMockUserManager.getUserProfiles()).thenReturn(usersOnDevice); mUserManagerState = new UserManagerState.RuntimeUserManagerState(mMockContext, current, diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java index 61094ba94..4627358f8 100644 --- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java +++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java @@ -41,8 +41,6 @@ import androidx.test.runner.AndroidJUnit4; import com.android.documentsui.DocumentsAccess; import com.android.documentsui.Injector; import com.android.documentsui.R; -import com.android.documentsui.TestUserIdManager; -import com.android.documentsui.UserIdManager; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.Lookup; @@ -58,6 +56,7 @@ import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestLastAccessedStorage; import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.TestResolveInfo; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; import org.junit.AfterClass; @@ -80,7 +79,6 @@ public class ActionHandlerTest { private TestableActionHandler<TestActivity> mHandler; private TestLastAccessedStorage mLastAccessed; private PickCountRecordStorage mPickCountRecord; - private TestUserIdManager mTestUserIdManager; @Before public void setUp() { @@ -89,7 +87,6 @@ public class ActionHandlerTest { mEnv.providers.configurePm(mActivity.packageMgr); mEnv.injector.pickResult = new PickResult(); mLastAccessed = new TestLastAccessedStorage(); - mTestUserIdManager = new TestUserIdManager(); mPickCountRecord = mock(PickCountRecordStorage.class); mHandler = new TestableActionHandler<>( @@ -101,17 +98,20 @@ public class ActionHandlerTest { mEnv::lookupExecutor, mEnv.injector, mLastAccessed, - mPickCountRecord, - mTestUserIdManager + mPickCountRecord ); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.USER_ID, true); + } + mEnv.selectionMgr.select("1"); AsyncTask.setDefaultExecutor(mEnv.mExecutor); } private static class TestableActionHandler<T extends FragmentActivity & Addons> - extends ActionHandler { + extends ActionHandler { private UpdatePickResultTask mTask; @@ -124,10 +124,8 @@ public class ActionHandlerTest { Lookup<String, Executor> executors, Injector injector, LastAccessedStorage lastAccessed, - PickCountRecordStorage pickCountRecordStorage, - UserIdManager userIdManager) { - super(activity, state, providers, docs, searchMgr, executors, injector, lastAccessed, - userIdManager); + PickCountRecordStorage pickCountRecordStorage) { + super(activity, state, providers, docs, searchMgr, executors, injector, lastAccessed); mTask = new UpdatePickResultTask( mActivity, mInjector.pickResult, pickCountRecordStorage); } @@ -284,7 +282,7 @@ public class ActionHandlerTest { mEnv.beforeAsserts(); verify(mPickCountRecord).increasePickCountRecord( - mActivity.getApplicationContext(), TestEnv.FILE_JPG.derivedUri); + mActivity.getApplicationContext(), TestEnv.FILE_JPG.derivedUri); mActivity.finishedHandler.assertCalled(); } @@ -355,7 +353,8 @@ public class ActionHandlerTest { final String mimeType = "audio/aac"; final String displayName = "foobar.m4a"; - mHandler.saveDocument(mimeType, displayName, (boolean inProgress) -> {}); + mHandler.saveDocument(mimeType, displayName, (boolean inProgress) -> { + }); mEnv.beforeAsserts(); @@ -431,7 +430,7 @@ public class ActionHandlerTest { mEnv.state.action = State.ACTION_GET_CONTENT; mEnv.state.stack.changeRoot(TestProvidersAccess.HOME); mEnv.state.stack.push(TestEnv.FOLDER_1); - mEnv.state.acceptMimes = new String[] { "image/*" }; + mEnv.state.acceptMimes = new String[]{"image/*"}; mActivity.finishedHandler.assertNotCalled(); @@ -483,7 +482,7 @@ public class ActionHandlerTest { mEnv.state.action = State.ACTION_OPEN; mEnv.state.stack.changeRoot(TestProvidersAccess.HOME); mEnv.state.stack.push(TestEnv.FOLDER_1); - mEnv.state.acceptMimes = new String[] { "image/*" }; + mEnv.state.acceptMimes = new String[]{"image/*"}; mActivity.finishedHandler.assertNotCalled(); @@ -539,13 +538,17 @@ public class ActionHandlerTest { @Test public void testOpenAppRoot_otherUser() throws Exception { ResolveInfo info = TestResolveInfo.create(); - mEnv.state.canShareAcrossProfile = true; + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mEnv.state.canForwardToProfileIdMap.put(TestProvidersAccess.OtherUser.USER_ID, true); + } else { + mEnv.state.canShareAcrossProfile = true; + } mHandler.openRoot(info, TestProvidersAccess.OtherUser.USER_ID); assertThat(mActivity.startActivityAsUser.getLastValue().first.getComponent()).isEqualTo( new ComponentName(info.activityInfo.applicationInfo.packageName, - info.activityInfo.name)); + info.activityInfo.name)); assertThat(mActivity.startActivityAsUser.getLastValue().second) - .isEqualTo(TestProvidersAccess.OtherUser.USER_HANDLE); + .isEqualTo(TestProvidersAccess.OtherUser.USER_HANDLE); int flags = mActivity.startActivityAsUser.getLastValue().first.getFlags(); assertEquals(0, flags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); @@ -569,7 +572,7 @@ public class ActionHandlerTest { mHandler.openRoot(info, TestProvidersAccess.USER_ID); assertThat(mActivity.startActivity.getLastValue().getComponent()).isEqualTo( new ComponentName(info.activityInfo.applicationInfo.packageName, - info.activityInfo.name)); + info.activityInfo.name)); int flags = mActivity.startActivity.getLastValue().getFlags(); assertEquals(0, flags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); @@ -589,7 +592,7 @@ public class ActionHandlerTest { mHandler.openRoot(TestResolveInfo.create(), TestProvidersAccess.USER_ID); assertEquals(queryContent, mActivity.startActivity.getLastValue().getStringExtra( - Intent.EXTRA_CONTENT_QUERY)); + Intent.EXTRA_CONTENT_QUERY)); } @Test @@ -712,4 +715,4 @@ public class ActionHandlerTest { } } } -} +}
\ No newline at end of file |