Include cloned apps in system sharesheet.
This CL is the first of multiple Cls which integrate Cloned Apps
with the system sharesheet.
Contents:
1. Adding API to get UserHandle from ResolveInfo.
2. Adding userHandle to uniquely identify ResolveInfo in sharesheet.
3. Loading Icons with userHandle supplied from 1.
4. Personal Adaptor starts with Cloned User Id when sharesheet is
started in Cloned App.
5. AzInfoComparator adds userHandle to sort apps.
6. Different Disambiguation dialog for last choosen activity is
disabled, when clonedProfile is present.
7. Setting 'Always' choosen activity is disabled personal adapter when
clonedProfile is present.
8. Profile based icons added in Dialog Boxes. (Stacked apps and Long
press)
Bug: 240100378
Test: atest ResolverActivityTest
Change-Id: I5eb288e4f3059f0945a8c3ff410b19b8820a69cf
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 2316738..eef6fde 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -16,8 +16,8 @@
package com.android.internal.app;
import android.annotation.IntDef;
-import android.annotation.Nullable;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.AppGlobals;
import android.content.ContentResolver;
@@ -60,16 +60,19 @@
private Set<Integer> mLoadedPages;
private final EmptyStateProvider mEmptyStateProvider;
private final UserHandle mWorkProfileUserHandle;
+ private final UserHandle mCloneUserHandle;
private final QuietModeManager mQuietModeManager;
AbstractMultiProfilePagerAdapter(Context context, int currentPage,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
mLoadedPages = new HashSet<>();
mWorkProfileUserHandle = workProfileUserHandle;
+ mCloneUserHandle = cloneUserHandle;
mEmptyStateProvider = emptyStateProvider;
mQuietModeManager = quietModeManager;
}
@@ -160,6 +163,10 @@
return null;
}
+ public UserHandle getCloneUserHandle() {
+ return mCloneUserHandle;
+ }
+
/**
* Returns the {@link ProfileDescriptor} relevant to the given <code>pageIndex</code>.
* <ul>
@@ -318,18 +325,6 @@
}
/**
- * Class to get user id of the current process
- */
- public static class MyUserIdProvider {
- /**
- * @return user id of the current process
- */
- public int getMyUserId() {
- return UserHandle.myUserId();
- }
- }
-
- /**
* Utility class to check if there are cross profile intents, it is in a separate class so
* it could be mocked in tests
*/
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 9f283d4..791819b 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -873,7 +873,7 @@
return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch());
}
private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile(
@@ -886,13 +886,14 @@
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ /* userHandle */ getPersonalProfileUserHandle());
return new ChooserMultiProfilePagerAdapter(
/* context */ this,
adapter,
createEmptyStateProvider(/* workProfileUserHandle= */ null),
mQuietModeManager,
/* workProfileUserHandle= */ null,
+ getCloneProfileUserHandle(),
mMaxTargetsPerRow);
}
@@ -923,13 +924,14 @@
mQuietModeManager,
selectedProfile,
getWorkProfileUserHandle(),
+ getCloneProfileUserHandle(),
mMaxTargetsPerRow);
}
private int findSelectedProfile() {
int selectedProfile = getSelectedProfileExtra();
if (selectedProfile == -1) {
- selectedProfile = getProfileForUser(getUser());
+ selectedProfile = getProfileForUser(getTabOwnerUserHandleForLaunch());
}
return selectedProfile;
}
@@ -1800,8 +1802,12 @@
targetList = new ArrayList<DisplayResolveInfo>();
targetList.add((DisplayResolveInfo) targetInfo);
}
+ // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be
+ // resolved correctly.
bundle.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+ getResolveInfoUserHandle(
+ targetInfo.getResolveInfo(),
+ mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
bundle.putParcelableArrayList(ChooserTargetActionsDialogFragment.TARGET_INFOS_KEY,
targetList);
fragment.setArguments(bundle);
@@ -1865,8 +1871,11 @@
if (!mti.hasSelected()) {
ChooserStackedAppDialogFragment f = new ChooserStackedAppDialogFragment();
Bundle b = new Bundle();
+ // Add userHandle based badge to the stackedAppDialogBox.
b.putParcelable(ChooserTargetActionsDialogFragment.USER_HANDLE_KEY,
- mChooserMultiProfilePagerAdapter.getCurrentUserHandle());
+ getResolveInfoUserHandle(
+ targetInfo.getResolveInfo(),
+ mChooserMultiProfilePagerAdapter.getCurrentUserHandle()));
b.putObject(ChooserStackedAppDialogFragment.MULTI_DRI_KEY,
mti);
b.putInt(ChooserStackedAppDialogFragment.WHICH_KEY, which);
@@ -2433,15 +2442,24 @@
* Sort intents alphabetically based on display label.
*/
static class AzInfoComparator implements Comparator<DisplayResolveInfo> {
- Collator mCollator;
+ Comparator<DisplayResolveInfo> mComparator;
AzInfoComparator(Context context) {
- mCollator = Collator.getInstance(context.getResources().getConfiguration().locale);
+ Collator collator = Collator
+ .getInstance(context.getResources().getConfiguration().locale);
+ // Adding two stage comparator, first stage compares using displayLabel, next stage
+ // compares using resolveInfo.userHandle
+ mComparator = Comparator.comparing(DisplayResolveInfo::getDisplayLabel, collator)
+ .thenComparingInt(displayResolveInfo ->
+ getResolveInfoUserHandle(
+ displayResolveInfo.getResolveInfo(),
+ // TODO: User resolveInfo.userHandle, once its available.
+ UserHandle.SYSTEM).getIdentifier());
}
@Override
public int compare(
DisplayResolveInfo lhsp, DisplayResolveInfo rhsp) {
- return mCollator.compare(lhsp.getDisplayLabel(), rhsp.getDisplayLabel());
+ return mComparator.compare(lhsp, rhsp);
}
}
@@ -2466,9 +2484,10 @@
String referrerPackageName,
int launchedFromUid,
UserHandle userId,
- AbstractResolverComparator resolverComparator) {
+ AbstractResolverComparator resolverComparator,
+ UserHandle queryIntentsAsUser) {
super(context, pm, targetIntent, referrerPackageName, launchedFromUid, userId,
- resolverComparator);
+ resolverComparator, queryIntentsAsUser);
}
@Override
@@ -2533,6 +2552,7 @@
getReferrerPackageName(), null, getChooserActivityLogger());
}
+ UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
return new ChooserListController(
this,
mPm,
@@ -2540,7 +2560,8 @@
getReferrerPackageName(),
mLaunchedFromUid,
userHandle,
- resolverComparator);
+ resolverComparator,
+ queryIntentsUser == null ? userHandle : queryIntentsUser);
}
@VisibleForTesting
@@ -2741,17 +2762,16 @@
}
/**
- * Returns {@link #PROFILE_PERSONAL}, {@link #PROFILE_WORK}, or -1 if the given user handle
- * does not match either the personal or work user handle.
+ * Returns {@link #PROFILE_WORK}, if the given user handle matches work user handle.
+ * Returns {@link #PROFILE_PERSONAL}, otherwise.
**/
private int getProfileForUser(UserHandle currentUserHandle) {
- if (currentUserHandle.equals(getPersonalProfileUserHandle())) {
- return PROFILE_PERSONAL;
- } else if (currentUserHandle.equals(getWorkProfileUserHandle())) {
+ if (currentUserHandle.equals(getWorkProfileUserHandle())) {
return PROFILE_WORK;
}
- Log.e(TAG, "User " + currentUserHandle + " does not belong to a personal or work profile.");
- return -1;
+ // We return personal profile, as it is the default when there is no work profile, personal
+ // profile represents rootUser, clonedUser & secondaryUser, covering all use cases.
+ return PROFILE_PERSONAL;
}
private ViewGroup getActiveEmptyStateView() {
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index 2ae2c09..1f22531 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -197,6 +197,8 @@
ri.nonLocalizedLabel = li.getNonLocalizedLabel();
ri.icon = li.getIconResource();
ri.iconResourceId = ri.icon;
+ // TODO: Uncomment the below line once userHandle is added to ResolveInfo
+ //ri.userHandle = getUserHandle();
}
if (userManager.isManagedProfile()) {
ri.noResourceId = true;
@@ -351,7 +353,9 @@
Map<String, DisplayResolveInfo> consolidated = new HashMap<>();
for (DisplayResolveInfo info : allTargets) {
String resolvedTarget = info.getResolvedComponentName().getPackageName()
- + '#' + info.getDisplayLabel();
+ + '#' + info.getDisplayLabel()
+ + '#' + ResolverActivity.getResolveInfoUserHandle(
+ info.getResolveInfo(), getUserHandle()).getIdentifier();
DisplayResolveInfo multiDri = consolidated.get(resolvedTarget);
if (multiDri == null) {
consolidated.put(resolvedTarget, info);
@@ -367,7 +371,8 @@
}
List<DisplayResolveInfo> groupedTargets = new ArrayList<>();
groupedTargets.addAll(consolidated.values());
- Collections.sort(groupedTargets, new ChooserActivity.AzInfoComparator(mContext));
+ Collections.sort(groupedTargets,
+ new ChooserActivity.AzInfoComparator(mContext));
return groupedTargets;
}
@Override
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 0509b67..f56f818 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -45,9 +45,10 @@
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle,
int maxTargetsPerRow) {
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(adapter)
};
@@ -61,9 +62,10 @@
QuietModeManager quietModeManager,
@Profile int defaultProfile,
UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle,
int maxTargetsPerRow) {
super(context, /* currentPage */ defaultProfile, emptyStateProvider,
- quietModeManager, workProfileUserHandle);
+ quietModeManager, workProfileUserHandle, cloneUserHandle);
mItems = new ChooserProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
@@ -110,11 +112,12 @@
@Override
@Nullable
ChooserListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
- if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
- return getActiveListAdapter();
- } else if (getInactiveListAdapter() != null
- && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
- return getInactiveListAdapter();
+ if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+ || userHandle.equals(getCloneUserHandle())) {
+ return getPersonalListAdapter();
+ } else if (getWorkListAdapter() != null
+ && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+ return getWorkListAdapter();
}
return null;
}
@@ -153,13 +156,13 @@
}
@Override
- public ResolverListAdapter getPersonalListAdapter() {
+ public ChooserListAdapter getPersonalListAdapter() {
return getAdapterForIndex(PROFILE_PERSONAL).getListAdapter();
}
@Override
@Nullable
- public ResolverListAdapter getWorkListAdapter() {
+ public ChooserListAdapter getWorkListAdapter() {
return getAdapterForIndex(PROFILE_WORK).getListAdapter();
}
diff --git a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
index 34249f2..747780b 100644
--- a/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoAppsAvailableEmptyStateProvider.java
@@ -28,10 +28,9 @@
import android.os.UserHandle;
import android.stats.devicepolicy.nano.DevicePolicyEnums;
+import com.android.internal.R;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
-import com.android.internal.R;
import java.util.List;
@@ -50,16 +49,16 @@
@NonNull
private final String mMetricsCategory;
@NonNull
- private final MyUserIdProvider mMyUserIdProvider;
+ private final UserHandle mTabOwnerUserHandleForLaunch;
public NoAppsAvailableEmptyStateProvider(Context context, UserHandle workProfileUserHandle,
UserHandle personalProfileUserHandle, String metricsCategory,
- MyUserIdProvider myUserIdProvider) {
+ UserHandle tabOwnerUserHandleForLaunch) {
mContext = context;
mWorkProfileUserHandle = workProfileUserHandle;
mPersonalProfileUserHandle = personalProfileUserHandle;
mMetricsCategory = metricsCategory;
- mMyUserIdProvider = myUserIdProvider;
+ mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch;
}
@Nullable
@@ -69,7 +68,7 @@
UserHandle listUserHandle = resolverListAdapter.getUserHandle();
if (mWorkProfileUserHandle != null
- && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier()
+ && (mTabOwnerUserHandleForLaunch.equals(listUserHandle)
|| !hasAppsInOtherProfile(resolverListAdapter))) {
String title;
@@ -102,7 +101,7 @@
return false;
}
List<ResolverActivity.ResolvedComponentInfo> resolversForIntent =
- adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId()));
+ adapter.getResolversForUser(mTabOwnerUserHandleForLaunch);
for (ResolverActivity.ResolvedComponentInfo info : resolversForIntent) {
ResolveInfo resolveInfo = info.getResolveInfoAt(0);
if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) {
diff --git a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
index 2e7d5bf..2046bfc 100644
--- a/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
+++ b/core/java/com/android/internal/app/NoCrossProfileEmptyStateProvider.java
@@ -27,7 +27,6 @@
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyState;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
/**
* Empty state provider that does not allow cross profile sharing, it will return a blocker
@@ -39,28 +38,28 @@
private final EmptyState mNoWorkToPersonalEmptyState;
private final EmptyState mNoPersonalToWorkEmptyState;
private final CrossProfileIntentsChecker mCrossProfileIntentsChecker;
- private final MyUserIdProvider mUserIdProvider;
+ private final UserHandle mTabOwnerUserHandleForLaunch;
public NoCrossProfileEmptyStateProvider(UserHandle personalUserHandle,
EmptyState noWorkToPersonalEmptyState,
EmptyState noPersonalToWorkEmptyState,
CrossProfileIntentsChecker crossProfileIntentsChecker,
- MyUserIdProvider myUserIdProvider) {
+ UserHandle preselectedTabOwnerUserHandle) {
mPersonalProfileUserHandle = personalUserHandle;
mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState;
mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState;
mCrossProfileIntentsChecker = crossProfileIntentsChecker;
- mUserIdProvider = myUserIdProvider;
+ mTabOwnerUserHandleForLaunch = preselectedTabOwnerUserHandle;
}
@Nullable
@Override
public EmptyState getEmptyState(ResolverListAdapter resolverListAdapter) {
boolean shouldShowBlocker =
- mUserIdProvider.getMyUserId() != resolverListAdapter.getUserHandle().getIdentifier()
+ !mTabOwnerUserHandleForLaunch.equals(resolverListAdapter.getUserHandle())
&& !mCrossProfileIntentsChecker
.hasCrossProfileIntents(resolverListAdapter.getIntents(),
- mUserIdProvider.getMyUserId(),
+ mTabOwnerUserHandleForLaunch.getIdentifier(),
resolverListAdapter.getUserHandle().getIdentifier());
if (!shouldShowBlocker) {
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index f098e2c..3850347 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -102,7 +102,6 @@
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CompositeEmptyStateProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.EmptyStateProvider;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.OnSwitchOnWorkSelectedListener;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.Profile;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
@@ -166,6 +165,7 @@
@UnsupportedAppUsage
protected PackageManager mPm;
protected int mLaunchedFromUid;
+ private UserHandle mLaunchedFromUserHandle;
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
@@ -229,12 +229,15 @@
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
-
+ private UserHandle mPersonalProfileUserHandle;
private UserHandle mWorkProfileUserHandle;
@Nullable
private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener;
+ private UserHandle mCloneProfileUserHandle;
+ private UserHandle mTabOwnerUserHandleForLaunch;
+
protected final LatencyTracker mLatencyTracker = getLatencyTracker();
private LatencyTracker getLatencyTracker() {
@@ -400,6 +403,7 @@
setProfileSwitchMessage(intent.getContentUserHint());
mLaunchedFromUid = getLaunchedFromUid();
+ mLaunchedFromUserHandle = UserHandle.getUserHandleForUid(mLaunchedFromUid);
if (mLaunchedFromUid < 0 || UserHandle.isIsolated(mLaunchedFromUid)) {
// Gulp!
finish();
@@ -416,15 +420,21 @@
mDefaultTitleResId = defaultTitleRes;
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
+ mPersonalProfileUserHandle = fetchPersonalProfileUserHandle();
mWorkProfileUserHandle = fetchWorkProfileUserProfile();
+ mCloneProfileUserHandle = fetchCloneProfileUserHandle();
+ mTabOwnerUserHandleForLaunch = fetchTabOwnerUserHandleForLaunch();
// The last argument of createResolverListAdapter is whether to do special handling
// 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.
+ // 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.
boolean filterLastUsed = mSupportsAlwaysUseOption && !isVoiceInteraction()
- && !shouldShowTabs();
+ && !shouldShowTabs() && !hasCloneProfile();
mMultiProfilePagerAdapter = createMultiProfilePagerAdapter(initialIntents, rList, filterLastUsed);
if (configureContentView()) {
return;
@@ -496,11 +506,6 @@
}
@VisibleForTesting
- protected MyUserIdProvider createMyUserIdProvider() {
- return new MyUserIdProvider();
- }
-
- @VisibleForTesting
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
return new CrossProfileIntentsChecker(getContentResolver());
}
@@ -566,9 +571,12 @@
/* devicePolicyEventId= */ RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK,
/* devicePolicyEventCategory= */ ResolverActivity.METRICS_CATEGORY_RESOLVER);
- return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(),
- noWorkToPersonalEmptyState, noPersonalToWorkEmptyState,
- createCrossProfileIntentsChecker(), createMyUserIdProvider());
+ return new NoCrossProfileEmptyStateProvider(
+ getPersonalProfileUserHandle(),
+ noWorkToPersonalEmptyState,
+ noPersonalToWorkEmptyState,
+ createCrossProfileIntentsChecker(),
+ getTabOwnerUserHandleForLaunch());
}
protected EmptyStateProvider createEmptyStateProvider(
@@ -589,7 +597,7 @@
workProfileUserHandle,
getPersonalProfileUserHandle(),
getMetricsCategory(),
- createMyUserIdProvider()
+ getTabOwnerUserHandleForLaunch()
);
// Return composite provider, the order matters (the higher, the more priority)
@@ -609,14 +617,15 @@
initialIntents,
rList,
filterLastUsed,
- /* userHandle */ UserHandle.of(UserHandle.myUserId()));
+ /* userHandle */ getPersonalProfileUserHandle());
QuietModeManager quietModeManager = createQuietModeManager();
return new ResolverMultiProfilePagerAdapter(
/* context */ this,
adapter,
createEmptyStateProvider(/* workProfileUserHandle= */ null),
quietModeManager,
- /* workProfileUserHandle= */ null);
+ /* workProfileUserHandle= */ null,
+ getCloneProfileUserHandle());
}
private UserHandle getIntentUser() {
@@ -634,7 +643,7 @@
// this happens, we check for it here and set the current profile's tab.
int selectedProfile = getCurrentProfile();
UserHandle intentUser = getIntentUser();
- if (!getUser().equals(intentUser)) {
+ if (!getTabOwnerUserHandleForLaunch().equals(intentUser)) {
if (getPersonalProfileUserHandle().equals(intentUser)) {
selectedProfile = PROFILE_PERSONAL;
} else if (getWorkProfileUserHandle().equals(intentUser)) {
@@ -674,7 +683,8 @@
createEmptyStateProvider(getWorkProfileUserHandle()),
quietModeManager,
selectedProfile,
- getWorkProfileUserHandle());
+ getWorkProfileUserHandle(),
+ getCloneProfileUserHandle());
}
protected int appliedThemeResId() {
@@ -701,20 +711,35 @@
}
protected @Profile int getCurrentProfile() {
- return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK);
+ return (UserHandle.myUserId() == getPersonalProfileUserHandle().getIdentifier()
+ ? PROFILE_PERSONAL : PROFILE_WORK);
}
protected UserHandle getPersonalProfileUserHandle() {
- return UserHandle.of(ActivityManager.getCurrentUser());
+ return mPersonalProfileUserHandle;
}
protected @Nullable UserHandle getWorkProfileUserHandle() {
return mWorkProfileUserHandle;
}
+ protected @Nullable UserHandle getCloneProfileUserHandle() {
+ return mCloneProfileUserHandle;
+ }
+
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ return mTabOwnerUserHandleForLaunch;
+ }
+
+ protected UserHandle fetchPersonalProfileUserHandle() {
+ mPersonalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser());
+ return mPersonalProfileUserHandle;
+ }
+
protected @Nullable UserHandle fetchWorkProfileUserProfile() {
mWorkProfileUserHandle = null;
UserManager userManager = getSystemService(UserManager.class);
- for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) {
+ for (final UserInfo userInfo : userManager
+ .getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
if (userInfo.isManagedProfile()) {
mWorkProfileUserHandle = userInfo.getUserHandle();
}
@@ -722,10 +747,38 @@
return mWorkProfileUserHandle;
}
+ protected @Nullable UserHandle fetchCloneProfileUserHandle() {
+ mCloneProfileUserHandle = null;
+ UserManager userManager = getSystemService(UserManager.class);
+ for (final UserInfo userInfo :
+ userManager.getProfiles(mPersonalProfileUserHandle.getIdentifier())) {
+ if (userInfo.isCloneProfile()) {
+ mCloneProfileUserHandle = userInfo.getUserHandle();
+ }
+ }
+ return mCloneProfileUserHandle;
+ }
+
+ private UserHandle fetchTabOwnerUserHandleForLaunch() {
+ if (isLaunchedAsCloneProfile()) {
+ return getPersonalProfileUserHandle();
+ }
+ return mLaunchedFromUserHandle;
+ }
+
private boolean hasWorkProfile() {
return getWorkProfileUserHandle() != null;
}
+ private boolean hasCloneProfile() {
+ return getCloneProfileUserHandle() != null;
+ }
+
+ private boolean isLaunchedAsCloneProfile() {
+ return hasCloneProfile()
+ && (UserHandle.myUserId() == getCloneProfileUserHandle().getIdentifier());
+ }
+
protected boolean shouldShowTabs() {
return hasWorkProfile() && ENABLE_TABBED_VIEW;
}
@@ -1123,6 +1176,14 @@
mAlwaysButton.setEnabled(false);
return;
}
+ // In case of clonedProfile being active, we do not allow the 'Always' option in the
+ // disambiguation dialog of Personal Profile as the package manager cannot distinguish
+ // between cross-profile preferred activities.
+ if (hasCloneProfile() && !mMultiProfilePagerAdapter
+ .getCurrentUserHandle().equals(mWorkProfileUserHandle)) {
+ mAlwaysButton.setEnabled(false);
+ return;
+ }
boolean enabled = false;
ResolveInfo ri = null;
if (hasValidSelection) {
@@ -1431,17 +1492,14 @@
return true;
}
- @VisibleForTesting
- public void safelyStartActivity(TargetInfo cti) {
- // We're dispatching intents that might be coming from legacy apps, so
- // don't kill ourselves.
- StrictMode.disableDeathOnFileUriExposure();
- try {
- UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
- safelyStartActivityInternal(cti, currentUserHandle, null);
- } finally {
- StrictMode.enableDeathOnFileUriExposure();
- }
+ /** Start the activity specified by the {@link TargetInfo}.*/
+ public final void safelyStartActivity(TargetInfo cti) {
+ // In case cloned apps are present, we would want to start those apps in cloned user
+ // space, which will not be same as adaptor's userHandle. resolveInfo.userHandle
+ // identifies the correct user space in such cases.
+ UserHandle activityUserHandle = getResolveInfoUserHandle(
+ cti.getResolveInfo(), mMultiProfilePagerAdapter.getCurrentUserHandle());
+ safelyStartActivityAsUser(cti, activityUserHandle, null);
}
/**
@@ -1449,11 +1507,12 @@
* @param cti TargetInfo to be launched.
* @param user User to launch this activity as.
*/
- @VisibleForTesting
- public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+ public final void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
safelyStartActivityAsUser(cti, user, null);
}
+ // TODO: Make method public final.
+ @VisibleForTesting
protected void safelyStartActivityAsUser(
TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// We're dispatching intents that might be coming from legacy apps, so
@@ -1466,7 +1525,8 @@
}
}
- private void safelyStartActivityInternal(
+ @VisibleForTesting
+ protected void safelyStartActivityInternal(
TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// If the target is suspended, the activity will not be successfully launched.
// Do not unregister from package manager updates in this case
@@ -1550,13 +1610,15 @@
@VisibleForTesting
protected ResolverListController createListController(UserHandle userHandle) {
+ UserHandle queryIntentsUser = getQueryIntentsUser(userHandle);
return new ResolverListController(
this,
mPm,
getTargetIntent(),
getReferrerPackageName(),
mLaunchedFromUid,
- userHandle);
+ userHandle,
+ queryIntentsUser);
}
/**
@@ -2170,16 +2232,10 @@
public boolean useLayoutWithDefault() {
// We only use the default app layout when the profile of the active user has a
// filtered item. We always show the same default app even in the inactive user profile.
- boolean currentUserAdapterHasFilteredItem;
- if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
- == UserHandle.myUserId()) {
- currentUserAdapterHasFilteredItem =
- mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
- } else {
- currentUserAdapterHasFilteredItem =
- mMultiProfilePagerAdapter.getInactiveListAdapter().hasFilteredItem();
- }
- return mSupportsAlwaysUseOption && currentUserAdapterHasFilteredItem;
+ boolean adapterForCurrentUserHasFilteredItem =
+ mMultiProfilePagerAdapter.getListAdapterForUserHandle(
+ getTabOwnerUserHandleForLaunch()).hasFilteredItem();
+ return mSupportsAlwaysUseOption && adapterForCurrentUserHasFilteredItem;
}
/**
@@ -2198,7 +2254,14 @@
return lhs == null ? rhs == null
: lhs.activityInfo == null ? rhs.activityInfo == null
: Objects.equals(lhs.activityInfo.name, rhs.activityInfo.name)
- && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName);
+ && Objects.equals(lhs.activityInfo.packageName, rhs.activityInfo.packageName)
+ // Comparing against resolveInfo.userHandle in case cloned apps are present,
+ // as they will have the same activityInfo.
+ && Objects.equals(
+ getResolveInfoUserHandle(lhs,
+ mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()),
+ getResolveInfoUserHandle(rhs,
+ mMultiProfilePagerAdapter.getActiveListAdapter().getUserHandle()));
}
protected String getMetricsCategory() {
@@ -2439,4 +2502,30 @@
}
protected void maybeLogProfileChange() {}
+
+ /**
+ * Returns the {@link UserHandle} to use when querying resolutions for intents in a
+ * {@link ResolverListController} configured for the provided {@code userHandle}.
+ */
+ protected final UserHandle getQueryIntentsUser(UserHandle userHandle) {
+ // In case launching app is in clonedProfile, and we are building the personal tab, intent
+ // resolution will be attempted as clonedUser instead of user 0. This is because intent
+ // resolution from user 0 and clonedUser is not guaranteed to return same results.
+ // We do not care about the case when personal adapter is started with non-root user
+ // (secondary user case), as clone profile is guaranteed to be non-active in that case.
+ UserHandle queryIntentsUser = userHandle;
+ if (isLaunchedAsCloneProfile() && userHandle.equals(getPersonalProfileUserHandle())) {
+ queryIntentsUser = getCloneProfileUserHandle();
+ }
+ return queryIntentsUser;
+ }
+
+ /**
+ * This function is temporary in nature, and its usages will be replaced with just
+ * resolveInfo.userHandle, once it is available, once sharesheet is stable.
+ */
+ public static UserHandle getResolveInfoUserHandle(ResolveInfo resolveInfo,
+ UserHandle predictedHandle) {
+ return predictedHandle;
+ }
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 42b46cd..683dc2b 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -737,8 +737,10 @@
}
Drawable loadIconForResolveInfo(ResolveInfo ri) {
- // Load icons based on the current process. If in work profile icons should be badged.
- return makePresentationGetter(ri).getIcon(getUserHandle());
+ // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons
+ // should be badged.
+ return makePresentationGetter(ri)
+ .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, getUserHandle()));
}
void loadFilteredItemIconTaskAsync(@NonNull ImageView iconView) {
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 01dcf962..60b68ad 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -60,6 +60,7 @@
private AbstractResolverComparator mResolverComparator;
private boolean isComputed = false;
+ private final UserHandle mQueryIntentsAsUser;
public ResolverListController(
Context context,
@@ -67,10 +68,11 @@
Intent targetIntent,
String referrerPackage,
int launchedFromUid,
- UserHandle userHandle) {
+ UserHandle userHandle,
+ UserHandle queryIntentsAsUser) {
this(context, pm, targetIntent, referrerPackage, launchedFromUid, userHandle,
new ResolverRankerServiceResolverComparator(
- context, targetIntent, referrerPackage, null, null));
+ context, targetIntent, referrerPackage, null, null), queryIntentsAsUser);
}
public ResolverListController(
@@ -80,7 +82,8 @@
String referrerPackage,
int launchedFromUid,
UserHandle userHandle,
- AbstractResolverComparator resolverComparator) {
+ AbstractResolverComparator resolverComparator,
+ UserHandle queryIntentsAsUser) {
mContext = context;
mpm = pm;
mLaunchedFromUid = launchedFromUid;
@@ -88,6 +91,7 @@
mReferrerPackage = referrerPackage;
mUserHandle = userHandle;
mResolverComparator = resolverComparator;
+ mQueryIntentsAsUser = queryIntentsAsUser;
}
@VisibleForTesting
@@ -113,7 +117,7 @@
boolean shouldGetOnlyDefaultActivities,
List<Intent> intents) {
return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata,
- shouldGetOnlyDefaultActivities, intents, mUserHandle);
+ shouldGetOnlyDefaultActivities, intents, mQueryIntentsAsUser);
}
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser(
@@ -170,6 +174,10 @@
final int intoCount = into.size();
for (int i = 0; i < fromCount; i++) {
final ResolveInfo newInfo = from.get(i);
+ // TODO: Once resolveInfo.userHandle is available, add the logic to drop any ResolveInfo
+ // with userHandle as null, with a warning logged. This is necessary as sharesheet is
+ // going to use userHandle to determine icon badging and starting the activity in
+ // correct userSpace.
boolean found = false;
// Only loop to the end of into as it was before we started; no dupes in from.
for (int j = 0; j < intoCount; j++) {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 9922051..1ecaf21 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -41,9 +41,10 @@
ResolverListAdapter adapter,
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
super(context, /* currentPage */ 0, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(adapter)
};
@@ -55,9 +56,10 @@
EmptyStateProvider emptyStateProvider,
QuietModeManager quietModeManager,
@Profile int defaultProfile,
- UserHandle workProfileUserHandle) {
+ UserHandle workProfileUserHandle,
+ UserHandle cloneUserHandle) {
super(context, /* currentPage */ defaultProfile, emptyStateProvider, quietModeManager,
- workProfileUserHandle);
+ workProfileUserHandle, cloneUserHandle);
mItems = new ResolverProfileDescriptor[] {
createProfileDescriptor(personalAdapter),
createProfileDescriptor(workAdapter)
@@ -107,11 +109,12 @@
@Override
@Nullable
ResolverListAdapter getListAdapterForUserHandle(UserHandle userHandle) {
- if (getActiveListAdapter().getUserHandle().equals(userHandle)) {
- return getActiveListAdapter();
- } else if (getInactiveListAdapter() != null
- && getInactiveListAdapter().getUserHandle().equals(userHandle)) {
- return getInactiveListAdapter();
+ if (getPersonalListAdapter().getUserHandle().equals(userHandle)
+ || userHandle.equals(getCloneUserHandle())) {
+ return getPersonalListAdapter();
+ } else if (getWorkListAdapter() != null
+ && getWorkListAdapter().getUserHandle().equals(userHandle)) {
+ return getWorkListAdapter();
}
return null;
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
index eead4ed..9f8c739 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityOverrideData.java
@@ -29,7 +29,6 @@
import android.util.Pair;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
import com.android.internal.logging.MetricsLogger;
@@ -71,13 +70,13 @@
public int alternateProfileSetting;
public Resources resources;
public UserHandle workProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public boolean isWorkProfileUserRunning;
public boolean isWorkProfileUserUnlocked;
public Integer myUserId;
public QuietModeManager mQuietModeManager;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public PackageManager packageManager;
@@ -98,6 +97,7 @@
alternateProfileSetting = 0;
resources = null;
workProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
isWorkProfileUserRunning = true;
@@ -126,13 +126,6 @@
}
};
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
index b6ea9dd..1f5a548 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityWorkProfileTest.java
@@ -49,8 +49,8 @@
import androidx.test.rule.ActivityTestRule;
import com.android.internal.R;
-import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.ChooserActivityWorkProfileTest.TestCase.Tab;
+import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import junit.framework.AssertionFailedError;
@@ -93,7 +93,7 @@
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
- sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+ sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
launchActivity(mTestCase.getIsSendAction());
switchToTab(mTestCase.getTab());
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 5dc0c8b..d7a8b3a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -16,10 +16,6 @@
package com.android.internal.app;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -34,12 +30,12 @@
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserHandle;
import android.util.Pair;
import android.util.Size;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.app.chooser.TargetInfo;
@@ -132,14 +128,6 @@
}
@Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
- }
-
- @Override
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
if (sOverrides.mCrossProfileIntentsChecker != null) {
return sOverrides.mCrossProfileIntentsChecker;
@@ -155,13 +143,15 @@
return super.createQuietModeManager();
}
+ // TODO: Remove this and override safelyStartActivityInternal() instead.
@Override
- public void safelyStartActivity(com.android.internal.app.chooser.TargetInfo cti) {
+ public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityAsUser(cti, user, options);
}
@Override
@@ -253,6 +243,14 @@
}
@Override
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+ return super.getTabOwnerUserHandleForLaunch();
+ }
+ return sOverrides.tabOwnerUserHandleForLaunch;
+ }
+
+ @Override
public Context createContextAsUser(UserHandle user, int flags) {
// return the current context as a work profile doesn't really exist in these tests
return getApplicationContext();
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 92c05b0..3284f4a 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -911,6 +911,172 @@
assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
}
+ @Test
+ public void testClonedProfilePresent_personalAdapterIsSetWithPersonalProfile() {
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle(), is(activity.getPersonalProfileUserHandle()));
+ assertThat(activity.getAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testClonedProfilePresent_layoutWithDefault_neverShown() throws Exception {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ Espresso.registerIdlingResources(activity.getAdapter().getLabelIdlingResource());
+ waitForIdle();
+
+ assertThat(activity.getAdapter().hasFilteredItem(), is(false));
+ assertThat(activity.getAdapter().getCount(), is(2));
+ assertThat(activity.getAdapter().getPlaceholderCount(), is(2));
+ }
+
+ @Test
+ public void testClonedProfilePresent_alwaysButtonDisabled() throws Exception {
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+ Intent sendIntent = createSendImageIntent();
+ List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(3);
+
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
+ when(sOverrides.resolverListController.getLastChosen())
+ .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ // Confirm that the button bar is disabled by default
+ onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+
+ // Make a stable copy of the components as the original list may be modified
+ List<ResolvedComponentInfo> stableCopy =
+ createResolvedComponentsForTestWithOtherProfile(2);
+
+ onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
+ .perform(click());
+
+ onView(withId(R.id.button_once)).check(matches(isEnabled()));
+ onView(withId(R.id.button_always)).check(matches(not(isEnabled())));
+ }
+
+ @Test
+ public void testClonedProfilePresent_personalProfileActivityIsStartedInCorrectUser()
+ throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ sOverrides.hasCrossProfileIntents = false;
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+ sOverrides.onSafelyStartInternalCallback = userHandle -> {
+ selectedActivityUserHandle[0] = userHandle;
+ return true;
+ };
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(first(allOf(withText(personalResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+ }
+
+ @Test
+ public void testClonedProfilePresent_workProfileActivityIsStartedInCorrectUser()
+ throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ // enable cloneProfile
+ markCloneProfileUserAvailable();
+
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ setupResolverControllers(personalResolvedComponentInfos, workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ sendIntent.setType("TestType");
+ final UserHandle[] selectedActivityUserHandle = new UserHandle[1];
+ sOverrides.onSafelyStartInternalCallback = userHandle -> {
+ selectedActivityUserHandle[0] = userHandle;
+ return true;
+ };
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(selectedActivityUserHandle[0], is(activity.getAdapter().getUserHandle()));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -969,6 +1135,10 @@
ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
}
+ private void markCloneProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.cloneProfileUserHandle = UserHandle.of(11);
+ }
+
private void setupResolverControllers(
List<ResolvedComponentInfo> personalResolvedComponentInfos,
List<ResolvedComponentInfo> workResolvedComponentInfos) {
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
index 4a79f5e..fe8cd09 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityWorkProfileTest.java
@@ -92,7 +92,7 @@
public void testBlocker() {
setUpPersonalAndWorkComponentInfos();
sOverrides.hasCrossProfileIntents = mTestCase.hasCrossProfileIntents();
- sOverrides.myUserId = mTestCase.getMyUserHandle().getIdentifier();
+ sOverrides.tabOwnerUserHandleForLaunch = mTestCase.getMyUserHandle();
launchActivity(/* callingUser= */ mTestCase.getExtraCallingUser());
switchToTab(mTestCase.getTab());
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index 42593f6..bb0d946 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -119,7 +119,8 @@
anyString(), anyInt(), anyString(), any(), anyString());
when(mMockContext.getOpPackageName()).thenReturn(refererPackage);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+ UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
long beforeReport = getCount(mUsm, packageName, action, annotation);
mController.updateChooserCounts(packageName, UserHandle.USER_CURRENT, action);
@@ -134,7 +135,8 @@
String refererPackage = "test_referer_package";
List<ResolvedComponentInfo> resolvedComponents = createResolvedComponentsForTest(10);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
- refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
+ refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM,
+ UserHandle.SYSTEM);
List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
mController.topK(topKList, 5);
List<ResolvedComponentInfo> sortList = new ArrayList<>(topKList);
@@ -162,7 +164,7 @@
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
@@ -186,7 +188,7 @@
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
@@ -210,7 +212,7 @@
any(UserHandle.class))).thenReturn(infos);
mController = new ResolverListController(mMockContext, mMockPackageManager,
createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
- /* userHandle= */ UserHandle.SYSTEM);
+ /* userHandle= */ UserHandle.SYSTEM, UserHandle.SYSTEM);
List<Intent> intents = new ArrayList<>();
intents.add(createActionMainIntent());
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index c778dfe..8f6f29d 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
@@ -30,7 +31,6 @@
import android.os.UserHandle;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker;
-import com.android.internal.app.AbstractMultiProfilePagerAdapter.MyUserIdProvider;
import com.android.internal.app.AbstractMultiProfilePagerAdapter.QuietModeManager;
import com.android.internal.app.chooser.TargetInfo;
@@ -57,14 +57,6 @@
}
@Override
- protected MyUserIdProvider createMyUserIdProvider() {
- if (sOverrides.mMyUserIdProvider != null) {
- return sOverrides.mMyUserIdProvider;
- }
- return super.createMyUserIdProvider();
- }
-
- @Override
protected CrossProfileIntentsChecker createCrossProfileIntentsChecker() {
if (sOverrides.mCrossProfileIntentsChecker != null) {
return sOverrides.mCrossProfileIntentsChecker;
@@ -103,13 +95,25 @@
return super.isVoiceInteraction();
}
+ // TODO: Remove this and override safelyStartActivityInternal() instead.
@Override
- public void safelyStartActivity(TargetInfo cti) {
+ public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
if (sOverrides.onSafelyStartCallback != null &&
sOverrides.onSafelyStartCallback.apply(cti)) {
return;
}
- super.safelyStartActivity(cti);
+ super.safelyStartActivityAsUser(cti, user, options);
+ }
+
+ @Override
+ public void safelyStartActivityInternal(TargetInfo cti, UserHandle user,
+ @Nullable Bundle options) {
+ if (sOverrides.onSafelyStartInternalCallback != null
+ && sOverrides.onSafelyStartInternalCallback.apply(user)) {
+ return;
+ }
+ super.safelyStartActivityInternal(cti, user, options);
}
@Override
@@ -135,11 +139,29 @@
}
@Override
+ protected UserHandle getPersonalProfileUserHandle() {
+ return super.getPersonalProfileUserHandle();
+ }
+
+ @Override
protected UserHandle getWorkProfileUserHandle() {
return sOverrides.workProfileUserHandle;
}
@Override
+ protected UserHandle getCloneProfileUserHandle() {
+ return sOverrides.cloneProfileUserHandle;
+ }
+
+ @Override
+ protected UserHandle getTabOwnerUserHandleForLaunch() {
+ if (sOverrides.tabOwnerUserHandleForLaunch == null) {
+ return super.getTabOwnerUserHandleForLaunch();
+ }
+ return sOverrides.tabOwnerUserHandleForLaunch;
+ }
+
+ @Override
public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
super.startActivityAsUser(intent, options, user);
}
@@ -153,24 +175,29 @@
@SuppressWarnings("Since15")
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
+ public Function<UserHandle, Boolean> onSafelyStartInternalCallback;
public ResolverListController resolverListController;
public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public UserHandle workProfileUserHandle;
+ public UserHandle cloneProfileUserHandle;
+ public UserHandle tabOwnerUserHandleForLaunch;
public Integer myUserId;
public boolean hasCrossProfileIntents;
public boolean isQuietModeEnabled;
public QuietModeManager mQuietModeManager;
- public MyUserIdProvider mMyUserIdProvider;
public CrossProfileIntentsChecker mCrossProfileIntentsChecker;
public void reset() {
onSafelyStartCallback = null;
+ onSafelyStartInternalCallback = null;
isVoiceInteraction = null;
createPackageManager = null;
resolverListController = mock(ResolverListController.class);
workResolverListController = mock(ResolverListController.class);
workProfileUserHandle = null;
+ cloneProfileUserHandle = null;
+ tabOwnerUserHandleForLaunch = null;
myUserId = null;
hasCrossProfileIntents = true;
isQuietModeEnabled = false;
@@ -197,13 +224,6 @@
}
};
- mMyUserIdProvider = new MyUserIdProvider() {
- @Override
- public int getMyUserId() {
- return myUserId != null ? myUserId : UserHandle.myUserId();
- }
- };
-
mCrossProfileIntentsChecker = mock(CrossProfileIntentsChecker.class);
when(mCrossProfileIntentsChecker.hasCrossProfileIntents(any(), anyInt(), anyInt()))
.thenAnswer(invocation -> hasCrossProfileIntents);