Make "personal+work" conditions explicit
Historically sometimes we assume that "having tabs" means we have
these two particular tabs, or that the "inactive tab" is the work
profile when the personal profile is "active" and vice versa. These
assumptions won't hold in the future, so this CL switches such uses
to refer explicitly to the personal/work configuration they were
built for. There are no behavior changes included in this CL.
These changes are as prototyped in ag/25335069 and described in
go/chooser-ntab-refactoring, in particular the changes from "snapshot
10" to "snapshot 15" (skipping #14 which will depend on some other
changes in the series). See below for a "by-snapshot" breakdown of
the incremental changes composed in this CL.
Snapshot 1:
Switch "mini-resolver" to use explicit personal/work configurations.
The legacy `ResolverActivityTest.testMiniResolver` covers the basic
use case (e.g. failing if `shouldUseMiniResolver()` switches up which
tab is considered "active" vs. "inactive").
Snapshot 2:
Move remaining cross-profile autolaunch conditions over as guard
clauses in `maybeAutolaunchIfCrossProfileSupported()`. Now
that we have more explicit requirements about *which* profiles we're
talking about, it's easier to refer to them all in one place -- the
other partitioning was going to get a little clumsy. This change is
minimally tested, e.g. having the cross-profile "maybe autolaunch"
method always return true (->autolaunch) causes test failures in both
IntentResolver-tests-activity and the legacy ResolverActivityTests.
Snapshot 3:
Specify that cross-profile autolaunch only applies in the specific
"two-tab personal-and-work profiles" case. This doesn't change any
behavior for now, but makes it easy to adjust the legacy logic from
"active and inactive" tabs to "work and personal" tabs (in the next
snapshot). As in the previous snapshot this is minimally covered in
tests; in particular, inverting the "two-page configuration" condition
from this CL causes `ResolverActivityTest` to fail.
Snapshot 4:
Implement cross-profile autolaunch explicitly in terms of "personal"
and "work" tabs, so we don't have to refer to an "inactive tab."
Snapshot 5:
Fix a few places where `ResolverActivity` was relying on the
`shouldShowTabs()` condition when it really explicitly meant to refer
to (i.e. inline) the `hasWorkProfile()` check.
Bug: 310211468
Test: `ResolverActivityTest` & IntentResolver activity tests. Notes ^
Change-Id: I95e383e2822917198425acf9ba8bfbea76fdf948
diff --git a/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java
index f785c11..2d9be81 100644
--- a/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java
+++ b/java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java
@@ -204,6 +204,14 @@
return mCurrentPage;
}
+ public final @Profile int getActiveProfile() {
+ // TODO: here and elsewhere in this class, distinguish between a "profile ID" integer and
+ // its mapped "page index." When we support more than two profiles, this won't be a "stable
+ // mapping" -- some particular profile may not be represented by a "page," but the ones that
+ // are will be assigned contiguous page numbers that skip over the holes.
+ return getCurrentPage();
+ }
+
@VisibleForTesting
public UserHandle getCurrentUserHandle() {
return getActiveListAdapter().getUserHandle();
@@ -329,6 +337,15 @@
return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_PERSONAL));
}
+ /** @return whether our tab data contains a page for the specified {@code profile} ID. */
+ public final boolean hasPageForProfile(@Profile int profile) {
+ // TODO: here and elsewhere in this class, distinguish between a "profile ID" integer and
+ // its mapped "page index." When we support more than two profiles, this won't be a "stable
+ // mapping" -- some particular profile may not be represented by a "page," but the ones that
+ // are will be assigned contiguous page numbers that skip over the holes.
+ return hasAdapterForIndex(profile);
+ }
+
@Nullable
public final ListAdapterT getWorkListAdapter() {
if (!hasAdapterForIndex(PROFILE_WORK)) {
diff --git a/java/src/com/android/intentresolver/v2/ResolverActivity.java b/java/src/com/android/intentresolver/v2/ResolverActivity.java
index 3d08735..2ba50ec 100644
--- a/java/src/com/android/intentresolver/v2/ResolverActivity.java
+++ b/java/src/com/android/intentresolver/v2/ResolverActivity.java
@@ -308,7 +308,7 @@
// of the last used choice to highlight it in the list. We need to always
// turn this off when running under voice interaction, since it results in
// a more complicated UI that the current voice interaction flow is not able
- // to handle. We also turn it off when the work tab is shown to simplify the UX.
+ // to handle. We also turn it off when multiple tabs are shown to simplify the UX.
// We also turn it off when clonedProfile is present on the device, because we might have
// different "last chosen" activities in the different profiles, and PackageManager doesn't
// provide any more information to help us select between them.
@@ -332,7 +332,7 @@
requireAnnotatedUserHandles().personalProfileUserHandle,
false
);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
mWorkPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getWorkListAdapter());
mWorkPackageMonitor.register(
@@ -1276,7 +1276,7 @@
getMainLooper(),
requireAnnotatedUserHandles().personalProfileUserHandle,
false);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
if (mWorkPackageMonitor == null) {
mWorkPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getWorkListAdapter());
@@ -1291,7 +1291,7 @@
}
WorkProfileAvailabilityManager workProfileAvailabilityManager =
mLogic.getWorkProfileAvailabilityManager();
- if (shouldShowTabs() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) {
+ if (hasWorkProfile() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) {
if (workProfileAvailabilityManager.isQuietModeEnabled()) {
workProfileAvailabilityManager.markWorkProfileEnabledBroadcastReceived();
}
@@ -1305,7 +1305,7 @@
super.onStart();
this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
mLogic.getWorkProfileAvailabilityManager().registerWorkProfileStateReceiver(this);
}
}
@@ -1510,6 +1510,7 @@
Trace.beginSection("configureContentView");
// We partially rebuild the inactive adapter to determine if we should auto launch
// isTabLoaded will be true here if the empty state screen is shown instead of the list.
+ // To date, we really only care about "partially rebuilding" tabs for work and/or personal.
boolean rebuildCompleted = mMultiProfilePagerAdapter.rebuildTabs(shouldShowTabs());
if (shouldUseMiniResolver()) {
@@ -1540,12 +1541,25 @@
mLayoutId = R.layout.miniresolver;
setContentView(mLayoutId);
- DisplayResolveInfo sameProfileResolveInfo =
- mMultiProfilePagerAdapter.getActiveListAdapter().getFirstDisplayResolveInfo();
+ // TODO: try to dedupe and use the pager's `getActiveProfile()` instead of the activity
+ // `getCurrentProfile()` (or align them if they're not currently equivalent). If they truly
+ // need to be distinct here, then `getCurrentProfile()` should at *least* get a more
+ // specific name -- but note that checking `getCurrentProfile()` here, then following
+ // `getActiveProfile()` to find the "in/active adapter," is exactly the legacy behavior.
boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK;
- final ResolverListAdapter inactiveAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
+ ResolverListAdapter sameProfileAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
+ ResolverListAdapter inactiveAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
+
+ DisplayResolveInfo sameProfileResolveInfo = sameProfileAdapter.getFirstDisplayResolveInfo();
+
final DisplayResolveInfo otherProfileResolveInfo =
inactiveAdapter.getFirstDisplayResolveInfo();
@@ -1584,24 +1598,36 @@
});
}
+ private boolean isTwoPagePersonalAndWorkConfiguration() {
+ return (mMultiProfilePagerAdapter.getCount() == 2)
+ && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_PERSONAL)
+ && mMultiProfilePagerAdapter.hasPageForProfile(PROFILE_WORK);
+ }
+
/**
* Mini resolver should be used when all of the following are true:
* 1. This is the intent picker (ResolverActivity).
- * 2. This profile only has web browser matches.
- * 3. The other profile has a single non-browser match.
+ * 2. There are exactly two tabs, for the "personal" and "work" profiles.
+ * 3. This profile only has web browser matches.
+ * 4. The other profile has a single non-browser match.
*/
private boolean shouldUseMiniResolver() {
if (!mIsIntentPicker) {
return false;
}
- if (mMultiProfilePagerAdapter.getActiveListAdapter() == null
- || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
return false;
}
+
ResolverListAdapter sameProfileAdapter =
- mMultiProfilePagerAdapter.getActiveListAdapter();
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
ResolverListAdapter otherProfileAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) {
Log.d(TAG, "No targets in the current profile");
@@ -1661,10 +1687,7 @@
int numberOfProfiles = mMultiProfilePagerAdapter.getItemCount();
if (numberOfProfiles == 1 && maybeAutolaunchIfSingleTarget()) {
return true;
- } else if (numberOfProfiles == 2
- && mMultiProfilePagerAdapter.getActiveListAdapter().isTabLoaded()
- && mMultiProfilePagerAdapter.getInactiveListAdapter().isTabLoaded()
- && maybeAutolaunchIfCrossProfileSupported()) {
+ } else if (maybeAutolaunchIfCrossProfileSupported()) {
// TODO(b/280988288): If the ChooserActivity is shown we should consider showing the
// correct intent-picker UIs (e.g., mini-resolver) if it was launched without
// ACTION_SEND.
@@ -1695,33 +1718,48 @@
}
/**
- * When we have a personal and a work profile, we auto launch in the following scenario:
+ * When we have just a personal and a work profile, we auto launch in the following scenario:
* - There is 1 resolved target on each profile
* - That target is the same app on both profiles
* - The target app has permission to communicate cross profiles
* - The target app has declared it supports cross-profile communication via manifest metadata
*/
private boolean maybeAutolaunchIfCrossProfileSupported() {
- ResolverListAdapter activeListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
- int count = activeListAdapter.getUnfilteredCount();
- if (count != 1) {
+ if (!isTwoPagePersonalAndWorkConfiguration()) {
return false;
}
+
+ ResolverListAdapter activeListAdapter =
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getPersonalListAdapter()
+ : mMultiProfilePagerAdapter.getWorkListAdapter();
+
ResolverListAdapter inactiveListAdapter =
- mMultiProfilePagerAdapter.getInactiveListAdapter();
- if (inactiveListAdapter.getUnfilteredCount() != 1) {
+ (mMultiProfilePagerAdapter.getActiveProfile() == PROFILE_PERSONAL)
+ ? mMultiProfilePagerAdapter.getWorkListAdapter()
+ : mMultiProfilePagerAdapter.getPersonalListAdapter();
+
+ if (!activeListAdapter.isTabLoaded() || !inactiveListAdapter.isTabLoaded()) {
return false;
}
- TargetInfo activeProfileTarget = activeListAdapter
- .targetInfoForPosition(0, false);
+
+ if ((activeListAdapter.getUnfilteredCount() != 1)
+ || (inactiveListAdapter.getUnfilteredCount() != 1)) {
+ return false;
+ }
+
+ TargetInfo activeProfileTarget = activeListAdapter.targetInfoForPosition(0, false);
TargetInfo inactiveProfileTarget = inactiveListAdapter.targetInfoForPosition(0, false);
- if (!Objects.equals(activeProfileTarget.getResolvedComponentName(),
+ if (!Objects.equals(
+ activeProfileTarget.getResolvedComponentName(),
inactiveProfileTarget.getResolvedComponentName())) {
return false;
}
+
if (!shouldAutoLaunchSingleChoice(activeProfileTarget)) {
return false;
}
+
String packageName = activeProfileTarget.getResolvedComponentName().getPackageName();
if (!canAppInteractCrossProfiles(packageName)) {
return false;