diff options
32 files changed, 1151 insertions, 343 deletions
diff --git a/res/layout-sw720dp/item_doc_list.xml b/res/layout-sw720dp/item_doc_list.xml index da9179606..4b2b3715a 100644 --- a/res/layout-sw720dp/item_doc_list.xml +++ b/res/layout-sw720dp/item_doc_list.xml @@ -95,7 +95,7 @@ android:orientation="horizontal"> <ImageView - android:id="@+id/icon_briefcase" + android:id="@+id/icon_profile_badge" android:layout_height="@dimen/briefcase_icon_size" android:layout_width="@dimen/briefcase_icon_size" android:layout_marginEnd="@dimen/briefcase_icon_margin" diff --git a/res/layout/item_dir_grid.xml b/res/layout/item_dir_grid.xml index 8720c37cf..423aba0c1 100644 --- a/res/layout/item_dir_grid.xml +++ b/res/layout/item_dir_grid.xml @@ -81,7 +81,7 @@ </FrameLayout> <ImageView - android:id="@+id/icon_briefcase" + android:id="@+id/icon_profile_badge" android:layout_height="@dimen/briefcase_icon_size" android:layout_width="@dimen/briefcase_icon_size" android:layout_marginEnd="@dimen/briefcase_icon_margin" diff --git a/res/layout/item_doc_grid.xml b/res/layout/item_doc_grid.xml index 5e2e93842..d0b0ee305 100644 --- a/res/layout/item_doc_grid.xml +++ b/res/layout/item_doc_grid.xml @@ -146,7 +146,7 @@ android:paddingEnd="12dp"> <ImageView - android:id="@+id/icon_briefcase" + android:id="@+id/icon_profile_badge" android:layout_height="@dimen/briefcase_icon_size" android:layout_width="@dimen/briefcase_icon_size" android:layout_marginEnd="@dimen/briefcase_icon_margin" @@ -161,7 +161,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" - android:layout_toEndOf="@+id/icon_briefcase" + android:layout_toEndOf="@+id/icon_profile_badge" android:singleLine="true" android:ellipsize="end" android:textAlignment="viewStart" diff --git a/res/layout/item_doc_list.xml b/res/layout/item_doc_list.xml index 0942c4b5f..026c6efe6 100644 --- a/res/layout/item_doc_list.xml +++ b/res/layout/item_doc_list.xml @@ -92,7 +92,7 @@ android:layout_weight="1"> <ImageView - android:id="@+id/icon_briefcase" + android:id="@+id/icon_profile_badge" android:layout_height="@dimen/briefcase_icon_size" android:layout_width="@dimen/briefcase_icon_size" android:layout_marginEnd="@dimen/briefcase_icon_margin" diff --git a/res/layout/item_photo_grid.xml b/res/layout/item_photo_grid.xml index 5cf685004..cb2c40c25 100644 --- a/res/layout/item_photo_grid.xml +++ b/res/layout/item_photo_grid.xml @@ -104,7 +104,7 @@ </FrameLayout> <FrameLayout - android:id="@+id/icon_briefcase" + android:id="@+id/icon_profile_badge" android:layout_width="@dimen/button_touch_size" android:layout_height="@dimen/button_touch_size" android:layout_alignParentBottom="true" diff --git a/res/values/strings.xml b/res/values/strings.xml index 8509bd247..89e40ac4c 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -189,6 +189,15 @@ <!-- Button text shown on a button when work profile is paused. Tapping the button will switch on the work profile [CHAR LIMIT=48] --> <string name="quiet_mode_button">Turn on work apps</string> + <!-- Title of an error message. This screen is shown when the selected profile is paused. [CHAR LIMIT=72] --> + <string name="profile_quiet_mode_error_title"> + <xliff:g id="profile" example="Work">%1$s</xliff:g> apps are paused + </string> + <!-- Button text shown on a button when selected profile is paused. Tapping the button will switch on the selected profile [CHAR LIMIT=48] --> + <string name="profile_quiet_mode_button"> + Turn on <xliff:g id="profile" example="work">%1$s</xliff:g> apps + </string> + <!-- Error message title shown when a user's IT admin does not allow the user to select work files from a personal app. [CHAR LIMIT=72] --> <string name="cant_select_work_files_error_title">Can\u2019t select work files</string> <!-- Error message that's shown when the user's IT admin doesn't allow the user to select work files from a personal app. [CHAR LIMIT=200] --> @@ -203,6 +212,16 @@ access personal files from a work app </string> + <!-- Error message title shown when a user's cross profile setting does not allow the user to select one profile's files from another profile app. [CHAR LIMIT=72] --> + <string name="cant_select_cross_profile_files_error_title">Can\u2019t select + <xliff:g id="profile" example="work">%1$s</xliff:g> files + </string> + <!-- Error message that's shown when the user's cross profile setting doesn't allow the user to select one profile's files from another profile app. [CHAR LIMIT=200] --> + <string name="cant_select_cross_profile_files_error_message">Your IT admin doesn\u2019t allow + you to access <xliff:g id="profile" example="work">%1$s</xliff:g> files from a + <xliff:g id="profile" example="personal">%2$s</xliff:g> app + </string> + <!-- Error message title shown when the admin does not allow the user to save files from their personal profile to their work profile. [CHAR LIMIT=72] --> <string name="cant_save_to_work_error_title">Can\u2019t save to work profile</string> <!-- Error message shown when the user's IT admin doesn't allow the user to save files from their personal profile to their work profile. [CHAR LIMIT=200] --> @@ -217,6 +236,16 @@ work files to your personal profile </string> + <!-- Error message title shown when the user's cross profile setting does not allow the user to save files from one profile to another profile. [CHAR LIMIT=72] --> + <string name="cant_save_to_cross_profile_error_title">Can\u2019t save to + <xliff:g id="profile" example="work">%1$s</xliff:g> profile + </string> + <!-- Error message shown when the user's cross profile setting doesn't allow the user to save files from one profile to another profile. [CHAR LIMIT=200] --> + <string name="cant_save_to_cross_profile_error_message">Your IT admin doesn\u2019t allow you to + save <xliff:g id="profile" example="personal">%1$s</xliff:g> files to your + <xliff:g id="profile" example="work">%2$s</xliff:g> profile + </string> + <!-- Error message title. This message is shown when a user tries to do something on their work device, but that action isn't allowed by their IT admin. [CHAR LIMIT=72] --> <string name="cross_profile_action_not_allowed_title">This action isn\u2019t allowed</string> <!-- Error message. This message is shown when a user tries to do something on their work device, but that action isn't allowed by their IT admin. [CHAR LIMIT=200] --> @@ -509,6 +538,8 @@ <string name="preview_file">Preview the file <xliff:g id="fileName" example="example.jpg">%1$s</xliff:g></string> <!-- Content description text that's spoken by a screen reader. This text is for previewing a work file before opening it. --> <string name="preview_work_file">Preview the work file <xliff:g id="fileName" example="example.jpg">%1$s</xliff:g></string> + <!-- Content description text that's spoken by a screen reader. This text is for previewing a private file before opening it. --> + <string name="preview_cross_profile_file">Preview the <xliff:g id="profile" example="work">%1$s</xliff:g> file <xliff:g id="fileName" example="example.jpg">%2$s</xliff:g></string> <!-- Apps row title. [CHAR_LIMIT=60] --> <string name="apps_row_title">Browse files in other apps</string> diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index d075651a7..42d2d9f9f 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -118,6 +118,12 @@ public abstract class BaseActivity private PreferencesMonitor mPreferencesMonitor; + private boolean mHasProfileBecomeUnavailable = false; + + public void setHasProfileBecomeUnavailable(boolean hasProfileBecomeUnavailable) { + mHasProfileBecomeUnavailable = hasProfileBecomeUnavailable; + } + public BaseActivity(@LayoutRes int layoutId, String tag) { mLayoutId = layoutId; mTag = tag; @@ -266,6 +272,7 @@ public abstract class BaseActivity cmdInterceptor); ViewGroup chipGroup = findViewById(R.id.search_chip_group); + mUserIdManager = DocumentsApplication.getUserIdManager(this); mUserManagerState = DocumentsApplication.getUserManagerState(this); mSearchManager = new SearchViewManager(searchListener, queryInterceptor, @@ -317,7 +324,12 @@ public abstract class BaseActivity // The activity will clear search on root picked. If we don't clear the search, // user may see the search result screen show up briefly and then get cleared. mSearchManager.cancelSearch(); - mInjector.actions.loadCrossProfileRoot(getCurrentRoot(), userId); + // When a profile with user property SHOW_IN_QUIET_MODE_HIDDEN is currently + // selected, and it becomes unavailable, we reset the roots to recents. + // We do not reset it to recents when pick activity is due to ACTION_CREATE_DOCUMENT + mInjector.actions.loadCrossProfileRoot( + (mHasProfileBecomeUnavailable && mState.action != State.ACTION_CREATE) + ? getRecentsRoot() : getCurrentRoot(), userId); } }); @@ -868,6 +880,10 @@ public abstract class BaseActivity } } + public RootInfo getRecentsRoot() { + return mProviders.generateRecentsRoot(getSelectedUser()); + } + @Override public DocumentInfo getCurrentDirectory() { return mState.stack.peek(); diff --git a/src/com/android/documentsui/UserManagerState.java b/src/com/android/documentsui/UserManagerState.java index 50858f389..8e880600a 100644 --- a/src/com/android/documentsui/UserManagerState.java +++ b/src/com/android/documentsui/UserManagerState.java @@ -86,6 +86,16 @@ public interface UserManagerState { Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent); /** + * Updates the state of the list of userIds and all the associated maps according the intent + * received in broadcast + * + * @param userId {@link UserId} for the profile for which the availability status changed + * @param action {@link Intent}.ACTION_PROFILE_UNAVAILABLE or + * {@link Intent}.ACTION_PROFILE_AVAILABLE + */ + void onProfileActionStatusChange(String action, UserId userId); + + /** * Creates an implementation of {@link UserManagerState}. */ // TODO: b/314746383 Make this class a singleton @@ -172,6 +182,7 @@ public interface UserManagerState { public List<UserId> getUserIds() { synchronized (mUserIds) { if (mUserIds.isEmpty()) { + Log.d("profileAction", "user ids empty"); mUserIds.addAll(getUserIdsInternal()); } return mUserIds; @@ -208,6 +219,47 @@ public interface UserManagerState { } } + @Override + @SuppressLint("NewApi") + public void onProfileActionStatusChange(String action, UserId userId) { + UserProperties userProperties = mUserManager.getUserProperties( + UserHandle.of(userId.getIdentifier())); + if (userProperties.getShowInQuietMode() != UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) { + return; + } + if (Intent.ACTION_PROFILE_UNAVAILABLE.equals(action)) { + synchronized (mUserIds) { + mUserIds.remove(userId); + } + synchronized (mUserIdToLabelMap) { + mUserIdToLabelMap.remove(userId); + } + synchronized (mUserIdToBadgeMap) { + mUserIdToBadgeMap.remove(userId); + } + synchronized (mCanFrowardToProfileIdMap) { + mCanFrowardToProfileIdMap.remove(userId); + } + } else if (Intent.ACTION_PROFILE_AVAILABLE.equals(action)) { + synchronized (mUserIds) { + if (!mUserIds.contains(userId)) { + mUserIds.add(userId); + } + } + synchronized (mUserIdToLabelMap) { + mUserIdToLabelMap.put(userId, getProfileLabel(userId)); + } + synchronized (mUserIdToBadgeMap) { + mUserIdToBadgeMap.put(userId, getProfileBadge(userId)); + } + synchronized (mCanFrowardToProfileIdMap) { + mCanFrowardToProfileIdMap.put(userId, true); + } + } else { + Log.e(TAG, "Unexpected action received: " + action); + } + } + private List<UserId> getUserIdsInternal() { final List<UserId> result = new ArrayList<>(); @@ -488,11 +540,11 @@ public interface UserManagerState { * 2. current user does not delegate check to the parent and the target user is the * parent profile */ - UserId needToCheck; + UserId needToCheck = null; if (parentOrDelegatedFromParent.contains(mCurrentUser) && !noDelegation.isEmpty()) { needToCheck = noDelegation.get(0); - } else { + } else if (mCurrentUser.getIdentifier() != ActivityManager.getCurrentUser()) { final UserHandle parentProfile = mUserManager.getProfileParent( UserHandle.of(mCurrentUser.getIdentifier())); needToCheck = UserId.of(parentProfile); diff --git a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java index 7bccbac4e..335126e59 100644 --- a/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java +++ b/src/com/android/documentsui/dirlist/DirectoryAddonsAdapter.java @@ -16,8 +16,10 @@ package com.android.documentsui.dirlist; +import android.os.UserManager; import android.view.ViewGroup; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver; @@ -25,10 +27,13 @@ import com.android.documentsui.Model; import com.android.documentsui.Model.Update; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.State; +import com.android.documentsui.base.UserId; import com.android.documentsui.dirlist.Message.HeaderMessage; import com.android.documentsui.dirlist.Message.InflateMessage; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.List; +import java.util.Map; /** * Adapter wrapper that embellishes the directory list by inserting Holder views inbetween @@ -49,12 +54,23 @@ final class DirectoryAddonsAdapter extends DocumentsAdapter { private final Message mInflateMessage; DirectoryAddonsAdapter(Environment environment, DocumentsAdapter delegate) { + this(environment, delegate, null, null, null, null); + } + + DirectoryAddonsAdapter(Environment environment, DocumentsAdapter delegate, + @Nullable UserId sourceUserId, @Nullable UserId selectedUserId, + @Nullable Map<UserId, String> userIdLabelMap, UserManager userManager) { mEnv = environment; mDelegate = delegate; // TODO: We should not instantiate the messages here, but rather instantiate them // when we get an update event. mHeaderMessage = new HeaderMessage(environment, this::onDismissHeaderMessage); - mInflateMessage = new InflateMessage(environment, this::onDismissHeaderMessage); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mInflateMessage = new InflateMessage(environment, this::onDismissHeaderMessage, + sourceUserId, selectedUserId, userIdLabelMap, userManager); + } else { + mInflateMessage = new InflateMessage(environment, this::onDismissHeaderMessage); + } // Relay events published by our delegate to our listeners (presumably RecyclerView) // with adjusted positions. @@ -295,13 +311,13 @@ final class DirectoryAddonsAdapter extends DocumentsAdapter { @Override public void onItemRangeChanged(int positionStart, int itemCount, Object payload) { - assert(itemCount == 1); + assert (itemCount == 1); notifyItemRangeChanged(toViewPosition(positionStart), itemCount, payload); } @Override public void onItemRangeInserted(int positionStart, int itemCount) { - assert(itemCount == 1); + assert (itemCount == 1); if (positionStart < mBreakPosition) { mBreakPosition++; } @@ -310,7 +326,7 @@ final class DirectoryAddonsAdapter extends DocumentsAdapter { @Override public void onItemRangeRemoved(int positionStart, int itemCount) { - assert(itemCount == 1); + assert (itemCount == 1); if (positionStart < mBreakPosition) { mBreakPosition--; } diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index a5306bd97..77047ad7a 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -28,6 +28,7 @@ import android.content.ContentProviderClient; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.UserProperties; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -35,6 +36,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Parcelable; import android.os.UserHandle; +import android.os.UserManager; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.util.Log; @@ -89,6 +91,7 @@ import com.android.documentsui.ProfileTabsController; import com.android.documentsui.R; import com.android.documentsui.ThumbnailCache; import com.android.documentsui.TimeoutTask; +import com.android.documentsui.UserManagerState; import com.android.documentsui.base.DocumentFilters; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; @@ -110,8 +113,10 @@ import com.android.documentsui.services.FileOperationService.OpType; import com.android.documentsui.services.FileOperations; import com.android.documentsui.sorting.SortDimension; import com.android.documentsui.sorting.SortModel; - +import com.android.documentsui.util.FeatureFlagUtils; import com.android.documentsui.util.VersionUtils; +import com.android.modules.utils.build.SdkLevel; + import com.google.common.base.Objects; import java.io.IOException; @@ -132,7 +137,8 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On REQUEST_COPY_DESTINATION }) @Retention(RetentionPolicy.SOURCE) - public @interface RequestCode {} + public @interface RequestCode { + } public static final int REQUEST_COPY_DESTINATION = 1; @@ -236,33 +242,92 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (isManagedProfileAction(action)) { - UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); - UserId userId = UserId.of(userHandle); + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + profileStatusReceiverPostV(intent, action); + } else { + profileStatusReceiverPreV(intent, action); + } + + } + + private void profileStatusReceiverPostV(Intent intent, String action) { + if (!SdkLevel.isAtLeastV()) return; + if (!isProfileStatusAction(action)) return; + UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); + UserId userId = UserId.of(userHandle); + UserManager userManager = mActivity.getSystemService(UserManager.class); + if (userManager == null) { + Log.e(TAG, "cannot obtain user manager"); + return; + } + UserProperties userProperties = userManager.getUserProperties(userHandle); + if (userProperties.getShowInQuietMode() + == UserProperties.SHOW_IN_QUIET_MODE_PAUSED) { if (Objects.equal(mActivity.getSelectedUser(), userId)) { // We only need to refresh the layout when the selected user is equal to the // received profile user. - if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - // If the managed profile is turned off, we need to refresh the directory - // to update the UI to show an appropriate error message. - if (mProviderTestRunnable != null) { - mHandler.removeCallbacks(mProviderTestRunnable); - mProviderTestRunnable = null; - } - onRefresh(); - return; - } - - // When the managed profile becomes available, the provider may not be available - // immediately, we need to check if it is ready before we reload the content. - if (Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { - checkUriAndScheduleCheckIfNeeded(userId); - } + onPausedProfileStatusChange(action, userId); } + return; + } + if (userProperties.getShowInQuietMode() + == UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) { + onHiddenProfileStatusChange(action, userId); + } + } + + private void profileStatusReceiverPreV(Intent intent, String action) { + if (!isManagedProfileAction(action)) return; + UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER); + UserId userId = UserId.of(userHandle); + if (Objects.equal(mActivity.getSelectedUser(), userId)) { + // We only need to refresh the layout when the selected user is equal to the + // received profile user. + onPausedProfileStatusChange(action, userId); } } }; + private void onPausedProfileStatusChange(String action, UserId userId) { + if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) + || (SdkLevel.isAtLeastV() && Intent.ACTION_PROFILE_UNAVAILABLE.equals(action))) { + // If the managed/paused profile is turned off, we need to refresh the directory + // to update the UI to show an appropriate error message. + if (mProviderTestRunnable != null) { + mHandler.removeCallbacks(mProviderTestRunnable); + mProviderTestRunnable = null; + } + onRefresh(); + return; + } + + // When the managed/paused profile becomes available, the provider may not be available + // immediately, we need to check if it is ready before we reload the content. + if (Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action) + || (SdkLevel.isAtLeastV() && Intent.ACTION_PROFILE_AVAILABLE.equals(action))) { + checkUriAndScheduleCheckIfNeeded(userId); + } + } + + private void onHiddenProfileStatusChange(String action, UserId userId) { + UserManagerState userManagerState = DocumentsApplication.getUserManagerState(mActivity); + userManagerState.onProfileActionStatusChange(action, userId); + if (Intent.ACTION_PROFILE_UNAVAILABLE.equals(action)) { + mActivity.setHasProfileBecomeUnavailable(true); + if (mProviderTestRunnable != null) { + mHandler.removeCallbacks(mProviderTestRunnable); + mProviderTestRunnable = null; + } + while (mState.stack.size() > 0) { + mState.stack.pop(); + } + mActivity.updateNavigator(); + mActivity.setHasProfileBecomeUnavailable(false); + } else { + checkUriAndScheduleCheckIfNeeded(userId); + } + } + private final BroadcastReceiver mSdCardBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -288,7 +353,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mHandler.removeCallbacks(mProviderTestRunnable); mProviderTestRunnable = null; } - mHandler.post(() -> onRefresh()); + mHandler.post(this::onRefresh); } else { checkUriWithDelay(/* numOfRetries= */1, uri, userId); } @@ -326,8 +391,8 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On private boolean isProviderAvailable(Uri uri, UserId userId) { try (ContentProviderClient userClient = - DocumentsApplication.acquireUnstableProviderOrThrow( - userId.getContentResolver(mActivity), uri.getAuthority())) { + DocumentsApplication.acquireUnstableProviderOrThrow( + userId.getContentResolver(mActivity), uri.getAuthority())) { Cursor testCursor = userClient.query(uri, /* projection= */ null, /* queryArgs= */null, /* cancellationSignal= */ null); if (testCursor != null) { @@ -339,6 +404,12 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On return false; } + private boolean isProfileStatusAction(String action) { + if (!SdkLevel.isAtLeastV()) return isManagedProfileAction(action); + return Intent.ACTION_PROFILE_AVAILABLE.equals(action) + || Intent.ACTION_PROFILE_UNAVAILABLE.equals(action); + } + private static boolean isManagedProfileAction(String action) { return Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action) || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action); @@ -447,10 +518,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mIconHelper = new IconHelper(mActivity, MODE_GRID, mState.supportsCrossProfile()); - mAdapter = new DirectoryAddonsAdapter( - mAdapterEnv, - new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper, mInjector.fileTypeLookup) - ); + mAdapter = getModelBackedDocumentsAdapter(); mRecView.setAdapter(mAdapter); @@ -486,14 +554,14 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On DragStartListener dragStartListener = mInjector.config.dragAndDropEnabled() ? DragStartListener.create( - mIconHelper, - mModel, - mSelectionMgr, - mSelectionMetadata, - mState, - this::getModelId, - mRecView::findChildViewUnder, - DocumentsApplication.getDragAndDropManager(mActivity)) + mIconHelper, + mModel, + mSelectionMgr, + mSelectionMetadata, + mState, + this::getModelId, + mRecView::findChildViewUnder, + DocumentsApplication.getDragAndDropManager(mActivity)) : DragStartListener.STUB; { @@ -505,16 +573,16 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On new DocsStableIdProvider(mAdapter), mDetailsLookup, StorageStrategy.createStringStorage()) - .withBandOverlay(R.drawable.band_select_overlay) - .withFocusDelegate(mFocusManager) - .withOnDragInitiatedListener(dragStartListener::onDragEvent) - .withOnContextClickListener(this::onContextMenuClick) - .withOnItemActivatedListener(this::onItemActivated) - .withOperationMonitor(mContentLock.getMonitor()) - .withSelectionPredicate(selectionPredicate) - .withGestureTooltypes(MotionEvent.TOOL_TYPE_FINGER, - MotionEvent.TOOL_TYPE_STYLUS) - .build(); + .withBandOverlay(R.drawable.band_select_overlay) + .withFocusDelegate(mFocusManager) + .withOnDragInitiatedListener(dragStartListener::onDragEvent) + .withOnContextClickListener(this::onContextMenuClick) + .withOnItemActivatedListener(this::onItemActivated) + .withOperationMonitor(mContentLock.getMonitor()) + .withSelectionPredicate(selectionPredicate) + .withGestureTooltypes(MotionEvent.TOOL_TYPE_FINGER, + MotionEvent.TOOL_TYPE_STYLUS) + .build(); mInjector.updateSharedSelectionTracker(localTracker); } @@ -573,6 +641,10 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNLOCKED); filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE); + if (SdkLevel.isAtLeastV()) { + filter.addAction(Intent.ACTION_PROFILE_AVAILABLE); + filter.addAction(Intent.ACTION_PROFILE_UNAVAILABLE); + } // DocumentsApplication will resend the broadcast locally after roots are updated. // Register to a local broadcast manager to avoid this fragment from updating before // roots are updated. @@ -581,6 +653,21 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On getContext().registerReceiver(mSdCardBroadcastReceiver, getSdCardStateChangeFilter()); } + private DocumentsAdapter getModelBackedDocumentsAdapter() { + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? new DirectoryAddonsAdapter( + mAdapterEnv, new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper, + mInjector.fileTypeLookup), + UserId.CURRENT_USER, + mActivity.getSelectedUser(), + DocumentsApplication.getUserManagerState(getContext()).getUserIdToLabelMap(), + getContext().getSystemService(UserManager.class)) + : new DirectoryAddonsAdapter( + mAdapterEnv, + new ModelBackedDocumentsAdapter(mAdapterEnv, mIconHelper, + mInjector.fileTypeLookup)); + } + @Override public void onStart() { super.onStart(); @@ -737,7 +824,7 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On /** * Updates the layout after the view mode switches. * - * @param mode The new view mode. + * @param scale The new view mode. */ private void scaleLayout(float scale) { assert DEBUG; @@ -833,127 +920,99 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On MutableSelection<String> selection = new MutableSelection<>(); mSelectionMgr.copySelection(selection); - switch (item.getItemId()) { - case R.id.action_menu_select: - case R.id.dir_menu_open: - openDocuments(selection); - mActionModeController.finishActionMode(); - return true; - - case R.id.action_menu_open_with: - case R.id.dir_menu_open_with: - showChooserForDoc(selection); - return true; - - case R.id.dir_menu_open_in_new_window: - mActions.openSelectedInNewWindow(); - return true; - - case R.id.action_menu_share: - case R.id.dir_menu_share: - mActions.shareSelectedDocuments(); - return true; - - case R.id.action_menu_delete: - case R.id.dir_menu_delete: - // deleteDocuments will end action mode if the documents are deleted. - // It won't end action mode if user cancels the delete. - mActions.showDeleteDialog(); - return true; - - case R.id.action_menu_copy_to: - transferDocuments(selection, null, FileOperationService.OPERATION_COPY); - // TODO: Only finish selection mode if copy-to is not canceled. - // Need to plum down into handling the way we do with deleteDocuments. - mActionModeController.finishActionMode(); - return true; - - case R.id.action_menu_compress: - transferDocuments(selection, mState.stack, - FileOperationService.OPERATION_COMPRESS); - // TODO: Only finish selection mode if compress is not canceled. - // Need to plum down into handling the way we do with deleteDocuments. - mActionModeController.finishActionMode(); - return true; + final int id = item.getItemId(); + if (id == R.id.action_menu_select || id == R.id.dir_menu_open) { + openDocuments(selection); + mActionModeController.finishActionMode(); + return true; + } else if (id == R.id.action_menu_open_with || id == R.id.dir_menu_open_with) { + showChooserForDoc(selection); + return true; + } else if (id == R.id.dir_menu_open_in_new_window) { + mActions.openSelectedInNewWindow(); + return true; + } else if (id == R.id.action_menu_share || id == R.id.dir_menu_share) { + mActions.shareSelectedDocuments(); + return true; + } else if (id == R.id.action_menu_delete || id == R.id.dir_menu_delete) { + // deleteDocuments will end action mode if the documents are deleted. + // It won't end action mode if user cancels the delete. + mActions.showDeleteDialog(); + return true; + } else if (id == R.id.action_menu_copy_to) { + transferDocuments(selection, null, FileOperationService.OPERATION_COPY); + // TODO: Only finish selection mode if copy-to is not canceled. + // Need to plum down into handling the way we do with deleteDocuments. + mActionModeController.finishActionMode(); + return true; + } else if (id == R.id.action_menu_compress) { + transferDocuments(selection, mState.stack, + FileOperationService.OPERATION_COMPRESS); + // TODO: Only finish selection mode if compress is not canceled. + // Need to plum down into handling the way we do with deleteDocuments. + mActionModeController.finishActionMode(); + return true; // TODO: Implement extract (to the current directory). - case R.id.action_menu_extract_to: - transferDocuments(selection, null, FileOperationService.OPERATION_EXTRACT); - // TODO: Only finish selection mode if compress-to is not canceled. - // Need to plum down into handling the way we do with deleteDocuments. - mActionModeController.finishActionMode(); - return true; - - case R.id.action_menu_move_to: - if (mModel.hasDocuments(selection, DocumentFilters.NOT_MOVABLE)) { - mInjector.dialogs.showOperationUnsupported(); - return true; - } - // Exit selection mode first, so we avoid deselecting deleted documents. - mActionModeController.finishActionMode(); - transferDocuments(selection, null, FileOperationService.OPERATION_MOVE); - return true; - - case R.id.action_menu_inspect: - case R.id.dir_menu_inspect: - mActionModeController.finishActionMode(); - assert selection.size() <= 1; - DocumentInfo doc = selection.isEmpty() - ? mActivity.getCurrentDirectory() - : mModel.getDocuments(selection).get(0); - - mActions.showInspector(doc); - return true; - - case R.id.dir_menu_cut_to_clipboard: - mActions.cutToClipboard(); - return true; - - case R.id.dir_menu_copy_to_clipboard: - mActions.copyToClipboard(); - return true; - - case R.id.dir_menu_paste_from_clipboard: - pasteFromClipboard(); - return true; - - case R.id.dir_menu_paste_into_folder: - pasteIntoFolder(); - return true; - - case R.id.action_menu_select_all: - case R.id.dir_menu_select_all: - mActions.selectAllFiles(); - return true; - - case R.id.action_menu_deselect_all: - case R.id.dir_menu_deselect_all: - mActions.deselectAllFiles(); - return true; - - case R.id.action_menu_rename: - case R.id.dir_menu_rename: - renameDocuments(selection); - return true; - - case R.id.dir_menu_create_dir: - mActions.showCreateDirectoryDialog(); - return true; - - case R.id.dir_menu_view_in_owner: - mActions.viewInOwner(); - return true; - - case R.id.action_menu_sort: - mActions.showSortDialog(); + } else if (id == R.id.action_menu_extract_to) { + transferDocuments(selection, null, FileOperationService.OPERATION_EXTRACT); + // TODO: Only finish selection mode if compress-to is not canceled. + // Need to plum down into handling the way we do with deleteDocuments. + mActionModeController.finishActionMode(); + return true; + } else if (id == R.id.action_menu_move_to) { + if (mModel.hasDocuments(selection, DocumentFilters.NOT_MOVABLE)) { + mInjector.dialogs.showOperationUnsupported(); return true; - - default: - if (DEBUG) { - Log.d(TAG, "Unhandled menu item selected: " + item); - } - return false; + } + // Exit selection mode first, so we avoid deselecting deleted documents. + mActionModeController.finishActionMode(); + transferDocuments(selection, null, FileOperationService.OPERATION_MOVE); + return true; + } else if (id == R.id.action_menu_inspect || id == R.id.dir_menu_inspect) { + mActionModeController.finishActionMode(); + assert selection.size() <= 1; + DocumentInfo doc = selection.isEmpty() + ? mActivity.getCurrentDirectory() + : mModel.getDocuments(selection).get(0); + + mActions.showInspector(doc); + return true; + } else if (id == R.id.dir_menu_cut_to_clipboard) { + mActions.cutToClipboard(); + return true; + } else if (id == R.id.dir_menu_copy_to_clipboard) { + mActions.copyToClipboard(); + return true; + } else if (id == R.id.dir_menu_paste_from_clipboard) { + pasteFromClipboard(); + return true; + } else if (id == R.id.dir_menu_paste_into_folder) { + pasteIntoFolder(); + return true; + } else if (id == R.id.action_menu_select_all || id == R.id.dir_menu_select_all) { + mActions.selectAllFiles(); + return true; + } else if (id == R.id.action_menu_deselect_all || id == R.id.dir_menu_deselect_all) { + mActions.deselectAllFiles(); + return true; + } else if (id == R.id.action_menu_rename || id == R.id.dir_menu_rename) { + renameDocuments(selection); + return true; + } else if (id == R.id.dir_menu_create_dir) { + mActions.showCreateDirectoryDialog(); + return true; + } else if (id == R.id.dir_menu_view_in_owner) { + mActions.viewInOwner(); + return true; + } else if (id == R.id.action_menu_sort) { + mActions.showSortDialog(); + return true; } + if (DEBUG) { + Log.d(TAG, "Unhandled menu item selected: " + item); + } + return false; } private boolean onAccessibilityClick(View child) { @@ -1358,7 +1417,6 @@ public class DirectoryFragment extends Fragment implements SwipeRefreshLayout.On mRefreshLayout.setRefreshing(false); if (rootDoc != null && mActivity.getCurrentDirectory() == null) { // Make sure the stack does not change during task was running. - Log.d(TAG, "Root doc is retrieved. Pushing to the stack"); mState.stack.push(rootDoc); mActivity.updateNavigator(); mActions.loadDocumentsForCurrentStack(); diff --git a/src/com/android/documentsui/dirlist/DocumentHolder.java b/src/com/android/documentsui/dirlist/DocumentHolder.java index 5e38b4882..d2fa0fde2 100644 --- a/src/com/android/documentsui/dirlist/DocumentHolder.java +++ b/src/com/android/documentsui/dirlist/DocumentHolder.java @@ -36,9 +36,13 @@ import androidx.annotation.RequiresApi; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.recyclerview.widget.RecyclerView; +import com.android.documentsui.DocumentsApplication; import com.android.documentsui.R; +import com.android.documentsui.UserManagerState; import com.android.documentsui.base.Shared; 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 java.util.function.Function; @@ -63,6 +67,7 @@ public abstract class DocumentHolder private KeyboardEventListener<DocumentItemDetails> mKeyEventListener; private final DocumentItemDetails mDetails; + private UserManagerState mUserManagerState = null; public DocumentHolder(Context context, ViewGroup parent, int layout) { this(context, inflateLayout(context, parent, layout)); @@ -75,13 +80,13 @@ public abstract class DocumentHolder mContext = context; mDetails = new DocumentItemDetails(this); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mUserManagerState = DocumentsApplication.getUserManagerState(mContext); + } } /** * Binds the view to the given item data. - * @param cursor - * @param modelId - * @param state */ public abstract void bind(Cursor cursor, String modelId); @@ -95,10 +100,10 @@ public abstract class DocumentHolder * TODO: Use the DirectoryItemAnimator instead of manually controlling animation using a boolean * flag. * - * @param selected * @param animate Whether or not to animate the change. Only selection changes initiated by the - * selection manager should be animated. See - * {@link ModelBackedDocumentsAdapter#onBindViewHolder(DocumentHolder, int, java.util.List)} + * selection manager should be animated. See + * {@link ModelBackedDocumentsAdapter#onBindViewHolder(DocumentHolder, int, + * java.util.List)} */ public void setSelected(boolean selected, boolean animate) { itemView.setActivated(selected); @@ -113,13 +118,31 @@ public abstract class DocumentHolder mAction = action; } - public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) {} + /** + * @param show boolean denoting whether the current profile is non-personal + * @param clickCallback call back function + */ + public void bindPreviewIcon(boolean show, Function<View, Boolean> clickCallback) { + } - public void bindBriefcaseIcon(boolean show) {} + /** + * @param show boolean denoting whether the current profile is managed + */ + public void bindBriefcaseIcon(boolean show) { + } + + /** + * Binds profile badge icon to the documents thumbnail + * + * @param show boolean denoting whether the current profile is non-personal/parent + * @param userIdIdentifier user id of the profile the document belongs to + */ + public void bindProfileIcon(boolean show, int userIdIdentifier) { + } @Override public boolean onKey(View v, int keyCode, KeyEvent event) { - assert(mKeyEventListener != null); + assert (mKeyEventListener != null); DocumentItemDetails details = getItemDetails(); return (details == null) ? false @@ -135,7 +158,7 @@ public abstract class DocumentHolder * <p>Ideally we'd not involve DocumentHolder in propagation of events like this. */ public void addKeyEventListener(KeyboardEventListener<DocumentItemDetails> listener) { - assert(mKeyEventListener == null); + assert (mKeyEventListener == null); mKeyEventListener = listener; } @@ -179,12 +202,21 @@ public abstract class DocumentHolder return view.animate().setDuration(Shared.CHECK_ANIMATION_DURATION).alpha(alpha); } - protected String getPreviewIconContentDescription(boolean isWorkProfile, String fileName) { + protected String getPreviewIconContentDescription(boolean isNonPersonalProfile, + String fileName, UserId userId) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + String profileLabel = mUserManagerState.getUserIdToLabelMap().get(userId); + return isNonPersonalProfile + ? itemView.getResources().getString(R.string.preview_cross_profile_file, + profileLabel, fileName) + : itemView.getResources().getString(R.string.preview_file, fileName); + } if (SdkLevel.isAtLeastT()) { - return getUpdatablePreviewIconContentDescription(isWorkProfile, fileName); + return getUpdatablePreviewIconContentDescription(isNonPersonalProfile, fileName); } else { return itemView.getResources().getString( - isWorkProfile ? R.string.preview_work_file : R.string.preview_file, fileName); + isNonPersonalProfile ? R.string.preview_work_file : R.string.preview_file, + fileName); } } diff --git a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java index 744b0c9d9..2fe0155cb 100644 --- a/src/com/android/documentsui/dirlist/GridDirectoryHolder.java +++ b/src/com/android/documentsui/dirlist/GridDirectoryHolder.java @@ -35,33 +35,37 @@ import android.widget.TextView; import androidx.annotation.RequiresApi; +import com.android.documentsui.DocumentsApplication; import com.android.documentsui.IconUtils; import com.android.documentsui.R; import com.android.documentsui.base.State; +import com.android.documentsui.base.UserId; import com.android.documentsui.ui.Views; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; -final class GridDirectoryHolder extends DocumentHolder { +import java.util.Map; +final class GridDirectoryHolder extends DocumentHolder { final TextView mTitle; private final ImageView mIconCheck; private final ImageView mIconMime; - private final ImageView mIconBriefcase; + private final ImageView mIconBadge; private final View mIconLayout; - public GridDirectoryHolder(Context context, ViewGroup parent) { + GridDirectoryHolder(Context context, ViewGroup parent) { super(context, parent, R.layout.item_dir_grid); mIconLayout = itemView.findViewById(R.id.icon); mTitle = (TextView) itemView.findViewById(android.R.id.title); mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime_sm); mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check); - mIconBriefcase = (ImageView) itemView.findViewById(R.id.icon_briefcase); + mIconBadge = (ImageView) itemView.findViewById(R.id.icon_profile_badge); mIconMime.setImageDrawable( IconUtils.loadMimeIcon(context, DocumentsContract.Document.MIME_TYPE_DIR)); - if (SdkLevel.isAtLeastT()) { + if (SdkLevel.isAtLeastT() && !FeatureFlagUtils.isPrivateSpaceEnabled()) { setUpdatableWorkProfileIcon(context); } } @@ -71,7 +75,7 @@ final class GridDirectoryHolder extends DocumentHolder { DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); Drawable drawable = dpm.getResources().getDrawable(WORK_PROFILE_ICON, SOLID_COLORED, () -> context.getDrawable(R.drawable.ic_briefcase)); - mIconBriefcase.setImageDrawable(drawable); + mIconBadge.setImageDrawable(drawable); } @Override @@ -90,7 +94,16 @@ final class GridDirectoryHolder extends DocumentHolder { @Override public void bindBriefcaseIcon(boolean show) { - mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void bindProfileIcon(boolean show, int userIdIdentifier) { + Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState( + mContext).getUserIdToBadgeMap(); + Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier)); + mIconBadge.setImageDrawable(drawable); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); } @Override @@ -107,12 +120,13 @@ final class GridDirectoryHolder extends DocumentHolder { /** * Bind this view to the given document for display. - * @param cursor Pointing to the item to be bound. + * + * @param cursor Pointing to the item to be bound. * @param modelId The model ID of the item. */ @Override public void bind(Cursor cursor, String modelId) { - assert(cursor != null); + assert (cursor != null); this.mModelId = modelId; diff --git a/src/com/android/documentsui/dirlist/GridDocumentHolder.java b/src/com/android/documentsui/dirlist/GridDocumentHolder.java index 2da538203..a63fba264 100644 --- a/src/com/android/documentsui/dirlist/GridDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/GridDocumentHolder.java @@ -37,14 +37,17 @@ import android.widget.TextView; import androidx.annotation.RequiresApi; +import com.android.documentsui.DocumentsApplication; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Shared; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.ui.Views; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; +import java.util.Map; import java.util.function.Function; final class GridDocumentHolder extends DocumentHolder { @@ -56,7 +59,7 @@ final class GridDocumentHolder extends DocumentHolder { final ImageView mIconMimeSm; final ImageView mIconThumb; final ImageView mIconCheck; - final ImageView mIconBriefcase; + final ImageView mIconBadge; final IconHelper mIconHelper; final View mIconLayout; final View mPreviewIcon; @@ -64,7 +67,7 @@ final class GridDocumentHolder extends DocumentHolder { // This is used in as a convenience in our bind method. private final DocumentInfo mDoc = new DocumentInfo(); - public GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) { + GridDocumentHolder(Context context, ViewGroup parent, IconHelper iconHelper) { super(context, parent, R.layout.item_doc_grid); mIconLayout = itemView.findViewById(R.id.icon); @@ -75,12 +78,12 @@ final class GridDocumentHolder extends DocumentHolder { mIconMimeSm = (ImageView) itemView.findViewById(R.id.icon_mime_sm); mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb); mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check); - mIconBriefcase = (ImageView) itemView.findViewById(R.id.icon_briefcase); + mIconBadge = (ImageView) itemView.findViewById(R.id.icon_profile_badge); mPreviewIcon = itemView.findViewById(R.id.preview_icon); mIconHelper = iconHelper; - if (SdkLevel.isAtLeastT()) { + if (SdkLevel.isAtLeastT() && !FeatureFlagUtils.isPrivateSpaceEnabled()) { setUpdatableWorkProfileIcon(context); } } @@ -90,7 +93,7 @@ final class GridDocumentHolder extends DocumentHolder { DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); Drawable drawable = dpm.getResources().getDrawable(WORK_PROFILE_ICON, SOLID_COLORED, () -> context.getDrawable(R.drawable.ic_briefcase)); - mIconBriefcase.setImageDrawable(drawable); + mIconBadge.setImageDrawable(drawable); } @Override @@ -108,7 +111,7 @@ final class GridDocumentHolder extends DocumentHolder { // But it should be an error to be set to selected && be disabled. if (!itemView.isEnabled()) { - assert(!selected); + assert (!selected); } super.setSelected(selected, animate); @@ -138,19 +141,28 @@ final class GridDocumentHolder extends DocumentHolder { mPreviewIcon.setContentDescription( getPreviewIconContentDescription( mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier()), - mDoc.displayName)); + mDoc.displayName, mDoc.userId)); mPreviewIcon.setAccessibilityDelegate(new PreviewAccessibilityDelegate(clickCallback)); } } @Override public void bindBriefcaseIcon(boolean show) { - mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void bindProfileIcon(boolean show, int userIdIdentifier) { + Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState( + mContext).getUserIdToBadgeMap(); + Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier)); + mIconBadge.setImageDrawable(drawable); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); } @Override public boolean inDragRegion(MotionEvent event) { - // Entire grid box should be draggable + // Entire grid box should be draggable return true; } @@ -166,12 +178,13 @@ final class GridDocumentHolder extends DocumentHolder { /** * Bind this view to the given document for display. - * @param cursor Pointing to the item to be bound. + * + * @param cursor Pointing to the item to be bound. * @param modelId The model ID of the item. */ @Override public void bind(Cursor cursor, String modelId) { - assert(cursor != null); + assert (cursor != null); mModelId = modelId; diff --git a/src/com/android/documentsui/dirlist/GridPhotoHolder.java b/src/com/android/documentsui/dirlist/GridPhotoHolder.java index 06185068d..84aab3c20 100644 --- a/src/com/android/documentsui/dirlist/GridPhotoHolder.java +++ b/src/com/android/documentsui/dirlist/GridPhotoHolder.java @@ -36,14 +36,17 @@ import android.widget.ImageView; import androidx.annotation.RequiresApi; +import com.android.documentsui.DocumentsApplication; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Shared; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.ui.Views; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; +import java.util.Map; import java.util.function.Function; final class GridPhotoHolder extends DocumentHolder { @@ -53,23 +56,23 @@ final class GridPhotoHolder extends DocumentHolder { private final ImageView mIconCheck; private final IconHelper mIconHelper; private final View mPreviewIcon; - private final View mIconBriefcase; + private final View mIconBadge; // This is used in as a convenience in our bind method. private final DocumentInfo mDoc = new DocumentInfo(); - public GridPhotoHolder(Context context, ViewGroup parent, IconHelper iconHelper) { + GridPhotoHolder(Context context, ViewGroup parent, IconHelper iconHelper) { super(context, parent, R.layout.item_photo_grid); mIconMimeLg = (ImageView) itemView.findViewById(R.id.icon_mime_lg); mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb); mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check); - mIconBriefcase = itemView.findViewById(R.id.icon_briefcase); + mIconBadge = itemView.findViewById(R.id.icon_profile_badge); mPreviewIcon = itemView.findViewById(R.id.preview_icon); mIconHelper = iconHelper; - if (SdkLevel.isAtLeastT()) { + if (SdkLevel.isAtLeastT() && !FeatureFlagUtils.isPrivateSpaceEnabled()) { setUpdatableWorkProfileIcon(context); } } @@ -80,7 +83,7 @@ final class GridPhotoHolder extends DocumentHolder { Drawable drawable = dpm.getResources().getDrawable( WORK_PROFILE_ICON, SOLID_NOT_COLORED, () -> context.getDrawable(R.drawable.ic_briefcase)); - ImageView icon = (ImageView) mIconBriefcase.findViewById(R.id.icon_id); + ImageView icon = (ImageView) mIconBadge.findViewById(R.id.icon_id); icon.setImageDrawable(drawable); } @@ -123,14 +126,24 @@ final class GridPhotoHolder extends DocumentHolder { mPreviewIcon.setContentDescription( getPreviewIconContentDescription( mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier()), - mDoc.displayName)); + mDoc.displayName, mDoc.userId)); mPreviewIcon.setAccessibilityDelegate(new PreviewAccessibilityDelegate(clickCallback)); } } @Override public void bindBriefcaseIcon(boolean show) { - mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void bindProfileIcon(boolean show, int userIdIdentifier) { + Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState( + mContext).getUserIdToBadgeMap(); + Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier)); + ImageView icon = mIconBadge.findViewById(R.id.icon_id); + icon.setImageDrawable(drawable); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); } @Override @@ -152,7 +165,8 @@ final class GridPhotoHolder extends DocumentHolder { /** * Bind this view to the given document for display. - * @param cursor Pointing to the item to be bound. + * + * @param cursor Pointing to the item to be bound. * @param modelId The model ID of the item. */ @Override diff --git a/src/com/android/documentsui/dirlist/HeaderMessageDocumentHolder.java b/src/com/android/documentsui/dirlist/HeaderMessageDocumentHolder.java index bb98894a3..b62605954 100644 --- a/src/com/android/documentsui/dirlist/HeaderMessageDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/HeaderMessageDocumentHolder.java @@ -45,7 +45,7 @@ final class HeaderMessageDocumentHolder extends MessageHolder { private final Button mActionButton; private Message mMessage; - public HeaderMessageDocumentHolder(Context context, ViewGroup parent) { + HeaderMessageDocumentHolder(Context context, ViewGroup parent) { super(context, parent, R.layout.item_doc_header_message); mRoot = itemView.findViewById(R.id.item_root); diff --git a/src/com/android/documentsui/dirlist/IconHelper.java b/src/com/android/documentsui/dirlist/IconHelper.java index e2c990583..9a650b32c 100644 --- a/src/com/android/documentsui/dirlist/IconHelper.java +++ b/src/com/android/documentsui/dirlist/IconHelper.java @@ -20,6 +20,7 @@ import static com.android.documentsui.base.SharedMinimal.VERBOSE; import static com.android.documentsui.base.State.MODE_GRID; import static com.android.documentsui.base.State.MODE_LIST; +import android.app.ActivityManager; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; @@ -41,11 +42,13 @@ import com.android.documentsui.R; import com.android.documentsui.ThumbnailCache; import com.android.documentsui.ThumbnailCache.Result; import com.android.documentsui.ThumbnailLoader; +import com.android.documentsui.UserManagerState; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.MimeTypes; import com.android.documentsui.base.State; import com.android.documentsui.base.State.ViewMode; import com.android.documentsui.base.UserId; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.function.BiConsumer; @@ -66,31 +69,33 @@ public class IconHelper { private final boolean mMaybeShowBadge; @Nullable private final UserId mManagedUser; + private final UserManagerState mUserManagerState; /** - * @param context * @param mode MODE_GRID or MODE_LIST */ public IconHelper(Context context, int mode, boolean maybeShowBadge) { this(context, mode, maybeShowBadge, DocumentsApplication.getThumbnailCache(context), - DocumentsApplication.getUserIdManager(context).getManagedUser()); + FeatureFlagUtils.isPrivateSpaceEnabled() ? null + : DocumentsApplication.getUserIdManager(context).getManagedUser(), + FeatureFlagUtils.isPrivateSpaceEnabled() + ? DocumentsApplication.getUserManagerState(context) : null); } @VisibleForTesting IconHelper(Context context, int mode, boolean maybeShowBadge, ThumbnailCache thumbnailCache, - @Nullable UserId managedUser) { + @Nullable UserId managedUser, @Nullable UserManagerState userManagerState) { mContext = context; setViewMode(mode); mThumbnailCache = thumbnailCache; mManagedUser = managedUser; mMaybeShowBadge = maybeShowBadge; + mUserManagerState = userManagerState; } /** * Enables or disables thumbnails. When thumbnails are disabled, mime icons (or custom icons, if * specified by the document) are used instead. - * - * @param enabled */ public void setThumbnailsEnabled(boolean enabled) { mThumbnailsEnabled = enabled; @@ -125,8 +130,6 @@ public class IconHelper { /** * Cancels any ongoing load operations associated with the given ImageView. - * - * @param icon */ public void stopLoading(ImageView icon) { final ThumbnailLoader oldTask = (ThumbnailLoader) icon.getTag(); @@ -139,11 +142,10 @@ public class IconHelper { /** * Load thumbnails for a directory list item. * - * @param doc The document - * @param iconThumb The itemview's thumbnail icon. - * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. + * @param doc The document + * @param iconThumb The itemview's thumbnail icon. + * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. * @param subIconMime The second itemview's mime icon. Always visible. - * @return */ public void load( DocumentInfo doc, @@ -157,15 +159,14 @@ public class IconHelper { /** * Load thumbnails for a directory list item. * - * @param uri The URI for the file being represented. - * @param mimeType The mime type of the file being represented. - * @param docFlags Flags for the file being represented. - * @param docIcon Custom icon (if any) for the file being requested. + * @param uri The URI for the file being represented. + * @param mimeType The mime type of the file being represented. + * @param docFlags Flags for the file being represented. + * @param docIcon Custom icon (if any) for the file being requested. * @param docLastModified the last modified value of the file being requested. - * @param iconThumb The itemview's thumbnail icon. - * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. - * @param subIconMime The second itemview's mime icon. Always visible. - * @return + * @param iconThumb The itemview's thumbnail icon. + * @param iconMime The itemview's mime icon. Hidden when iconThumb is shown. + * @param subIconMime The second itemview's mime icon. Always visible. */ public void load(Uri uri, UserId userId, String mimeType, int docFlags, int docIcon, long docLastModified, ImageView iconThumb, ImageView iconMime, @@ -180,7 +181,7 @@ public class IconHelper { final boolean showThumbnail = supportsThumbnail && allowThumbnail && mThumbnailsEnabled; if (showThumbnail) { loadedThumbnail = - loadThumbnail(uri, userId, docAuthority, docLastModified, iconThumb, iconMime); + loadThumbnail(uri, userId, docAuthority, docLastModified, iconThumb, iconMime); } final Drawable mimeIcon = getDocumentIcon(mContext, userId, docAuthority, @@ -207,9 +208,11 @@ public class IconHelper { iconThumb.setImageBitmap(cachedThumbnail); boolean stale = (docLastModified > result.getLastModified()); - if (VERBOSE) Log.v(TAG, - String.format("Load thumbnail for %s, got result %d and stale %b.", - uri.toString(), result.getStatus(), stale)); + if (VERBOSE) { + Log.v(TAG, + String.format("Load thumbnail for %s, got result %d and stale %b.", + uri.toString(), result.getStatus(), stale)); + } if (!result.isExactHit() || stale) { final BiConsumer<View, View> animator = (cachedThumbnail == null ? ThumbnailLoader.ANIM_FADE_IN : @@ -264,6 +267,11 @@ public class IconHelper { * Returns true if we should show a briefcase icon for the given user. */ public boolean shouldShowBadge(int userIdIdentifier) { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + return mMaybeShowBadge + && mUserManagerState.getUserIds().size() > 1 + && ActivityManager.getCurrentUser() != userIdIdentifier; + } return mMaybeShowBadge && mManagedUser != null && mManagedUser.getIdentifier() == userIdIdentifier; } diff --git a/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java b/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java index 2e2b92128..f20bb6eeb 100644 --- a/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/InflateMessageDocumentHolder.java @@ -52,7 +52,7 @@ final class InflateMessageDocumentHolder extends MessageHolder { private View mCrossProfileContent; private ProgressBar mCrossProfileProgress; - public InflateMessageDocumentHolder(Context context, ViewGroup parent) { + InflateMessageDocumentHolder(Context context, ViewGroup parent) { super(context, parent, R.layout.item_doc_inflated_message); mContentView = itemView.findViewById(R.id.content); mCrossProfileView = itemView.findViewById(R.id.cross_profile); diff --git a/src/com/android/documentsui/dirlist/ListDocumentHolder.java b/src/com/android/documentsui/dirlist/ListDocumentHolder.java index 96c49e047..dbeeb94f0 100644 --- a/src/com/android/documentsui/dirlist/ListDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/ListDocumentHolder.java @@ -40,6 +40,7 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; +import com.android.documentsui.DocumentsApplication; import com.android.documentsui.R; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Lookup; @@ -48,9 +49,11 @@ import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.roots.RootCursorWrapper; import com.android.documentsui.ui.Views; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; import java.util.ArrayList; +import java.util.Map; import java.util.function.Function; final class ListDocumentHolder extends DocumentHolder { @@ -67,7 +70,7 @@ final class ListDocumentHolder extends DocumentHolder { private final ImageView mIconMime; private final ImageView mIconThumb; private final ImageView mIconCheck; - private final ImageView mIconBriefcase; + private final ImageView mIconBadge; private final View mIconLayout; final View mPreviewIcon; @@ -84,7 +87,7 @@ final class ListDocumentHolder extends DocumentHolder { mIconMime = (ImageView) itemView.findViewById(R.id.icon_mime); mIconThumb = (ImageView) itemView.findViewById(R.id.icon_thumb); mIconCheck = (ImageView) itemView.findViewById(R.id.icon_check); - mIconBriefcase = (ImageView) itemView.findViewById(R.id.icon_briefcase); + mIconBadge = (ImageView) itemView.findViewById(R.id.icon_profile_badge); mTitle = (TextView) itemView.findViewById(android.R.id.title); mSize = (TextView) itemView.findViewById(R.id.size); mDate = (TextView) itemView.findViewById(R.id.date); @@ -98,7 +101,7 @@ final class ListDocumentHolder extends DocumentHolder { mFileTypeLookup = fileTypeLookup; mDoc = new DocumentInfo(); - if (SdkLevel.isAtLeastT()) { + if (SdkLevel.isAtLeastT() && !FeatureFlagUtils.isPrivateSpaceEnabled()) { setUpdatableWorkProfileIcon(context); } } @@ -108,7 +111,7 @@ final class ListDocumentHolder extends DocumentHolder { DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); Drawable drawable = dpm.getResources().getDrawable(WORK_PROFILE_ICON, SOLID_COLORED, () -> context.getDrawable(R.drawable.ic_briefcase)); - mIconBriefcase.setImageDrawable(drawable); + mIconBadge.setImageDrawable(drawable); } @Override @@ -158,7 +161,7 @@ final class ListDocumentHolder extends DocumentHolder { mPreviewIcon.setContentDescription( getPreviewIconContentDescription( mIconHelper.shouldShowBadge(mDoc.userId.getIdentifier()), - mDoc.displayName)); + mDoc.displayName, mDoc.userId)); mPreviewIcon.setAccessibilityDelegate( new PreviewAccessibilityDelegate(clickCallback)); } @@ -167,7 +170,16 @@ final class ListDocumentHolder extends DocumentHolder { @Override public void bindBriefcaseIcon(boolean show) { - mIconBriefcase.setVisibility(show ? View.VISIBLE : View.GONE); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); + } + + @Override + public void bindProfileIcon(boolean show, int userIdIdentifier) { + Map<UserId, Drawable> userIdToBadgeMap = DocumentsApplication.getUserManagerState( + mContext).getUserIdToBadgeMap(); + Drawable drawable = userIdToBadgeMap.get(UserId.of(userIdIdentifier)); + mIconBadge.setImageDrawable(drawable); + mIconBadge.setVisibility(show ? View.VISIBLE : View.GONE); } @Override @@ -209,7 +221,7 @@ final class ListDocumentHolder extends DocumentHolder { /** * Bind this view to the given document for display. * - * @param cursor Pointing to the item to be bound. + * @param cursor Pointing to the item to be bound. * @param modelId The model ID of the item. */ @Override diff --git a/src/com/android/documentsui/dirlist/Message.java b/src/com/android/documentsui/dirlist/Message.java index ccdad461a..37837e00f 100644 --- a/src/com/android/documentsui/dirlist/Message.java +++ b/src/com/android/documentsui/dirlist/Message.java @@ -35,8 +35,13 @@ import android.Manifest; import android.app.AuthenticationRequiredException; import android.app.admin.DevicePolicyManager; import android.content.pm.PackageManager; +import android.content.pm.UserProperties; +import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; @@ -52,13 +57,20 @@ import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.dirlist.DocumentsAdapter.Environment; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + /** * Data object used by {@link InflateMessageDocumentHolder} and {@link HeaderMessageDocumentHolder}. */ abstract class Message { + private static final int ACCESS_CROSS_PROFILE_FILES = -1; + protected final Environment mEnv; // If the message has a button, this will be the default button call back. protected final Runnable mDefaultCallback; @@ -78,7 +90,7 @@ abstract class Message { mDefaultCallback = defaultCallback; } - abstract void update(Update Event); + abstract void update(Update event); protected void update(@Nullable CharSequence messageTitle, CharSequence messageString, @Nullable CharSequence buttonString, Drawable icon) { @@ -121,6 +133,7 @@ abstract class Message { /** * Return this message should keep showing or not. + * * @return true if this message should keep showing. */ boolean shouldKeep() { @@ -173,7 +186,7 @@ abstract class Message { } private void updateToAuthenticationExceptionHeader(Update event) { - assert(mEnv.getFeatures().isRemoteActionsEnabled()); + assert (mEnv.getFeatures().isRemoteActionsEnabled()); RootInfo root = mEnv.getDisplayState().stack.getRoot(); String appName = DocumentsApplication.getProvidersCache( @@ -199,7 +212,12 @@ abstract class Message { final static class InflateMessage extends Message { + private static final String TAG = "InflateMessage"; + private UserId mSourceUserId = null; + private UserId mSelectedUserId = null; + private Map<UserId, String> mUserIdToLabelMap = new HashMap<>(); private final boolean mCanModifyQuietMode; + private UserManager mUserManager = null; InflateMessage(Environment env, Runnable callback) { super(env, callback); @@ -208,6 +226,38 @@ abstract class Message { == PackageManager.PERMISSION_GRANTED; } + InflateMessage(Environment env, Runnable callback, UserId sourceUserId, + UserId selectedUserId, Map<UserId, String> userIdToLabelMap, + UserManager userManager) { + super(env, callback); + mSourceUserId = sourceUserId; + mSelectedUserId = selectedUserId; + mUserIdToLabelMap = userIdToLabelMap; + mUserManager = userManager != null ? userManager + : mEnv.getContext().getSystemService(UserManager.class); + mCanModifyQuietMode = setCanModifyQuietMode(); + } + + private boolean setCanModifyQuietMode() { + if (SdkLevel.isAtLeastV() && FeatureFlagUtils.isPrivateSpaceEnabled()) { + if (mUserManager == null) { + Log.e(TAG, "can not obtain user manager class"); + return false; + } + + UserProperties userProperties = mUserManager.getUserProperties( + UserHandle.of(mSelectedUserId.getIdentifier())); + return userProperties.getShowInQuietMode() + == UserProperties.SHOW_IN_QUIET_MODE_PAUSED + && mEnv.getContext().checkSelfPermission( + Manifest.permission.MODIFY_QUIET_MODE) + == PackageManager.PERMISSION_GRANTED; + } else { + return mEnv.getContext().checkSelfPermission(Manifest.permission.MODIFY_QUIET_MODE) + == PackageManager.PERMISSION_GRANTED; + } + } + @Override void update(Update event) { reset(); @@ -233,16 +283,29 @@ abstract class Message { private void updateToQuietModeErrorMessage(UserId userId) { mLayout = InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR; - CharSequence buttonText = null; + String buttonText = null; + Resources res = null; + String selectedProfile = null; + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + res = mEnv.getContext().getResources(); + assert mUserIdToLabelMap != null; + selectedProfile = mUserIdToLabelMap.get(userId); + } if (mCanModifyQuietMode) { - buttonText = getEnterpriseString( - WORK_PROFILE_OFF_ENABLE_BUTTON, R.string.quiet_mode_button); + buttonText = FeatureFlagUtils.isPrivateSpaceEnabled() + ? res.getString(R.string.profile_quiet_mode_button, + selectedProfile.toLowerCase(Locale.getDefault())) + : getEnterpriseString( + WORK_PROFILE_OFF_ENABLE_BUTTON, R.string.quiet_mode_button); mCallback = () -> mEnv.getActionHandler().requestQuietModeDisabled( mEnv.getDisplayState().stack.getRoot(), userId); } - update( - getEnterpriseString( - WORK_PROFILE_OFF_ERROR_TITLE, R.string.quiet_mode_error_title), + + update(FeatureFlagUtils.isPrivateSpaceEnabled() + ? res.getString(R.string.profile_quiet_mode_error_title, + selectedProfile) + : getEnterpriseString( + WORK_PROFILE_OFF_ERROR_TITLE, R.string.quiet_mode_error_title), /* messageString= */ "", buttonText, getWorkProfileOffIcon()); @@ -257,58 +320,117 @@ abstract class Message { } private CharSequence getCrossProfileNoPermissionErrorTitle() { - boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); switch (mEnv.getDisplayState().action) { case State.ACTION_GET_CONTENT: case State.ACTION_OPEN: case State.ACTION_OPEN_TREE: - return currentUserIsSystem - ? getEnterpriseString( - CANT_SELECT_WORK_FILES_TITLE, - R.string.cant_select_work_files_error_title) - : getEnterpriseString( - CANT_SELECT_PERSONAL_FILES_TITLE, - R.string.cant_select_personal_files_error_title); + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? getErrorTitlePrivateSpaceEnabled(ACCESS_CROSS_PROFILE_FILES) + : getErrorTitlePrivateSpaceDisabled(ACCESS_CROSS_PROFILE_FILES); case State.ACTION_CREATE: - return currentUserIsSystem - ? getEnterpriseString( - CANT_SAVE_TO_WORK_TITLE, R.string.cant_save_to_work_error_title) - : getEnterpriseString( - CANT_SAVE_TO_PERSONAL_TITLE, - R.string.cant_save_to_personal_error_title); + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? getErrorTitlePrivateSpaceEnabled(State.ACTION_CREATE) + : getErrorTitlePrivateSpaceDisabled(State.ACTION_CREATE); } return getEnterpriseString( CROSS_PROFILE_NOT_ALLOWED_TITLE, R.string.cross_profile_action_not_allowed_title); } - private CharSequence getCrossProfileNoPermissionErrorMessage() { + private CharSequence getErrorTitlePrivateSpaceEnabled(int action) { + Resources res = mEnv.getContext().getResources(); + String selectedProfileLabel = mUserIdToLabelMap.get(mSelectedUserId); + if (selectedProfileLabel == null) return ""; + if (action == ACCESS_CROSS_PROFILE_FILES) { + return res.getString(R.string.cant_select_cross_profile_files_error_title, + selectedProfileLabel.toLowerCase(Locale.getDefault())); + } else if (action == State.ACTION_CREATE) { + return res.getString(R.string.cant_save_to_cross_profile_error_title, + selectedProfileLabel.toLowerCase(Locale.getDefault())); + } else { + Log.e(TAG, "Unexpected intent action received."); + return ""; + } + } + + private CharSequence getErrorTitlePrivateSpaceDisabled(int action) { boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); + if (action == ACCESS_CROSS_PROFILE_FILES) { + return currentUserIsSystem + ? getEnterpriseString(CANT_SELECT_WORK_FILES_TITLE, + R.string.cant_select_work_files_error_title) + : getEnterpriseString(CANT_SELECT_PERSONAL_FILES_TITLE, + R.string.cant_select_personal_files_error_title); + } else if (action == State.ACTION_CREATE) { + return currentUserIsSystem + ? getEnterpriseString(CANT_SAVE_TO_WORK_TITLE, + R.string.cant_save_to_work_error_title) + : getEnterpriseString(CANT_SAVE_TO_PERSONAL_TITLE, + R.string.cant_save_to_personal_error_title); + } else { + Log.e(TAG, "Unexpected intent action received."); + return ""; + } + } + + private CharSequence getCrossProfileNoPermissionErrorMessage() { switch (mEnv.getDisplayState().action) { case State.ACTION_GET_CONTENT: case State.ACTION_OPEN: case State.ACTION_OPEN_TREE: - return currentUserIsSystem - ? getEnterpriseString( - CANT_SELECT_WORK_FILES_MESSAGE, - R.string.cant_select_work_files_error_message) - : getEnterpriseString( - CANT_SELECT_PERSONAL_FILES_MESSAGE, - R.string.cant_select_personal_files_error_message); + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? getErrorMessagePrivateSpaceEnabled(ACCESS_CROSS_PROFILE_FILES) + : getErrorMessagePrivateSpaceDisabled(ACCESS_CROSS_PROFILE_FILES); case State.ACTION_CREATE: - return currentUserIsSystem - ? getEnterpriseString( - CANT_SAVE_TO_WORK_MESSAGE, - R.string.cant_save_to_work_error_message) - : getEnterpriseString( - CANT_SAVE_TO_PERSONAL_MESSAGE, - R.string.cant_save_to_personal_error_message); + return FeatureFlagUtils.isPrivateSpaceEnabled() + ? getErrorMessagePrivateSpaceEnabled(State.ACTION_CREATE) + : getErrorMessagePrivateSpaceDisabled(State.ACTION_CREATE); + } return getEnterpriseString( CROSS_PROFILE_NOT_ALLOWED_MESSAGE, R.string.cross_profile_action_not_allowed_message); } + private CharSequence getErrorMessagePrivateSpaceEnabled(int action) { + Resources res = mEnv.getContext().getResources(); + String sourceProfileLabel = mUserIdToLabelMap.get(mSourceUserId); + String selectedProfileLabel = mUserIdToLabelMap.get(mSelectedUserId); + if (sourceProfileLabel == null || selectedProfileLabel == null) return ""; + if (action == ACCESS_CROSS_PROFILE_FILES) { + return res.getString(R.string.cant_select_cross_profile_files_error_message, + selectedProfileLabel.toLowerCase(Locale.getDefault()), + sourceProfileLabel.toLowerCase(Locale.getDefault())); + } else if (action == State.ACTION_CREATE) { + return res.getString(R.string.cant_save_to_cross_profile_error_message, + sourceProfileLabel.toLowerCase(Locale.getDefault()), + selectedProfileLabel.toLowerCase(Locale.getDefault())); + } else { + Log.e(TAG, "Unexpected intent action received."); + return ""; + } + } + + private CharSequence getErrorMessagePrivateSpaceDisabled(int action) { + boolean currentUserIsSystem = UserId.CURRENT_USER.isSystem(); + if (action == ACCESS_CROSS_PROFILE_FILES) { + return currentUserIsSystem + ? getEnterpriseString(CANT_SELECT_WORK_FILES_MESSAGE, + R.string.cant_select_work_files_error_message) + : getEnterpriseString(CANT_SELECT_PERSONAL_FILES_MESSAGE, + R.string.cant_select_personal_files_error_message); + } else if (action == State.ACTION_CREATE) { + return currentUserIsSystem + ? getEnterpriseString(CANT_SAVE_TO_WORK_MESSAGE, + R.string.cant_save_to_work_error_message) + : getEnterpriseString(CANT_SAVE_TO_PERSONAL_MESSAGE, + R.string.cant_save_to_personal_error_message); + } else { + Log.e(TAG, "Unexpected intent action received."); + return ""; + } + } + private void updateToInflatedErrorMessage() { update(null, mEnv.getContext().getResources().getText(R.string.query_error), null, mEnv.getContext().getDrawable(R.drawable.hourglass)); diff --git a/src/com/android/documentsui/dirlist/MessageHolder.java b/src/com/android/documentsui/dirlist/MessageHolder.java index 1d8d7d61c..3d65cef04 100644 --- a/src/com/android/documentsui/dirlist/MessageHolder.java +++ b/src/com/android/documentsui/dirlist/MessageHolder.java @@ -24,7 +24,7 @@ import android.widget.Space; * Base class for all non-Document Holder classes. */ abstract class MessageHolder extends DocumentHolder { - public MessageHolder(Context context, Space space) { + MessageHolder(Context context, Space space) { super(context, space); } diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java index f76360790..d7bfb5d3f 100644 --- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java +++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java @@ -35,6 +35,7 @@ import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Lookup; import com.android.documentsui.base.State; import com.android.documentsui.roots.RootCursorWrapper; +import com.android.documentsui.util.FeatureFlagUtils; import java.util.ArrayList; import java.util.List; @@ -136,14 +137,18 @@ final class ModelBackedDocumentsAdapter extends DocumentsAdapter { boolean enabled = mEnv.isDocumentEnabled(docMimeType, docFlags); boolean selected = mEnv.isSelected(modelId); if (!enabled) { - assert(!selected); + assert (!selected); } holder.setEnabled(enabled); holder.setSelected(mEnv.isSelected(modelId), false); holder.setAction(mEnv.getDisplayState().action); holder.bindPreviewIcon(mEnv.getDisplayState().shouldShowPreview() && enabled, view -> mEnv.getActionHandler().previewItem(holder.getItemDetails())); - holder.bindBriefcaseIcon(mIconHelper.shouldShowBadge(userIdIdentifier)); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + holder.bindProfileIcon(mIconHelper.shouldShowBadge(userIdIdentifier), userIdIdentifier); + } else { + holder.bindBriefcaseIcon(mIconHelper.shouldShowBadge(userIdIdentifier)); + } mEnv.onBindDocumentHolder(holder, cursor); } diff --git a/src/com/android/documentsui/dirlist/TransparentDividerDocumentHolder.java b/src/com/android/documentsui/dirlist/TransparentDividerDocumentHolder.java index 44efd8090..f382900fe 100644 --- a/src/com/android/documentsui/dirlist/TransparentDividerDocumentHolder.java +++ b/src/com/android/documentsui/dirlist/TransparentDividerDocumentHolder.java @@ -32,7 +32,7 @@ final class TransparentDividerDocumentHolder extends MessageHolder { private final int mVisibleHeight; private State mState; - public TransparentDividerDocumentHolder(Context context) { + TransparentDividerDocumentHolder(Context context) { super(context, new Space(context)); mVisibleHeight = context.getResources().getDimensionPixelSize( diff --git a/tests/common/com/android/documentsui/TestUserManagerState.java b/tests/common/com/android/documentsui/TestUserManagerState.java index 0591770fc..8dbe62a2a 100644 --- a/tests/common/com/android/documentsui/TestUserManagerState.java +++ b/tests/common/com/android/documentsui/TestUserManagerState.java @@ -31,8 +31,9 @@ public class TestUserManagerState implements UserManagerState { public List<UserId> userIds = new ArrayList<>(); public Map<UserId, String> userIdToLabelMap = new HashMap<>(); public Map<UserId, Boolean> canFrowardToProfileIdMap = new HashMap<>(); - public Map<UserId, Drawable> userIdToBadgeMap = new HashMap<>(); + public String profileLabel = "Test"; + public Drawable profileBadge = null; @Override public List<UserId> getUserIds() { @@ -53,4 +54,21 @@ public class TestUserManagerState implements UserManagerState { public Map<UserId, Boolean> getCanForwardToProfileIdMap(Intent intent) { return canFrowardToProfileIdMap; } + + @Override + public void onProfileActionStatusChange(String action, UserId userId) { + if (Intent.ACTION_PROFILE_UNAVAILABLE.equals(action)) { + userIds.remove(userId); + userIdToLabelMap.remove(userId); + userIdToBadgeMap.remove(userId); + canFrowardToProfileIdMap.put(userId, false); + return; + } + if (!userIds.contains(userId)) { + userIds.add(userId); + } + userIdToLabelMap.put(userId, profileLabel); + userIdToBadgeMap.put(userId, profileBadge); + canFrowardToProfileIdMap.put(userId, true); + } } diff --git a/tests/common/com/android/documentsui/testing/TestProvidersAccess.java b/tests/common/com/android/documentsui/testing/TestProvidersAccess.java index 554c42c7d..5a138ea9f 100644 --- a/tests/common/com/android/documentsui/testing/TestProvidersAccess.java +++ b/tests/common/com/android/documentsui/testing/TestProvidersAccess.java @@ -111,7 +111,7 @@ public class TestProvidersAccess implements ProvidersAccess { INSPECTOR.rootId = InspectorProvider.ROOT_ID; INSPECTOR.title = "Inspector"; INSPECTOR.flags = Root.FLAG_LOCAL_ONLY - | Root.FLAG_SUPPORTS_CREATE; + | Root.FLAG_SUPPORTS_CREATE; IMAGE = new RootInfo(); IMAGE.userId = userId; diff --git a/tests/functional/com/android/documentsui/ActionCreateDocumentUiTest.java b/tests/functional/com/android/documentsui/ActionCreateDocumentUiTest.java index 9e12433f7..fd17f72fd 100644 --- a/tests/functional/com/android/documentsui/ActionCreateDocumentUiTest.java +++ b/tests/functional/com/android/documentsui/ActionCreateDocumentUiTest.java @@ -93,8 +93,8 @@ public class ActionCreateDocumentUiTest extends DocumentsUiTestBase { assertThat(uri.getPath()).contains(fileName); assertThat(resultData.getFlags()).isEqualTo(FLAG_GRANT_READ_URI_PERMISSION - | FLAG_GRANT_WRITE_URI_PERMISSION - | FLAG_GRANT_PERSISTABLE_URI_PERMISSION); + | FLAG_GRANT_WRITE_URI_PERMISSION + | FLAG_GRANT_PERSISTABLE_URI_PERMISSION); final boolean deletedSuccessfully = DocumentsContract.deleteDocument(context.getContentResolver(), uri); diff --git a/tests/functional/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java b/tests/functional/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java index bcd1131b7..dcb741721 100644 --- a/tests/functional/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java +++ b/tests/functional/com/android/documentsui/dirlist/DirectoryAddonsAdapterTest.java @@ -16,8 +16,13 @@ package com.android.documentsui.dirlist; +import static org.mockito.Mockito.when; + import android.content.Context; +import android.content.pm.UserProperties; import android.os.Bundle; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.DocumentsContract; import android.test.AndroidTestCase; import android.view.ViewGroup; @@ -29,10 +34,23 @@ import com.android.documentsui.ActionHandler; import com.android.documentsui.ModelId; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.State; +import com.android.documentsui.base.UserId; import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestFileTypeLookup; +import com.android.documentsui.testing.TestProvidersAccess; +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; + +import com.google.android.collect.Lists; + +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import java.util.HashMap; +import java.util.Map; @MediumTest public class DirectoryAddonsAdapterTest extends AndroidTestCase { @@ -43,6 +61,18 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase { private DirectoryAddonsAdapter mAdapter; private ActionHandler mActionHandler; + @Parameter(0) + public boolean isPrivateSpaceEnabled; + + /** + * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with + * private space flag enabled and once with it disabled. + */ + @Parameters(name = "privateSpaceEnabled={0}") + public static Iterable<?> data() { + return Lists.newArrayList(true, false); + } + public void setUp() { mEnv = TestEnv.create(AUTHORITY); @@ -52,13 +82,36 @@ public class DirectoryAddonsAdapterTest extends AndroidTestCase { final Context testContext = TestContext.createStorageTestContext(getContext(), AUTHORITY); DocumentsAdapter.Environment env = new TestEnvironment(testContext, mEnv, mActionHandler); - mAdapter = new DirectoryAddonsAdapter( - env, - new ModelBackedDocumentsAdapter( - env, - new IconHelper(testContext, State.MODE_GRID, /* maybeShowBadge= */ false), - new TestFileTypeLookup())); - + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + UserId managedUser = UserId.of(100); + Map<UserId, String> userIdToLabelMap = new HashMap<>(); + userIdToLabelMap.put(TestProvidersAccess.USER_ID, "Personal"); + userIdToLabelMap.put(managedUser, "Work"); + UserManager userManager = UserManagers.create(); + if (SdkLevel.isAtLeastV()) { + UserProperties managedUserProperties = new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + when(userManager.getUserProperties(UserHandle.of(100))) + .thenReturn(managedUserProperties); + } + mAdapter = new DirectoryAddonsAdapter( + env, + new ModelBackedDocumentsAdapter( + env, + new IconHelper(testContext, State.MODE_GRID, /* maybeShowBadge= */ + false), + new TestFileTypeLookup()), + TestProvidersAccess.USER_ID, managedUser, userIdToLabelMap, userManager); + } else { + mAdapter = new DirectoryAddonsAdapter( + env, + new ModelBackedDocumentsAdapter( + env, + new IconHelper(testContext, State.MODE_GRID, /* maybeShowBadge= */ + false), + new TestFileTypeLookup())); + } mEnv.model.addUpdateListener(mAdapter.getModelUpdateListener()); } diff --git a/tests/unit/com/android/documentsui/UserManagerStateTest.java b/tests/unit/com/android/documentsui/UserManagerStateTest.java index 670cb1da2..f05e186c6 100644 --- a/tests/unit/com/android/documentsui/UserManagerStateTest.java +++ b/tests/unit/com/android/documentsui/UserManagerStateTest.java @@ -43,6 +43,7 @@ import com.google.common.collect.Lists; import org.junit.Before; import org.junit.Test; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,12 +51,6 @@ import java.util.Map; @SmallTest public class UserManagerStateTest { - private static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; - private static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; - 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); @@ -83,29 +78,31 @@ public class UserManagerStateTest { if (SdkLevel.isAtLeastV()) { UserProperties systemUserProperties = new UserProperties.Builder() - .setShowInSharingSurfaces(SHOW_IN_SHARING_SURFACES_SEPARATE) + .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setCrossProfileContentSharingStrategy( - CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION) + UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION) .build(); UserProperties managedUserProperties = new UserProperties.Builder() - .setShowInSharingSurfaces(SHOW_IN_SHARING_SURFACES_SEPARATE) + .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setCrossProfileContentSharingStrategy( - CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION) + UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION) + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) .build(); UserProperties privateUserProperties = new UserProperties.Builder() - .setShowInSharingSurfaces(SHOW_IN_SHARING_SURFACES_SEPARATE) + .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setCrossProfileContentSharingStrategy( - CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) + UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_HIDDEN) .build(); UserProperties otherUserProperties = new UserProperties.Builder() - .setShowInSharingSurfaces(SHOW_IN_SHARING_SURFACES_WITH_PARENT) + .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT) .setCrossProfileContentSharingStrategy( - CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) + UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) .build(); UserProperties normalUserProperties = new UserProperties.Builder() - .setShowInSharingSurfaces(SHOW_IN_SHARING_SURFACES_NO) + .setShowInSharingSurfaces(UserProperties.SHOW_IN_SHARING_SURFACES_NO) .setCrossProfileContentSharingStrategy( - CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) + UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT) .build(); when(mMockUserManager.getUserProperties(mSystemUser)).thenReturn(systemUserProperties); when(mMockUserManager.getUserProperties(mManagedUser)).thenReturn( @@ -562,6 +559,118 @@ public class UserManagerStateTest { .isEqualTo(expectedCanForwardToProfileIdMap); } + @Test + public void testOnProfileStatusChange_anyIntentActionOnManagedProfile() { + if (!SdkLevel.isAtLeastV()) return; + UserId currentUser = UserId.of(mSystemUser); + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + + // UserManagerState#mUserId and UserManagerState#mCanForwardToProfileIdMap will empty + // by default if the getters of these member variables have not been called + List<UserId> userIdsBeforeIntent = new ArrayList<>(mUserManagerState.getUserIds()); + Map<UserId, Boolean> canForwardToProfileIdMapBeforeIntent = new HashMap<>( + mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)); + + String action = "any_intent"; + mUserManagerState.onProfileActionStatusChange(action, UserId.of(mManagedUser)); + + assertWithMessage("Unexpected changes to user id list on receiving intent: " + action) + .that(mUserManagerState.getUserIds()).isEqualTo(userIdsBeforeIntent); + assertWithMessage( + "Unexpected changes to canForwardToProfileIdMap on receiving intent: " + action) + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)).isEqualTo( + canForwardToProfileIdMapBeforeIntent); + } + + @Test + public void testOnProfileStatusChange_actionProfileUnavailableOnPrivateProfile() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mSystemUser); + UserId managedUser = UserId.of(mManagedUser); + UserId privateUser = UserId.of(mPrivateUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser)); + + // UserManagerState#mUserId and UserManagerState#mCanForwardToProfileIdMap will empty + // by default if the getters of these member variables have not been called + List<UserId> userIdsBeforeIntent = new ArrayList<>(mUserManagerState.getUserIds()); + Map<UserId, Boolean> canForwardToProfileIdMapBeforeIntent = new HashMap<>( + mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)); + + List<UserId> expectedUserIdsAfterIntent = Lists.newArrayList(currentUser, managedUser); + Map<UserId, Boolean> expectedCanForwardToProfileIdMapAfterIntent = new HashMap<>(); + expectedCanForwardToProfileIdMapAfterIntent.put(currentUser, true); + expectedCanForwardToProfileIdMapAfterIntent.put(managedUser, true); + + String action = Intent.ACTION_PROFILE_UNAVAILABLE; + mUserManagerState.onProfileActionStatusChange(action, privateUser); + + assertWithMessage( + "UserIds list should not be same before and after receiving intent: " + action) + .that(mUserManagerState.getUserIds()).isNotEqualTo(userIdsBeforeIntent); + assertWithMessage("Unexpected changes to user id list on receiving intent: " + action) + .that(mUserManagerState.getUserIds()).isEqualTo(expectedUserIdsAfterIntent); + assertWithMessage( + "CanForwardToLabelMap should not be same before and after receiving intent: " + + action) + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)).isNotEqualTo( + canForwardToProfileIdMapBeforeIntent); + assertWithMessage( + "Unexpected changes to canForwardToProfileIdMap on receiving intent: " + action) + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)).isEqualTo( + expectedCanForwardToProfileIdMapAfterIntent); + } + + @Test + public void testOnProfileStatusChange_actionProfileAvailableOnPrivateProfile() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + UserId currentUser = UserId.of(mSystemUser); + UserId managedUser = UserId.of(mManagedUser); + UserId privateUser = UserId.of(mPrivateUser); + final List<ResolveInfo> mMockResolveInfoList = Lists.newArrayList(mMockInfo1, mMockInfo2); + when(mMockPackageManager.queryIntentActivitiesAsUser(mMockIntent, + PackageManager.MATCH_DEFAULT_ONLY, mSystemUser)).thenReturn( + mMockResolveInfoList); + initializeUserManagerState(currentUser, + Lists.newArrayList(mSystemUser, mManagedUser)); + + // UserManagerState#mUserId and UserManagerState#mCanForwardToProfileIdMap will empty + // by default if the getters of these member variables have not been called + List<UserId> userIdsBeforeIntent = new ArrayList<>(mUserManagerState.getUserIds()); + Map<UserId, Boolean> canForwardToProfileIdMapBeforeIntent = new HashMap<>( + mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)); + + List<UserId> expectedUserIdsAfterIntent = Lists.newArrayList(currentUser, managedUser, + privateUser); + Map<UserId, Boolean> expectedCanForwardToProfileIdMapAfterIntent = new HashMap<>(); + expectedCanForwardToProfileIdMapAfterIntent.put(currentUser, true); + expectedCanForwardToProfileIdMapAfterIntent.put(managedUser, true); + expectedCanForwardToProfileIdMapAfterIntent.put(privateUser, true); + + String action = Intent.ACTION_PROFILE_AVAILABLE; + mUserManagerState.onProfileActionStatusChange(action, privateUser); + + assertWithMessage( + "UserIds list should not be same before and after receiving intent: " + action) + .that(mUserManagerState.getUserIds()).isNotEqualTo(userIdsBeforeIntent); + assertWithMessage("Unexpected changes to user id list on receiving intent: " + action) + .that(mUserManagerState.getUserIds()).isEqualTo(expectedUserIdsAfterIntent); + assertWithMessage( + "CanForwardToLabelMap should not be same before and after receiving intent: " + + action) + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)).isNotEqualTo( + canForwardToProfileIdMapBeforeIntent); + assertWithMessage( + "Unexpected changes to canForwardToProfileIdMap on receiving intent: " + action) + .that(mUserManagerState.getCanForwardToProfileIdMap(mMockIntent)).isEqualTo( + expectedCanForwardToProfileIdMapAfterIntent); + } + 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/dirlist/AppsRowManagerTest.java b/tests/unit/com/android/documentsui/dirlist/AppsRowManagerTest.java index 6482440da..fc78def0e 100644 --- a/tests/unit/com/android/documentsui/dirlist/AppsRowManagerTest.java +++ b/tests/unit/com/android/documentsui/dirlist/AppsRowManagerTest.java @@ -36,6 +36,7 @@ import com.android.documentsui.ActionHandler; import com.android.documentsui.BaseActivity; import com.android.documentsui.R; import com.android.documentsui.TestUserIdManager; +import com.android.documentsui.TestUserManagerState; import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.sidebar.AppItem; @@ -44,16 +45,22 @@ import com.android.documentsui.sidebar.RootItem; import com.android.documentsui.testing.TestActionHandler; 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 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.ArrayList; import java.util.List; +@RunWith(Parameterized.class) public class AppsRowManagerTest { private AppsRowManager mAppsRowManager; @@ -62,17 +69,31 @@ public class AppsRowManagerTest { private boolean mMaybeShowBadge; private BaseActivity mActivity; private TestUserIdManager mTestUserIdManager; + private TestUserManagerState mTestUserManagerState; private State mState; private View mAppsRow; private LinearLayout mAppsGroup; + @Parameter(0) + public boolean isPrivateSpaceEnabled; + + /** + * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with + * private space flag enabled and once with it disabled. + */ + @Parameters(name = "privateSpaceEnabled={0}") + public static Iterable<?> data() { + return com.google.android.collect.Lists.newArrayList(true, false); + } + @Before public void setUp() { mActionHandler = new TestActionHandler(); mTestUserIdManager = new TestUserIdManager(); + mTestUserManagerState = new TestUserManagerState(); - mAppsRowManager = new AppsRowManager(mActionHandler, mMaybeShowBadge, mTestUserIdManager); + mAppsRowManager = getAppsRowManager(); Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); LayoutInflater layoutInflater = LayoutInflater.from(context); @@ -86,9 +107,20 @@ public class AppsRowManagerTest { when(mActivity.findViewById(R.id.apps_row)).thenReturn(mAppsRow); when(mActivity.findViewById(R.id.apps_group)).thenReturn(mAppsGroup); when(mActivity.getSelectedUser()).thenReturn(TestProvidersAccess.USER_ID); + } + private AppsRowManager getAppsRowManager() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mTestUserManagerState = new TestUserManagerState(); + mTestUserManagerState.userIds = + Lists.newArrayList(UserId.DEFAULT_USER, TestProvidersAccess.OtherUser.USER_ID, + TestProvidersAccess.AnotherUser.USER_ID); + return new AppsRowManager(mActionHandler, mMaybeShowBadge, mTestUserManagerState); + } + mTestUserIdManager = new TestUserIdManager(); mTestUserIdManager.userIds = Lists.newArrayList(UserId.DEFAULT_USER, TestProvidersAccess.OtherUser.USER_ID); + return new AppsRowManager(mActionHandler, mMaybeShowBadge, mTestUserIdManager); } @Test @@ -105,9 +137,9 @@ public class AppsRowManagerTest { assertEquals(chipDataList.size(), rootList.size()); assertEquals(TestProvidersAccess.INSPECTOR.title, chipDataList.get(0).getTitle()); - assertEquals(null, chipDataList.get(0).getSummary()); + assertNull(chipDataList.get(0).getSummary()); assertEquals(TestProvidersAccess.PICKLES.title, chipDataList.get(1).getTitle()); - assertEquals(null, chipDataList.get(1).getSummary()); + assertNull(chipDataList.get(1).getSummary()); assertEquals(TestProvidersAccess.PICKLES.summary, chipDataList.get(2).getSummary()); assertEquals(TestProvidersAccess.PICKLES.summary, chipDataList.get(3).getSummary()); } diff --git a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java index 2574f73bd..1e48e3387 100644 --- a/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java +++ b/tests/unit/com/android/documentsui/dirlist/DocumentHolderTest.java @@ -44,7 +44,8 @@ public class DocumentHolderTest extends AndroidTestCase { LayoutInflater inflater = LayoutInflater.from(context); mHolder = new DocumentHolder(getContext(), inflater.inflate(R.layout.item_doc_list, null)) { @Override - public void bind(Cursor cursor, String modelId) {} + public void bind(Cursor cursor, String modelId) { + } }; mListener = new TestListener(); @@ -67,12 +68,12 @@ public class DocumentHolderTest extends AndroidTestCase { public MotionEvent createEvent(int tooltype) { long time = SystemClock.uptimeMillis(); - PointerProperties properties[] = new PointerProperties[] { + PointerProperties[] properties = new PointerProperties[]{ new PointerProperties() }; properties[0].toolType = tooltype; - PointerCoords coords[] = new PointerCoords[] { + PointerCoords[] coords = new PointerCoords[]{ new PointerCoords() }; @@ -96,7 +97,7 @@ public class DocumentHolderTest extends AndroidTestCase { 0, // edgeflags 0, // source 0 // flags - ); + ); } private class TestListener extends KeyboardEventListener<DocumentItemDetails> { diff --git a/tests/unit/com/android/documentsui/dirlist/IconHelperTest.java b/tests/unit/com/android/documentsui/dirlist/IconHelperTest.java index d2419e8cf..c129f0d79 100644 --- a/tests/unit/com/android/documentsui/dirlist/IconHelperTest.java +++ b/tests/unit/com/android/documentsui/dirlist/IconHelperTest.java @@ -24,51 +24,120 @@ import android.os.UserHandle; import androidx.test.filters.SmallTest; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.documentsui.TestUserManagerState; import com.android.documentsui.ThumbnailCache; 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.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; @SmallTest +@RunWith(Parameterized.class) public final class IconHelperTest { - + private final UserId mSystemUser = UserId.of(UserHandle.SYSTEM); + private final UserId mManagedUser = UserId.of(100); + private final UserId mPrivateUser = UserId.of(101); private Context mContext; private IconHelper mIconHelper; private ThumbnailCache mThumbnailCache = new ThumbnailCache(1000); + private final TestUserManagerState mTestUserManagerState = new TestUserManagerState(); - private UserId systemUser = UserId.of(UserHandle.SYSTEM); - private UserId managedUser = UserId.of(100); + @Parameter(0) + public boolean isPrivateSpaceEnabled; + + /** + * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with + * private space flag enabled and once with it disabled. + */ + @Parameters(name = "privateSpaceEnabled={0}") + public static Iterable<?> data() { + return Lists.newArrayList(true, false); + } @Before public void setUp() { mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, - mThumbnailCache, managedUser); + mIconHelper = FeatureFlagUtils.isPrivateSpaceEnabled() + ? new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, + mThumbnailCache, null, mTestUserManagerState) + : new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, + mThumbnailCache, mManagedUser, null); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mTestUserManagerState.userIds = SdkLevel.isAtLeastV() + ? Lists.newArrayList(mSystemUser, mManagedUser, mPrivateUser) + : Lists.newArrayList(mSystemUser, mManagedUser); + } } @Test public void testShouldShowBadge_returnFalse_onSystemUser() { - assertThat(mIconHelper.shouldShowBadge(systemUser.getIdentifier())).isFalse(); + assertThat(mIconHelper.shouldShowBadge(mSystemUser.getIdentifier())).isFalse(); } @Test public void testShouldShowBadge_returnTrue_onManagedUser() { - assertThat(mIconHelper.shouldShowBadge(managedUser.getIdentifier())).isTrue(); + assertThat(mIconHelper.shouldShowBadge(mManagedUser.getIdentifier())).isTrue(); } @Test - public void testShouldShowBadge_returnFalse_onManagedUser_doNotShowBadge() { + public void testShouldShowBadge_returnTrue_onPrivateUser() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + assertThat(mIconHelper.shouldShowBadge(mPrivateUser.getIdentifier())).isTrue(); + } + + @Test + public void testShouldShowBadge_returnFalseOnManagedUser_doNotShowBadge() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + mTestUserManagerState.userIds = Lists.newArrayList(mSystemUser, mManagedUser); + mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ false, + mThumbnailCache, null, mTestUserManagerState); + } else { + mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ false, + mThumbnailCache, mManagedUser, null); + } + assertThat(mIconHelper.shouldShowBadge(mManagedUser.getIdentifier())).isFalse(); + } + + @Test + public void testShouldShowBadge_returnFalseOnPrivateUser_doNotShowBadge() { + if (!FeatureFlagUtils.isPrivateSpaceEnabled()) return; mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ false, - mThumbnailCache, managedUser); - assertThat(mIconHelper.shouldShowBadge(managedUser.getIdentifier())).isFalse(); + mThumbnailCache, null, mTestUserManagerState); + assertThat(mIconHelper.shouldShowBadge(mPrivateUser.getIdentifier())).isFalse(); + } + + @Test + public void testShouldShowBadge_returnFalseOnManagedUser_withoutManagedUser() { + if (FeatureFlagUtils.isPrivateSpaceEnabled()) return; + mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, + mThumbnailCache, /* mManagedUser= */ null, null); + assertThat(mIconHelper.shouldShowBadge(mManagedUser.getIdentifier())).isFalse(); + } + + @Test + public void testShouldShowBadge_returnFalseOnManagedUser_withoutMultipleUsers() { + if (!FeatureFlagUtils.isPrivateSpaceEnabled()) return; + mTestUserManagerState.userIds = Lists.newArrayList(mManagedUser); + mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, + mThumbnailCache, /* mManagedUser= */ null, mTestUserManagerState); + assertThat(mIconHelper.shouldShowBadge(mManagedUser.getIdentifier())).isFalse(); } @Test - public void testShouldShowBadge_returnFalse_onManagedUser_withoutManagedUser() { + public void testShouldShowBadge_returnFalseOnPrivateUser_withoutMultipleUsers() { + if (!SdkLevel.isAtLeastV() || !FeatureFlagUtils.isPrivateSpaceEnabled()) return; + mTestUserManagerState.userIds = Lists.newArrayList(mPrivateUser); mIconHelper = new IconHelper(mContext, State.MODE_LIST, /* maybeShowBadge= */ true, - mThumbnailCache, /* managedUser= */ null); - assertThat(mIconHelper.shouldShowBadge(managedUser.getIdentifier())).isFalse(); + mThumbnailCache, /* mManagedUser= */ null, mTestUserManagerState); + assertThat(mIconHelper.shouldShowBadge(mPrivateUser.getIdentifier())).isFalse(); } } diff --git a/tests/unit/com/android/documentsui/dirlist/InflateMessageDocumentHolderTest.java b/tests/unit/com/android/documentsui/dirlist/InflateMessageDocumentHolderTest.java index 436e4fbca..e929f1152 100644 --- a/tests/unit/com/android/documentsui/dirlist/InflateMessageDocumentHolderTest.java +++ b/tests/unit/com/android/documentsui/dirlist/InflateMessageDocumentHolderTest.java @@ -18,7 +18,11 @@ package com.android.documentsui.dirlist; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.when; + import android.content.Context; +import android.content.pm.UserProperties; +import android.os.UserManager; import android.view.View; import android.widget.Button; @@ -29,14 +33,28 @@ import com.android.documentsui.CrossProfileQuietModeException; import com.android.documentsui.Model; import com.android.documentsui.R; import com.android.documentsui.base.State; +import com.android.documentsui.base.UserId; import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestEnv; import com.android.documentsui.testing.TestProvidersAccess; +import com.android.documentsui.testing.UserManagers; +import com.android.documentsui.util.FeatureFlagUtils; +import com.android.modules.utils.build.SdkLevel; + +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.HashMap; +import java.util.Map; @SmallTest +@RunWith(Parameterized.class) public final class InflateMessageDocumentHolderTest { private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); @@ -46,22 +64,65 @@ public final class InflateMessageDocumentHolderTest { private TestActionHandler mTestActionHandler = new TestActionHandler(); private InflateMessageDocumentHolder mHolder; + @Parameter(0) + public boolean isPrivateSpaceEnabled; + + /** + * Parametrize values for {@code isPrivateSpaceEnabled} to run all the tests twice once with + * private space flag enabled and once with it disabled. + */ + @Parameters(name = "privateSpaceEnabled={0}") + public static Iterable<?> data() { + return Lists.newArrayList(true, false); + } + @Before public void setUp() { DocumentsAdapter.Environment env = new TestEnvironment(mContext, TestEnv.create(), mTestActionHandler); env.getDisplayState().action = State.ACTION_GET_CONTENT; - env.getDisplayState().canShareAcrossProfile = true; env.getDisplayState().supportsCrossProfile = true; - mInflateMessage = new Message.InflateMessage(env, mDefaultCallback); + mContext.setTheme(R.style.DocumentsTheme); mContext.getTheme().applyStyle(R.style.DocumentsDefaultTheme, /* force= */false); + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + Map<UserId, String> userIdToLabelMap = new HashMap<>(); + userIdToLabelMap.put(TestProvidersAccess.USER_ID, "Personal"); + userIdToLabelMap.put(TestProvidersAccess.OtherUser.USER_ID, "Work"); + env.getDisplayState().canForwardToProfileIdMap.put(TestProvidersAccess.USER_ID, true); + env.getDisplayState().canForwardToProfileIdMap.put( + TestProvidersAccess.OtherUser.USER_ID, true); + UserManager userManager = UserManagers.create(); + if (SdkLevel.isAtLeastV()) { + userIdToLabelMap.put(TestProvidersAccess.AnotherUser.USER_ID, "Private"); + env.getDisplayState().canForwardToProfileIdMap.put( + TestProvidersAccess.AnotherUser.USER_ID, true); + UserProperties otherUserProperties = new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + UserProperties anotherUserProperties = new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + when(userManager.getUserProperties(TestProvidersAccess.OtherUser.USER_HANDLE)) + .thenReturn(otherUserProperties); + when(userManager.getUserProperties(TestProvidersAccess.AnotherUser.USER_HANDLE)) + .thenReturn(anotherUserProperties); + } + + mInflateMessage = new Message.InflateMessage(env, mDefaultCallback, + TestProvidersAccess.USER_ID, + TestProvidersAccess.OtherUser.USER_ID, userIdToLabelMap, userManager); + } else { + mInflateMessage = new Message.InflateMessage(env, mDefaultCallback); + env.getDisplayState().canShareAcrossProfile = true; + } mHolder = new InflateMessageDocumentHolder(mContext, /* parent= */null); } @Test public void testClickingButtonShouldShowProgressBar() { + if (SdkLevel.isAtLeastV()) return; Model.Update error = new Model.Update( new CrossProfileQuietModeException(TestProvidersAccess.OtherUser.USER_ID), /* remoteActionsEnabled= */ true); diff --git a/tests/unit/com/android/documentsui/dirlist/MessageTest.java b/tests/unit/com/android/documentsui/dirlist/MessageTest.java index 0816684d7..93266abcd 100644 --- a/tests/unit/com/android/documentsui/dirlist/MessageTest.java +++ b/tests/unit/com/android/documentsui/dirlist/MessageTest.java @@ -16,6 +16,8 @@ package com.android.documentsui.dirlist; +import static com.android.documentsui.DevicePolicyResources.Drawables.Style.OUTLINE; +import static com.android.documentsui.DevicePolicyResources.Drawables.WORK_PROFILE_OFF_ICON; import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_MESSAGE; import static com.android.documentsui.DevicePolicyResources.Strings.CANT_SELECT_WORK_FILES_TITLE; import static com.android.documentsui.DevicePolicyResources.Strings.WORK_PROFILE_OFF_ENABLE_BUTTON; @@ -31,6 +33,9 @@ import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyResourcesManager; import android.content.Context; +import android.content.pm.UserProperties; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; import android.os.UserManager; import androidx.core.util.Preconditions; @@ -45,12 +50,17 @@ import com.android.documentsui.base.State; import com.android.documentsui.base.UserId; import com.android.documentsui.testing.TestActionHandler; import com.android.documentsui.testing.TestEnv; +import com.android.documentsui.testing.TestProvidersAccess; import com.android.documentsui.testing.UserManagers; +import com.android.documentsui.util.FeatureFlagUtils; import com.android.modules.utils.build.SdkLevel; import org.junit.Before; import org.junit.Test; +import java.util.HashMap; +import java.util.Map; + @SmallTest public final class MessageTest { @@ -69,7 +79,8 @@ public final class MessageTest { mUserManager = UserManagers.create(); mTestActionHandler = new TestActionHandler(); mDevicePolicyManager = mock(DevicePolicyManager.class); - when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); + when(mContext.getSystemServiceName(UserManager.class)).thenReturn("mUserManager"); + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); when(mContext.getSystemServiceName(DevicePolicyManager.class)) .thenReturn(Context.DEVICE_POLICY_SERVICE); when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)) @@ -79,7 +90,24 @@ public final class MessageTest { DocumentsAdapter.Environment env = new TestEnvironment(mContext, TestEnv.create(), mTestActionHandler); env.getDisplayState().action = State.ACTION_GET_CONTENT; - mInflateMessage = new Message.InflateMessage(env, mDefaultCallback); + if (SdkLevel.isAtLeastV()) { + UserProperties userProperties = new UserProperties.Builder() + .setShowInQuietMode(UserProperties.SHOW_IN_QUIET_MODE_PAUSED) + .build(); + UserHandle userHandle = UserHandle.of(mUserId.getIdentifier()); + when(mUserManager.getUserProperties(userHandle)).thenReturn(userProperties); + } + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + String personalLabel = mContext.getString(R.string.personal_tab); + String workLabel = mContext.getString(R.string.work_tab); + Map<UserId, String> userIdToLabelMap = new HashMap<>(); + userIdToLabelMap.put(TestProvidersAccess.USER_ID, personalLabel); + userIdToLabelMap.put(mUserId, workLabel); + mInflateMessage = new Message.InflateMessage(env, mDefaultCallback, + TestProvidersAccess.USER_ID, mUserId, userIdToLabelMap, mUserManager); + } else { + mInflateMessage = new Message.InflateMessage(env, mDefaultCallback); + } } @Test @@ -115,6 +143,7 @@ public final class MessageTest { @Test public void testInflateMessage_updateToCrossProfileQuietMode() { + if (SdkLevel.isAtLeastV()) return; Model.Update error = new Model.Update( new CrossProfileQuietModeException(mUserId), /* isRemoteActionsEnabled= */ true); @@ -143,4 +172,47 @@ public final class MessageTest { assertThat(mTestActionHandler.mRequestDisablingQuietModeHappened).isTrue(); } + + @Test + public void testInflateMessage_updateToCrossProfileQuietMode_PostV() { + if (!SdkLevel.isAtLeastV()) return; + Model.Update error = new Model.Update( + new CrossProfileQuietModeException(mUserId), + /* isRemoteActionsEnabled= */ true); + + DevicePolicyResourcesManager devicePolicyResourcesManager = mock( + DevicePolicyResourcesManager.class); + when(mDevicePolicyManager.getResources()).thenReturn(devicePolicyResourcesManager); + + if (FeatureFlagUtils.isPrivateSpaceEnabled()) { + Drawable icon = mContext.getDrawable(R.drawable.work_off); + when(devicePolicyResourcesManager.getDrawable(eq(WORK_PROFILE_OFF_ICON), eq(OUTLINE), + any())) + .thenReturn(icon); + } else { + String title = mContext.getString(R.string.quiet_mode_error_title); + String text = mContext.getString(R.string.quiet_mode_button); + when(devicePolicyResourcesManager.getString(eq(WORK_PROFILE_OFF_ERROR_TITLE), any())) + .thenReturn(title); + when(devicePolicyResourcesManager.getString(eq(WORK_PROFILE_OFF_ENABLE_BUTTON), any())) + .thenReturn(text); + } + mInflateMessage.update(error); + + assertThat(mInflateMessage.getLayout()) + .isEqualTo(InflateMessageDocumentHolder.LAYOUT_CROSS_PROFILE_ERROR); + + if (!FeatureFlagUtils.isPrivateSpaceEnabled()) { + assert mInflateMessage.getTitleString() != null; + assertThat(mInflateMessage.getTitleString().toString()) + .isEqualTo(mContext.getString(R.string.quiet_mode_error_title)); + assert mInflateMessage.getButtonString() != null; + assertThat(mInflateMessage.getButtonString().toString()).isEqualTo( + mContext.getString(R.string.quiet_mode_button)); + } + assertThat(mInflateMessage.mCallback).isNotNull(); + mInflateMessage.mCallback.run(); + + assertThat(mTestActionHandler.mRequestDisablingQuietModeHappened).isTrue(); + } } |