aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Trask <joshtrask@google.com>2023-12-08 20:25:14 +0000
committerJoshua Trask <joshtrask@google.com>2023-12-13 21:51:28 +0000
commit3007d9f481e92ed57ca9e3783719b3d84797ef2c (patch)
tree7edfc23366f90cdca5852209a6ac207b7de884a4
parent35db6fd65cd8364bc3d63e70532130048186456a (diff)
downloadIntentResolver-3007d9f481e92ed57ca9e3783719b3d84797ef2c.tar.gz
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
-rw-r--r--java/src/com/android/intentresolver/v2/MultiProfilePagerAdapter.java17
-rw-r--r--java/src/com/android/intentresolver/v2/ResolverActivity.java94
2 files changed, 83 insertions, 28 deletions
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 @@ public class MultiProfilePagerAdapter<
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 @@ public class MultiProfilePagerAdapter<
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 @@ public class ResolverActivity extends FragmentActivity implements
// 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 @@ public class ResolverActivity extends FragmentActivity implements
requireAnnotatedUserHandles().personalProfileUserHandle,
false
);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
mWorkPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getWorkListAdapter());
mWorkPackageMonitor.register(
@@ -1276,7 +1276,7 @@ public class ResolverActivity extends FragmentActivity implements
getMainLooper(),
requireAnnotatedUserHandles().personalProfileUserHandle,
false);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
if (mWorkPackageMonitor == null) {
mWorkPackageMonitor = createPackageMonitor(
mMultiProfilePagerAdapter.getWorkListAdapter());
@@ -1291,7 +1291,7 @@ public class ResolverActivity extends FragmentActivity implements
}
WorkProfileAvailabilityManager workProfileAvailabilityManager =
mLogic.getWorkProfileAvailabilityManager();
- if (shouldShowTabs() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) {
+ if (hasWorkProfile() && workProfileAvailabilityManager.isWaitingToEnableWorkProfile()) {
if (workProfileAvailabilityManager.isQuietModeEnabled()) {
workProfileAvailabilityManager.markWorkProfileEnabledBroadcastReceived();
}
@@ -1305,7 +1305,7 @@ public class ResolverActivity extends FragmentActivity implements
super.onStart();
this.getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
- if (shouldShowTabs()) {
+ if (hasWorkProfile()) {
mLogic.getWorkProfileAvailabilityManager().registerWorkProfileStateReceiver(this);
}
}
@@ -1510,6 +1510,7 @@ public class ResolverActivity extends FragmentActivity implements
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 @@ public class ResolverActivity extends FragmentActivity implements
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 @@ public class ResolverActivity extends FragmentActivity implements
});
}
+ 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 @@ public class ResolverActivity extends FragmentActivity implements
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 @@ public class ResolverActivity extends FragmentActivity implements
}
/**
- * 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;