Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 27608554 authored by arangelov's avatar arangelov Committed by Antoan Angelov
Browse files

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
parent 66041721
Loading
Loading
Loading
Loading
+60 −16
Original line number Diff line number Diff line
@@ -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()
+4 −4
Original line number Diff line number Diff line
@@ -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;
    }
+23 −3
Original line number Diff line number Diff line
@@ -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;
+60 −2
Original line number Diff line number Diff line
@@ -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);
+61 −3
Original line number Diff line number Diff line
@@ -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();