diff options
4 files changed, 149 insertions, 32 deletions
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java index b81e47303a9d..b1e356d258ee 100644 --- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java @@ -300,30 +300,26 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } private boolean rebuildTab(ResolverListAdapter activeListAdapter, boolean doPostProcessing) { - UserHandle listUserHandle = activeListAdapter.getUserHandle(); - - if (UserHandle.myUserId() != listUserHandle.getIdentifier()) { - if (!mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), - UserHandle.myUserId(), listUserHandle.getIdentifier())) { - if (listUserHandle.equals(mPersonalProfileUserHandle)) { - DevicePolicyEventLogger.createEvent( - DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL) - .setStrings(getMetricsCategory()) - .write(); - showNoWorkToPersonalIntentsEmptyState(activeListAdapter); - } else { - DevicePolicyEventLogger.createEvent( - DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK) - .setStrings(getMetricsCategory()) - .write(); - showNoPersonalToWorkIntentsEmptyState(activeListAdapter); - } - return false; - } + if (shouldShowNoCrossProfileIntentsEmptyState(activeListAdapter)) { + activeListAdapter.postListReadyRunnable(doPostProcessing); + return false; } return activeListAdapter.rebuildList(doPostProcessing); } + private boolean shouldShowNoCrossProfileIntentsEmptyState( + ResolverListAdapter activeListAdapter) { + UserHandle listUserHandle = activeListAdapter.getUserHandle(); + return UserHandle.myUserId() != listUserHandle.getIdentifier() + && allowShowNoCrossProfileIntentsEmptyState() + && !mInjector.hasCrossProfileIntents(activeListAdapter.getIntents(), + UserHandle.myUserId(), listUserHandle.getIdentifier()); + } + + boolean allowShowNoCrossProfileIntentsEmptyState() { + return true; + } + protected abstract void showWorkProfileOffEmptyState( ResolverListAdapter activeListAdapter, View.OnClickListener listener); @@ -353,12 +349,35 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { * anyway. */ void showEmptyResolverListEmptyState(ResolverListAdapter listAdapter) { + if (maybeShowNoCrossProfileIntentsEmptyState(listAdapter)) { + return; + } if (maybeShowWorkProfileOffEmptyState(listAdapter)) { return; } maybeShowNoAppsAvailableEmptyState(listAdapter); } + private boolean maybeShowNoCrossProfileIntentsEmptyState(ResolverListAdapter listAdapter) { + if (!shouldShowNoCrossProfileIntentsEmptyState(listAdapter)) { + return false; + } + if (listAdapter.getUserHandle().equals(mPersonalProfileUserHandle)) { + DevicePolicyEventLogger.createEvent( + DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_PERSONAL) + .setStrings(getMetricsCategory()) + .write(); + showNoWorkToPersonalIntentsEmptyState(listAdapter); + } else { + DevicePolicyEventLogger.createEvent( + DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK) + .setStrings(getMetricsCategory()) + .write(); + showNoPersonalToWorkIntentsEmptyState(listAdapter); + } + return true; + } + /** * Returns {@code true} if the work profile off empty state screen is shown. */ diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index dd3a6603f46a..1bc982cdb42b 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -179,6 +179,7 @@ public class ResolverActivity extends Activity implements public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device"; private BroadcastReceiver mWorkProfileStateReceiver; + private boolean mIsHeaderCreated; /** * Get the string resource to be used as a label for the link to the resolver activity for an @@ -479,13 +480,42 @@ public class ResolverActivity extends Activity implements == workProfileUserHandle.getIdentifier()), mUseLayoutForBrowsables, /* userHandle */ workProfileUserHandle); + // In the edge case when we have 0 apps in the current profile and >1 apps in the other, + // the intent resolver is started in the other profile. Since this is the only case when + // this happens, we check for it here and set the current profile's tab. + int selectedProfile = getCurrentProfile(); + UserHandle intentUser = UserHandle.of(getLaunchingUserId()); + if (!getUser().equals(intentUser)) { + if (getPersonalProfileUserHandle().equals(intentUser)) { + selectedProfile = PROFILE_PERSONAL; + } else if (getWorkProfileUserHandle().equals(intentUser)) { + selectedProfile = PROFILE_WORK; + } + } return new ResolverMultiProfilePagerAdapter( /* context */ this, personalAdapter, workAdapter, - /* defaultProfile */ getCurrentProfile(), + selectedProfile, getPersonalProfileUserHandle(), - getWorkProfileUserHandle()); + getWorkProfileUserHandle(), + /* shouldShowNoCrossProfileIntentsEmptyState= */ getUser().equals(intentUser)); + } + + /** + * Returns the user id of the user that the starting intent originated from. + * <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()}, + * as there are edge cases when the intent resolver is launched in the other profile. + * For example, when we have 0 resolved apps in current profile and multiple resolved apps + * in the other profile, opening a link from the current profile launches the intent resolver + * in the other one. b/148536209 for more info. + */ + private int getLaunchingUserId() { + int contentUserHint = getIntent().getContentUserHint(); + if (contentUserHint == UserHandle.USER_CURRENT) { + return UserHandle.myUserId(); + } + return contentUserHint; } protected @Profile int getCurrentProfile() { @@ -856,7 +886,7 @@ public class ResolverActivity extends Activity implements private void setAlwaysButtonEnabled(boolean hasValidSelection, int checkedPos, boolean filtered) { - if (mMultiProfilePagerAdapter.getCurrentUserHandle() != getUser()) { + if (!mMultiProfilePagerAdapter.getCurrentUserHandle().equals(getUser())) { // Never allow the inactive profile to always open an app. mAlwaysButton.setEnabled(false); return; @@ -995,10 +1025,7 @@ public class ResolverActivity extends Activity implements mMultiProfilePagerAdapter.showListView(listAdapter); } if (doPostProcessing) { - if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier() - == UserHandle.myUserId()) { - setHeader(); - } + maybeCreateHeader(listAdapter); resetButtonBar(); onListRebuilt(listAdapter); } @@ -1679,10 +1706,15 @@ public class ResolverActivity extends Activity implements /** * Configure the area above the app selection list (title, content preview, etc). + * <p>The header is created once when first launching the activity and whenever a package is + * installed or uninstalled. */ - public void setHeader() { - if (mMultiProfilePagerAdapter.getActiveListAdapter().getCount() == 0 - && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) { + private void maybeCreateHeader(ResolverListAdapter listAdapter) { + if (mIsHeaderCreated) { + return; + } + if (!shouldShowTabs() + && listAdapter.getCount() == 0 && listAdapter.getPlaceholderCount() == 0) { final TextView titleView = findViewById(R.id.title); if (titleView != null) { titleView.setVisibility(View.GONE); @@ -1703,8 +1735,9 @@ public class ResolverActivity extends Activity implements final ImageView iconView = findViewById(R.id.icon); if (iconView != null) { - mMultiProfilePagerAdapter.getActiveListAdapter().loadFilteredItemIconTaskAsync(iconView); + listAdapter.loadFilteredItemIconTaskAsync(iconView); } + mIsHeaderCreated = true; } protected void resetButtonBar() { @@ -1804,6 +1837,7 @@ public class ResolverActivity extends Activity implements // turning on. return; } + mIsHeaderCreated = false; boolean listRebuilt = mMultiProfilePagerAdapter.rebuildActiveTab(true); if (listRebuilt) { ResolverListAdapter activeListAdapter = diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java index b690a18f2d0e..ad31d8b2e49a 100644 --- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java +++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java @@ -35,6 +35,7 @@ import com.android.internal.widget.PagerAdapter; public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerAdapter { private final ResolverProfileDescriptor[] mItems; + private final boolean mShouldShowNoCrossProfileIntentsEmptyState; ResolverMultiProfilePagerAdapter(Context context, ResolverListAdapter adapter, @@ -44,6 +45,7 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(adapter) }; + mShouldShowNoCrossProfileIntentsEmptyState = true; } ResolverMultiProfilePagerAdapter(Context context, @@ -51,13 +53,15 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA ResolverListAdapter workAdapter, @Profile int defaultProfile, UserHandle personalProfileUserHandle, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + boolean shouldShowNoCrossProfileIntentsEmptyState) { super(context, /* currentPage */ defaultProfile, personalProfileUserHandle, workProfileUserHandle); mItems = new ResolverProfileDescriptor[] { createProfileDescriptor(personalAdapter), createProfileDescriptor(workAdapter) }; + mShouldShowNoCrossProfileIntentsEmptyState = shouldShowNoCrossProfileIntentsEmptyState; } private ResolverProfileDescriptor createProfileDescriptor( @@ -163,6 +167,11 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA } @Override + boolean allowShowNoCrossProfileIntentsEmptyState() { + return mShouldShowNoCrossProfileIntentsEmptyState; + } + + @Override protected void showWorkProfileOffEmptyState(ResolverListAdapter activeListAdapter, View.OnClickListener listener) { showEmptyState(activeListAdapter, 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 eb39d58019d9..07aa654cae1e 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -38,13 +38,16 @@ import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +import static org.testng.Assert.assertFalse; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.UserHandle; import android.text.TextUtils; import android.view.View; import android.widget.RelativeLayout; +import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.espresso.Espresso; @@ -543,6 +546,51 @@ public class ResolverActivityTest { assertThat(activity.getWorkListAdapter().getCount(), is(4)); } + @Test + public void testWorkTab_headerIsVisibleInPersonalTab() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(1); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createOpenWebsiteIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + TextView headerText = activity.findViewById(R.id.title); + String initialText = headerText.getText().toString(); + assertFalse(initialText.isEmpty(), "Header text is empty."); + assertThat(headerText.getVisibility(), is(View.VISIBLE)); + } + + @Test + public void testWorkTab_switchTabs_headerStaysSame() { + // enable the work tab feature flag + ResolverActivity.ENABLE_TABBED_VIEW = true; + markWorkProfileUserAvailable(); + List<ResolvedComponentInfo> personalResolvedComponentInfos = + createResolvedComponentsForTestWithOtherProfile(1); + List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4); + setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos); + Intent sendIntent = createOpenWebsiteIntent(); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + TextView headerText = activity.findViewById(R.id.title); + String initialText = headerText.getText().toString(); + onView(withText(R.string.resolver_work_tab)) + .perform(click()); + + waitForIdle(); + String currentText = headerText.getText().toString(); + assertThat(headerText.getVisibility(), is(View.VISIBLE)); + assertThat(String.format("Header text is not the same when switching tabs, personal profile" + + " header was %s but work profile header is %s", initialText, currentText), + TextUtils.equals(initialText, currentText)); + } + @Ignore // b/148156663 @Test public void testWorkTab_noPersonalApps_canStartWorkApps() @@ -761,6 +809,13 @@ public class ResolverActivityTest { return sendIntent; } + private Intent createOpenWebsiteIntent() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_VIEW); + sendIntent.setData(Uri.parse("https://google.com")); + return sendIntent; + } + private List<ResolvedComponentInfo> createResolvedComponentsForTest(int numberOfResults) { List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { |