diff options
| author | 2020-03-27 16:57:50 +0000 | |
|---|---|---|
| committer | 2020-03-30 17:00:09 +0000 | |
| commit | 27608554dc385e94aa2ee210be3fe0f04d346434 (patch) | |
| tree | 55db812af47a3c168485b246291a7489a6f22bc7 | |
| parent | 66041721d7c6bf4713169f7f421d6907aa342057 (diff) | |
Show empty state screens in order of priority.
To improve the user experience, this CL changes the order
in which the empty state screens are shown:
1. (highest priority) cross-profile disabled by policy
2. no apps available
3. (least priority) work is off
The intention is to prevent the user from having to turn
the work profile on if there will not be any apps resolved
anyway.
Fixes: 150936283
Test: atest ResolverActivityTest
Test: atest ChooserActivityTest
Test: manually tested each possible combination of edge cases
(work profile off, no apps found, cross-profile intents disabled)
Change-Id: Ic86c90bdda93cf731df2133890081b5583ef35e1
5 files changed, 208 insertions, 28 deletions
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index c53516389cd9..9fc0da83c504 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -300,21 +300,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { UserHandle listUserHandle = activeListAdapter.getUserHandle(); - if (listUserHandle.equals(mWorkProfileUserHandle) - && mInjector.isQuietModeEnabled(mWorkProfileUserHandle)) { - DevicePolicyEventLogger - .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED) - .setStrings(getMetricsCategory()) - .write(); - showWorkProfileOffEmptyState(activeListAdapter, - v -> { - ProfileDescriptor descriptor = getItem( - userHandleToPageIndex(activeListAdapter.getUserHandle())); - showSpinner(descriptor.getEmptyStateView()); - mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle); - }); - return false; - } + if (UserHandle.myUserId() != listUserHandle.getIdentifier()) { if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), UserHandle.myUserId(), listUserHandle.getIdentifier())) { @@ -352,7 +338,65 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { protected abstract void showNoWorkToPersonalIntentsEmptyState( ResolverListAdapter activeListAdapter); - void showNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) { + /** + * The empty state screens are shown according to their priority: + * <ol> + * <li>(highest priority) cross-profile disabled by policy (handled in + * {@link #rebuildTab(ResolverListAdapter, boolean)})</li> + * <li>no apps available</li> + * <li>(least priority) work is off</li> + * </ol> + * + * The intention is to prevent the user from having to turn + * the work profile on if there will not be any apps resolved + * anyway. + */ + void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) { + if (maybeShowWorkProfileOffEmptyState(listAdapter)) { + return; + } + maybeShowNoAppsAvailableEmptyState(listAdapter); + } + + /** + * Returns {@code true} if the work profile off empty state screen is shown. + */ + private boolean maybeShowWorkProfileOffEmptyState(ResolverListAdapter listAdapter) { + UserHandle listUserHandle = listAdapter.getUserHandle(); + if (!listUserHandle.equals(mWorkProfileUserHandle) + || !mInjector.isQuietModeEnabled(mWorkProfileUserHandle) + || !hasResolvedAppsInWorkProfile(listAdapter)) { + return false; + } + DevicePolicyEventLogger + .createEvent(DevicePolicyEnums.RESOLVER_EMPTY_STATE_WORK_APPS_DISABLED) + .setStrings(getMetricsCategory()) + .write(); + showWorkProfileOffEmptyState(listAdapter, + v -> { + ProfileDescriptor descriptor = getItem( + userHandleToPageIndex(listAdapter.getUserHandle())); + showSpinner(descriptor.getEmptyStateView()); + mInjector.requestQuietModeEnabled(false, mWorkProfileUserHandle); + }); + return true; + } + + /** + * Returns {@code true} if there is at least one app resolved in the work profile, + * regardless of whether the work profile is enabled or not. + */ + private boolean hasResolvedAppsInWorkProfile(ResolverListAdapter listAdapter) { + List<ResolverActivity.ResolvedComponentInfo> userStateIndependentWorkResolvers = + listAdapter.mResolverListController.getUserStateIndependentResolversAsUser( + listAdapter.getIntents(), mWorkProfileUserHandle); + return userStateIndependentWorkResolvers.stream() + .anyMatch(resolvedComponentInfo -> + resolvedComponentInfo.getResolveInfoAt(0).targetUserId + == UserHandle.USER_CURRENT); + } + + private void maybeShowNoAppsAvailableEmptyState(ResolverListAdapter listAdapter) { UserHandle listUserHandle = listAdapter.getUserHandle(); if (mWorkProfileUserHandle != null && (UserHandle.myUserId() == listUserHandle.getIdentifier() diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 2352180bcba3..f088ab38658c 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -994,8 +994,8 @@ public class ResolverActivity extends Activity implements if (isAutolaunching() || maybeAutolaunchActivity()) { return; } - if (shouldShowEmptyState(listAdapter)) { - mMultiProfilePagerAdapter.showNoAppsAvailableEmptyState(listAdapter); + if (isResolverListEmpty(listAdapter)) { + mMultiProfilePagerAdapter.showEmptyResolverListEmptyState(listAdapter); } else { mMultiProfilePagerAdapter.showListView(listAdapter); } @@ -1640,12 +1640,12 @@ public class ResolverActivity extends Activity implements private void setupViewVisibilities() { ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter(); - if (!shouldShowEmptyState(activeListAdapter)) { + if (!isResolverListEmpty(activeListAdapter)) { addUseDifferentAppLabelIfNecessary(activeListAdapter); } } - private boolean shouldShowEmptyState(ResolverListAdapter listAdapter) { + private boolean isResolverListEmpty(ResolverListAdapter listAdapter) { int count = listAdapter.getUnfilteredCount(); return count == 0 && listAdapter.getPlaceholderCount() == 0; } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index 51dce5547890..3f897a5f26bf 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -120,12 +120,32 @@ public class ResolverListController { boolean shouldGetActivityMetadata, List<Intent> intents, UserHandle userHandle) { + int baseFlags = PackageManager.MATCH_DEFAULT_ONLY + | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); + return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); + } + + /** + * Returns a list of resolved intents which is user state-independent. This means it will + * return the same results regardless of whether the {@code userHandle} user is disabled or not. + */ + public List<ResolverActivity.ResolvedComponentInfo> getUserStateIndependentResolversAsUser( + List<Intent> intents, + UserHandle userHandle) { + int baseFlags = PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; + return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); + } + + private List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUserInternal( + List<Intent> intents, + UserHandle userHandle, + int baseFlags) { List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null; for (int i = 0, N = intents.size(); i < N; i++) { final Intent intent = intents.get(i); - int flags = PackageManager.MATCH_DEFAULT_ONLY - | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); + int flags = baseFlags; if (intent.isWebIntent() || (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) { flags |= PackageManager.MATCH_INSTANT; diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index e427421a5a80..812e2a6c63fd 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -1330,8 +1330,15 @@ public class ChooserActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( + Mockito.isA(List.class), + Mockito.isA(UserHandle.class))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + // When work profile is disabled, we get 0 results when we query the work profile + // intents. + setupResolverControllers(personalResolvedComponentInfos, + /* workResolvedComponentInfos */ new ArrayList<>()); Intent sendIntent = createSendTextIntent(); sendIntent.setType("TestType"); @@ -1348,7 +1355,7 @@ public class ChooserActivityTest { } @Test - public void testWorkTab_noWorkTargets_emptyStateShown() { + public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); @@ -1372,6 +1379,57 @@ public class ChooserActivityTest { .check(matches(isDisplayed())); } + @Test + public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + sOverrides.isQuietModeEnabled = true; + sOverrides.hasCrossProfileIntents = false; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_cant_share_with_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + sOverrides.isQuietModeEnabled = true; + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test")); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_no_work_apps_available_share)) + .check(matches(isDisplayed())); + } + private Intent createSendTextIntent() { Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java index 4ec89b7d2817..5a3aff937b79 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -598,7 +598,7 @@ public class ResolverActivityTest { onView(withId(R.id.contentPanel)) .perform(swipeUp()); - onView(withText(R.string.resolver_cant_share_with_work_apps)) + onView(withText(R.string.resolver_cant_access_work_apps)) .check(matches(isDisplayed())); } @@ -612,8 +612,15 @@ public class ResolverActivityTest { createResolvedComponentsForTestWithOtherProfile(3, /* userId */ 10); List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(workProfileTargets); + when(sOverrides.workResolverListController.getUserStateIndependentResolversAsUser( + Mockito.isA(List.class), + Mockito.isA(UserHandle.class))) + .thenReturn(new ArrayList<>(workResolvedComponentInfos)); sOverrides.isQuietModeEnabled = true; - setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + // When work profile is disabled, we get 0 results when we query the work profile + // intents. + setupResolverControllers(personalResolvedComponentInfos, + /* workResolvedComponentInfos */ new ArrayList<>()); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); @@ -629,7 +636,31 @@ public class ResolverActivityTest { } @Test - public void testWorkTab_noWorkTargets_emptyStateShown() { + public void testWorkTab_noWorkAppsAvailable_emptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_no_work_apps_available_resolve)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_xProfileOff_noAppsAvailable_workOff_xProfileOffEmptyStateShown() { // enable the work tab feature flag ResolverActivity.ENABLE_TABBED_VIEW = true; markWorkProfileUserAvailable(); @@ -640,6 +671,33 @@ public class ResolverActivityTest { setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); Intent sendIntent = createSendImageIntent(); sendIntent.setType("TestType"); + sOverrides.isQuietModeEnabled = true; + sOverrides.hasCrossProfileIntents = false; + + mActivityRule.launchActivity(sendIntent); + waitForIdle(); + onView(withId(R.id.contentPanel)) + .perform(swipeUp()); + onView(withText(R.string.resolver_work_tab)).perform(click()); + waitForIdle(); + + onView(withText(R.string.resolver_cant_access_work_apps)) + .check(matches(isDisplayed())); + } + + @Test + public void testWorkTab_noAppsAvailable_workOff_noAppsAvailableEmptyStateShown() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTest(3); + List<ResolvedComponentInfo> workResolvedComponentInfos = + createResolvedComponentsForTest(0); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createSendImageIntent(); + sendIntent.setType("TestType"); + sOverrides.isQuietModeEnabled = true; mActivityRule.launchActivity(sendIntent); waitForIdle(); |