summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Aditya <adityasngh@google.com> 2023-12-05 05:45:19 +0000
committer Aditya <adityasngh@google.com> 2024-01-25 10:56:13 +0000
commite1e2ebeea774f9885f4d39418894a8a4a5f19cf9 (patch)
tree222b17f990c4b554ac3c86cc44c0bdf119918756
parent7e5088515af58b811d782d7c1b450b27558026c4 (diff)
Implementing cross profile contnet sharing startegy for docsUI.
The new strategy is based on the user property that dictates whether cross profile contnet sharing is to be delegated from parent or not. Bug: 313471319 Test: DocumentsUIGoogleTests:com.android.documentsui.UserManagerStateTest Change-Id: Id5deb79e3837c9571b82c1e6ff72cd134c832dfd
-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