summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/com/android/documentsui/BaseActivity.java37
-rw-r--r--src/com/android/documentsui/DirectoryLoader.java11
-rw-r--r--src/com/android/documentsui/DocumentsAccess.java1
-rw-r--r--src/com/android/documentsui/DocumentsApplication.java53
-rw-r--r--src/com/android/documentsui/NavigationViewManager.java34
-rw-r--r--src/com/android/documentsui/ProfileTabs.java53
-rw-r--r--src/com/android/documentsui/RecentsLoader.java1
-rw-r--r--src/com/android/documentsui/UserManagerState.java140
-rw-r--r--src/com/android/documentsui/base/State.java11
-rw-r--r--src/com/android/documentsui/dirlist/AppsRowManager.java23
-rw-r--r--src/com/android/documentsui/picker/ActionHandler.java28
-rw-r--r--src/com/android/documentsui/picker/PickActivity.java60
-rw-r--r--src/com/android/documentsui/roots/ProvidersCache.java44
-rw-r--r--src/com/android/documentsui/sidebar/RootsFragment.java59
-rw-r--r--src/com/android/documentsui/util/CrossProfileUtils.java58
-rw-r--r--tests/common/com/android/documentsui/TestUserManagerState.java11
-rw-r--r--tests/unit/com/android/documentsui/RecentsLoaderTests.java38
-rw-r--r--tests/unit/com/android/documentsui/UserManagerStateTest.java231
-rw-r--r--tests/unit/com/android/documentsui/picker/ActionHandlerTest.java45
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