From 10f22425705485803cc723bcdc61623926a402c9 Mon Sep 17 00:00:00 2001 From: Himanshu Gupta Date: Sat, 25 Feb 2023 14:39:01 +0000 Subject: Migrating AppCloning code to unbundled sharesheet. This CL encapsulates the work done in previous CLs for the frameworks sharesheet: 1. ag/20982903 2. ag/20980382 3. ag/20480246 4. ag/21072117 5. ag/21223364 6. ag/21117396 7. ag/21465730 With this CL, Cloned Apps can be shown in unbundled sharesheet, at par with frameworks. Bug: 273294251 Test: atest com.android.intentresolver Change-Id: Ic01a93f7279c8beb998b3e98f53c459c5ed2b1bf --- .../AbstractMultiProfilePagerAdapter.java | 9 +- .../intentresolver/AnnotatedUserHandles.java | 42 ++- .../android/intentresolver/ChooserActivity.java | 85 +++++-- .../android/intentresolver/ChooserListAdapter.java | 9 +- .../ChooserMultiProfilePagerAdapter.java | 6 + .../GenericMultiProfilePagerAdapter.java | 24 +- .../NoAppsAvailableEmptyStateProvider.java | 12 +- .../NoCrossProfileEmptyStateProvider.java | 11 +- .../android/intentresolver/ResolverActivity.java | 184 ++++++++++---- .../intentresolver/ResolverListAdapter.java | 33 ++- .../intentresolver/ResolverListController.java | 45 ++-- .../ResolverMultiProfilePagerAdapter.java | 10 +- .../model/AbstractResolverComparator.java | 72 +++++- .../AppPredictionServiceResolverComparator.java | 43 ++-- .../model/ResolverComparatorModel.java | 7 +- .../ResolverRankerServiceResolverComparator.java | 281 +++++++++++++-------- 16 files changed, 608 insertions(+), 265 deletions(-) (limited to 'java/src') diff --git a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java index e3f1b233..4b06db3b 100644 --- a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java @@ -62,6 +62,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { private Set mLoadedPages; private final EmptyStateProvider mEmptyStateProvider; private final UserHandle mWorkProfileUserHandle; + private final UserHandle mCloneProfileUserHandle; private final Supplier mWorkProfileQuietModeChecker; // True when work is quiet. AbstractMultiProfilePagerAdapter( @@ -69,11 +70,13 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { int currentPage, EmptyStateProvider emptyStateProvider, Supplier workProfileQuietModeChecker, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle) { mContext = Objects.requireNonNull(context); mCurrentPage = currentPage; mLoadedPages = new HashSet<>(); mWorkProfileUserHandle = workProfileUserHandle; + mCloneProfileUserHandle = cloneProfileUserHandle; mEmptyStateProvider = emptyStateProvider; mWorkProfileQuietModeChecker = workProfileQuietModeChecker; } @@ -160,6 +163,10 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { return null; } + public UserHandle getCloneUserHandle() { + return mCloneProfileUserHandle; + } + /** * Returns the {@link ProfileDescriptor} relevant to the given pageIndex. *
    diff --git a/java/src/com/android/intentresolver/AnnotatedUserHandles.java b/java/src/com/android/intentresolver/AnnotatedUserHandles.java index b4365b84..769195ed 100644 --- a/java/src/com/android/intentresolver/AnnotatedUserHandles.java +++ b/java/src/com/android/intentresolver/AnnotatedUserHandles.java @@ -87,6 +87,11 @@ public final class AnnotatedUserHandles { // TODO: integrate logic for `ResolverActivity.EXTRA_CALLING_USER`. userHandleSharesheetLaunchedAs = UserHandle.of(UserHandle.myUserId()); + // ActivityManager.getCurrentUser() refers to the current Foreground user. When clone/work + // profile is active, we always make the personal tab from the foreground user. + // Outside profiles, current foreground user is potentially the same as the sharesheet + // process's user (UserHandle.myUserId()), so we continue to create personal tab with the + // current foreground user. personalProfileUserHandle = UserHandle.of(ActivityManager.getCurrentUser()); UserManager userManager = forShareActivity.getSystemService(UserManager.class); @@ -100,14 +105,43 @@ public final class AnnotatedUserHandles { @Nullable private static UserHandle getWorkProfileForUser( UserManager userManager, UserHandle profileOwnerUserHandle) { - return userManager.getProfiles(profileOwnerUserHandle.getIdentifier()).stream() - .filter(info -> info.isManagedProfile()).findFirst() - .map(info -> info.getUserHandle()).orElse(null); + return userManager.getProfiles(profileOwnerUserHandle.getIdentifier()) + .stream() + .filter(info -> info.isManagedProfile()) + .findFirst() + .map(info -> info.getUserHandle()) + .orElse(null); } @Nullable private static UserHandle getCloneProfileForUser( UserManager userManager, UserHandle profileOwnerUserHandle) { - return null; // Not yet supported in framework. + return userManager.getProfiles(profileOwnerUserHandle.getIdentifier()) + .stream() + .filter(info -> info.isCloneProfile()) + .findFirst() + .map(info -> info.getUserHandle()) + .orElse(null); + } + + /** + * Returns the {@link UserHandle} to use when querying resolutions for intents in a + * {@link ResolverListController} configured for the provided {@code userHandle}. + */ + public 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(personalProfileUserHandle)) { + queryIntentsUser = cloneProfileUserHandle; + } + return queryIntentsUser; + } + + private Boolean isLaunchedAsCloneProfile() { + return userHandleSharesheetLaunchedAs.equals(cloneProfileUserHandle); } } diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index ae5be26d..bae1feb2 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -495,7 +495,7 @@ public class ChooserActivity extends ResolverActivity implements return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(), noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, - createCrossProfileIntentsChecker(), createMyUserIdProvider()); + createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch()); } private ChooserMultiProfilePagerAdapter createChooserMultiProfilePagerAdapterForOneProfile( @@ -508,13 +508,14 @@ public class ChooserActivity extends ResolverActivity implements initialIntents, rList, filterLastUsed, - /* userHandle */ UserHandle.of(UserHandle.myUserId())); + /* userHandle */ getPersonalProfileUserHandle()); return new ChooserMultiProfilePagerAdapter( /* context */ this, adapter, createEmptyStateProvider(/* workProfileUserHandle= */ null), /* workProfileQuietModeChecker= */ () -> false, /* workProfileUserHandle= */ null, + getCloneProfileUserHandle(), mMaxTargetsPerRow); } @@ -545,13 +546,14 @@ public class ChooserActivity extends ResolverActivity implements () -> mWorkProfileAvailability.isQuietModeEnabled(), selectedProfile, getWorkProfileUserHandle(), + getCloneProfileUserHandle(), mMaxTargetsPerRow); } private int findSelectedProfile() { int selectedProfile = getSelectedProfileExtra(); if (selectedProfile == -1) { - selectedProfile = getProfileForUser(getUser()); + selectedProfile = getProfileForUser(getTabOwnerUserHandleForLaunch()); } return selectedProfile; } @@ -860,7 +862,11 @@ public class ChooserActivity extends ResolverActivity implements ChooserTargetActionsDialogFragment.show( getSupportFragmentManager(), targetList, - mChooserMultiProfilePagerAdapter.getCurrentUserHandle(), + // Adding userHandle from ResolveInfo allows the app icon in Dialog Box to be + // resolved correctly within the same tab. + getResolveInfoUserHandle( + targetInfo.getResolveInfo(), + mChooserMultiProfilePagerAdapter.getCurrentUserHandle()), shortcutIdKey, shortcutTitle, isShortcutPinned, @@ -892,11 +898,14 @@ public class ChooserActivity extends ResolverActivity implements if (targetInfo.isMultiDisplayResolveInfo()) { MultiDisplayResolveInfo mti = (MultiDisplayResolveInfo) targetInfo; if (!mti.hasSelected()) { + // Add userHandle based badge to the stackedAppDialogBox. ChooserStackedAppDialogFragment.show( getSupportFragmentManager(), mti, which, - mChooserMultiProfilePagerAdapter.getCurrentUserHandle()); + getResolveInfoUserHandle( + targetInfo.getResolveInfo(), + mChooserMultiProfilePagerAdapter.getCurrentUserHandle())); return; } } @@ -1008,9 +1017,11 @@ public class ChooserActivity extends ResolverActivity implements mChooserMultiProfilePagerAdapter.getActiveListAdapter(); if (currentListAdapter != null) { sendImpressionToAppPredictor(info, currentListAdapter); - currentListAdapter.updateModel(info.getResolvedComponentName()); - currentListAdapter.updateChooserCounts(ri.activityInfo.packageName, - targetIntent.getAction()); + currentListAdapter.updateModel(info); + currentListAdapter.updateChooserCounts( + ri.activityInfo.packageName, + targetIntent.getAction(), + ri.userHandle); } if (DEBUG) { Log.d(TAG, "ResolveInfo Package is " + ri.activityInfo.packageName); @@ -1096,22 +1107,33 @@ public class ChooserActivity extends ResolverActivity implements @Nullable private AppPredictor getAppPredictor(UserHandle userHandle) { ProfileRecord record = getProfileRecord(userHandle); - return (record == null) ? null : record.appPredictor; + // We cannot use APS service when clone profile is present as APS service cannot sort + // cross profile targets as of now. + return (record == null || getCloneProfileUserHandle() != null) ? null : record.appPredictor; } /** * Sort intents alphabetically based on display label. */ static class AzInfoComparator implements Comparator { - Collator mCollator; + Comparator 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); } } @@ -1129,14 +1151,16 @@ public class ChooserActivity extends ResolverActivity implements Intent targetIntent, String referrerPackageName, int launchedFromUid, - AbstractResolverComparator resolverComparator) { + AbstractResolverComparator resolverComparator, + UserHandle queryIntentsAsUser) { super( context, pm, targetIntent, referrerPackageName, launchedFromUid, - resolverComparator); + resolverComparator, + queryIntentsAsUser); } @Override @@ -1255,20 +1279,24 @@ public class ChooserActivity extends ResolverActivity implements Intent targetIntent, ChooserRequestParameters chooserRequest, int maxTargetsPerRow) { + UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() + && userHandle.equals(getPersonalProfileUserHandle()) + ? getCloneProfileUserHandle() : userHandle; return new ChooserListAdapter( context, payloadIntents, initialIntents, rList, filterLastUsed, - resolverListController, + createListController(userHandle), userHandle, targetIntent, this, context.getPackageManager(), getChooserActivityLogger(), chooserRequest, - maxTargetsPerRow); + maxTargetsPerRow, + initialIntentsUserSpace); } @Override @@ -1281,8 +1309,13 @@ public class ChooserActivity extends ResolverActivity implements getReferrerPackageName(), appPredictor, userHandle, getChooserActivityLogger()); } else { resolverComparator = - new ResolverRankerServiceResolverComparator(this, getTargetIntent(), - getReferrerPackageName(), null, getChooserActivityLogger()); + new ResolverRankerServiceResolverComparator( + this, + getTargetIntent(), + getReferrerPackageName(), + null, + getChooserActivityLogger(), + getResolverRankerServiceUserHandleList(userHandle)); } return new ChooserListController( @@ -1291,7 +1324,8 @@ public class ChooserActivity extends ResolverActivity implements getTargetIntent(), getReferrerPackageName(), getAnnotatedUserHandles().userIdOfCallingApp, - resolverComparator); + resolverComparator, + getQueryIntentsUser(userHandle)); } @VisibleForTesting @@ -1508,17 +1542,16 @@ public class ChooserActivity extends ResolverActivity implements } /** - * 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/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index f0651360..dab44577 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -142,7 +142,8 @@ public class ChooserListAdapter extends ResolverListAdapter { PackageManager packageManager, ChooserActivityLogger chooserActivityLogger, ChooserRequestParameters chooserRequest, - int maxRankedTargets) { + int maxRankedTargets, + UserHandle initialIntentsUserSpace) { // Don't send the initial intents through the shared ResolverActivity path, // we want to separate them into a different section. super( @@ -155,7 +156,8 @@ public class ChooserListAdapter extends ResolverListAdapter { userHandle, targetIntent, resolverListCommunicator, - false); + false, + initialIntentsUserSpace); mChooserRequest = chooserRequest; mMaxRankedTargets = maxRankedTargets; @@ -222,6 +224,7 @@ public class ChooserListAdapter extends ResolverListAdapter { ri.noResourceId = true; ri.icon = 0; } + ri.userHandle = initialIntentsUserSpace; DisplayResolveInfo displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo( ii, ri, ii, mPresentationFactory.makePresentationGetter(ri)); mCallerTargets.add(displayResolveInfo); @@ -351,6 +354,8 @@ public class ChooserListAdapter extends ResolverListAdapter { .collect(Collectors.groupingBy(target -> target.getResolvedComponentName().getPackageName() + "#" + target.getDisplayLabel() + + '#' + ResolverActivity.getResolveInfoUserHandle( + target.getResolveInfo(), getUserHandle()).getIdentifier() )) .values() .stream() diff --git a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java index 3e2ea473..9c096fd2 100644 --- a/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/ChooserMultiProfilePagerAdapter.java @@ -50,6 +50,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda EmptyStateProvider emptyStateProvider, Supplier workProfileQuietModeChecker, UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle, int maxTargetsPerRow) { this( context, @@ -59,6 +60,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda workProfileQuietModeChecker, /* defaultProfile= */ 0, workProfileUserHandle, + cloneProfileUserHandle, new BottomPaddingOverrideSupplier(context)); } @@ -70,6 +72,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda Supplier workProfileQuietModeChecker, @Profile int defaultProfile, UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle, int maxTargetsPerRow) { this( context, @@ -79,6 +82,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda workProfileQuietModeChecker, defaultProfile, workProfileUserHandle, + cloneProfileUserHandle, new BottomPaddingOverrideSupplier(context)); } @@ -90,6 +94,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda Supplier workProfileQuietModeChecker, @Profile int defaultProfile, UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle, BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) { super( context, @@ -100,6 +105,7 @@ public class ChooserMultiProfilePagerAdapter extends GenericMultiProfilePagerAda workProfileQuietModeChecker, defaultProfile, workProfileUserHandle, + cloneProfileUserHandle, () -> makeProfileView(context), bottomPaddingOverrideSupplier); mAdapterBinder = adapterBinder; diff --git a/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java index 7613f35f..a1c53402 100644 --- a/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/GenericMultiProfilePagerAdapter.java @@ -19,6 +19,7 @@ package com.android.intentresolver; import android.annotation.Nullable; import android.content.Context; import android.os.UserHandle; +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -84,6 +85,7 @@ class GenericMultiProfilePagerAdapter< Supplier workProfileQuietModeChecker, @Profile int defaultProfile, UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle, Supplier pageViewInflater, Supplier> containerBottomPaddingOverrideSupplier) { super( @@ -91,7 +93,8 @@ class GenericMultiProfilePagerAdapter< /* currentPage= */ defaultProfile, emptyStateProvider, workProfileQuietModeChecker, - workProfileUserHandle); + workProfileUserHandle, + cloneProfileUserHandle); mListAdapterExtractor = listAdapterExtractor; mAdapterBinder = adapterBinder; @@ -145,12 +148,12 @@ class GenericMultiProfilePagerAdapter< @Override @Nullable protected ListAdapterT getListAdapterForUserHandle(UserHandle userHandle) { - if (getActiveListAdapter().getUserHandle().equals(userHandle)) { - return getActiveListAdapter(); - } - 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; } @@ -177,6 +180,9 @@ class GenericMultiProfilePagerAdapter< @Override public ListAdapterT getWorkListAdapter() { + if (!hasAdapterForIndex(PROFILE_WORK)) { + return null; + } return mListAdapterExtractor.apply(getAdapterForIndex(PROFILE_WORK)); } @@ -209,6 +215,10 @@ class GenericMultiProfilePagerAdapter< paddingBottom)); } + private boolean hasAdapterForIndex(int pageIndex) { + return (pageIndex < getCount()); + } + // TODO: `ChooserActivity` also has a per-profile record type. Maybe the "multi-profile pager" // should be the owner of all per-profile data (especially now that the API is generic)? private static class GenericProfileDescriptor extends diff --git a/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java index d424f295..a7b50f38 100644 --- a/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/NoAppsAvailableEmptyStateProvider.java @@ -30,7 +30,7 @@ import android.stats.devicepolicy.nano.DevicePolicyEnums; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider; +import com.android.internal.R; import java.util.List; @@ -49,16 +49,16 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider { @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 @@ -68,7 +68,7 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider { UserHandle listUserHandle = resolverListAdapter.getUserHandle(); if (mWorkProfileUserHandle != null - && (mMyUserIdProvider.getMyUserId() == listUserHandle.getIdentifier() + && (mTabOwnerUserHandleForLaunch.equals(listUserHandle) || !hasAppsInOtherProfile(resolverListAdapter))) { String title; @@ -101,7 +101,7 @@ public class NoAppsAvailableEmptyStateProvider implements EmptyStateProvider { return false; } List resolversForIntent = - adapter.getResolversForUser(UserHandle.of(mMyUserIdProvider.getMyUserId())); + adapter.getResolversForUser(mTabOwnerUserHandleForLaunch); for (ResolvedComponentInfo info : resolversForIntent) { ResolveInfo resolveInfo = info.getResolveInfoAt(0); if (resolveInfo.targetUserId != UserHandle.USER_CURRENT) { diff --git a/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java index 420d26c5..6f72bb00 100644 --- a/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java +++ b/java/src/com/android/intentresolver/NoCrossProfileEmptyStateProvider.java @@ -27,7 +27,6 @@ import android.os.UserHandle; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.CrossProfileIntentsChecker; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyState; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.EmptyStateProvider; -import com.android.intentresolver.AbstractMultiProfilePagerAdapter.MyUserIdProvider; /** * Empty state provider that does not allow cross profile sharing, it will return a blocker @@ -39,28 +38,28 @@ public class NoCrossProfileEmptyStateProvider implements EmptyStateProvider { 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 tabOwnerUserHandleForLaunch) { mPersonalProfileUserHandle = personalUserHandle; mNoWorkToPersonalEmptyState = noWorkToPersonalEmptyState; mNoPersonalToWorkEmptyState = noPersonalToWorkEmptyState; mCrossProfileIntentsChecker = crossProfileIntentsChecker; - mUserIdProvider = myUserIdProvider; + mTabOwnerUserHandleForLaunch = tabOwnerUserHandleForLaunch; } @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/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index a240968b..3b9d2a53 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -33,6 +33,8 @@ import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_S import static android.stats.devicepolicy.nano.DevicePolicyEnums.RESOLVER_EMPTY_STATE_NO_SHARING_TO_WORK; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; +import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED; + import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.UiThread; @@ -106,6 +108,7 @@ import com.android.intentresolver.AbstractMultiProfilePagerAdapter.Profile; import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; +import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; import com.android.intentresolver.widget.ResolverDrawerLayout; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; @@ -375,8 +378,11 @@ public class ResolverActivity extends FragmentActivity implements // 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; @@ -480,7 +486,7 @@ public class ResolverActivity extends FragmentActivity implements return new NoCrossProfileEmptyStateProvider(getPersonalProfileUserHandle(), noWorkToPersonalEmptyState, noPersonalToWorkEmptyState, - createCrossProfileIntentsChecker(), createMyUserIdProvider()); + createCrossProfileIntentsChecker(), getTabOwnerUserHandleForLaunch()); } protected int appliedThemeResId() { @@ -857,13 +863,23 @@ public class ResolverActivity extends FragmentActivity implements // the future if resolver *were* to make any (non-overridden) calls to a version that used a // different signature (and thus didn't return the subclass type). @VisibleForTesting - protected ResolverListController createListController(UserHandle unused) { + protected ResolverListController createListController(UserHandle userHandle) { + ResolverRankerServiceResolverComparator resolverComparator = + new ResolverRankerServiceResolverComparator( + this, + getTargetIntent(), + getReferrerPackageName(), + null, + null, + getResolverRankerServiceUserHandleList(userHandle)); return new ResolverListController( this, mPm, getTargetIntent(), getReferrerPackageName(), - getAnnotatedUserHandles().userIdOfCallingApp); + getAnnotatedUserHandles().userIdOfCallingApp, + resolverComparator, + getQueryIntentsUser(userHandle)); } /** @@ -1003,27 +1019,6 @@ public class ResolverActivity extends FragmentActivity implements }); } - // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`. - // @NonFinalForTesting - @Nullable - protected UserHandle getWorkProfileUserHandle() { - return getAnnotatedUserHandles().workProfileUserHandle; - } - - // @NonFinalForTesting - @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(); - } - } - // @NonFinalForTesting @VisibleForTesting protected ResolverListAdapter createResolverListAdapter(Context context, @@ -1032,6 +1027,9 @@ public class ResolverActivity extends FragmentActivity implements Intent startIntent = getIntent(); boolean isAudioCaptureDevice = startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); + UserHandle initialIntentsUserSpace = isLaunchedAsCloneProfile() + && userHandle.equals(getPersonalProfileUserHandle()) + ? getCloneProfileUserHandle() : userHandle; return new ResolverListAdapter( context, payloadIntents, @@ -1042,7 +1040,8 @@ public class ResolverActivity extends FragmentActivity implements userHandle, getTargetIntent(), this, - isAudioCaptureDevice); + isAudioCaptureDevice, + initialIntentsUserSpace); } private LatencyTracker getLatencyTracker() { @@ -1081,7 +1080,7 @@ public class ResolverActivity extends FragmentActivity implements workProfileUserHandle, getPersonalProfileUserHandle(), getMetricsCategory(), - createMyUserIdProvider() + getTabOwnerUserHandleForLaunch() ); // Return composite provider, the order matters (the higher, the more priority) @@ -1124,19 +1123,20 @@ public class ResolverActivity extends FragmentActivity implements initialIntents, rList, filterLastUsed, - /* userHandle */ UserHandle.of(UserHandle.myUserId())); + /* userHandle */ getPersonalProfileUserHandle()); return new ResolverMultiProfilePagerAdapter( /* context */ this, adapter, createEmptyStateProvider(/* workProfileUserHandle= */ null), /* workProfileQuietModeChecker= */ () -> false, - /* workProfileUserHandle= */ null); + /* workProfileUserHandle= */ null, + getCloneProfileUserHandle()); } private UserHandle getIntentUser() { return getIntent().hasExtra(EXTRA_CALLING_USER) ? getIntent().getParcelableExtra(EXTRA_CALLING_USER) - : getUser(); + : getTabOwnerUserHandleForLaunch(); } private ResolverMultiProfilePagerAdapter createResolverMultiProfilePagerAdapterForTwoProfiles( @@ -1148,7 +1148,7 @@ public class ResolverActivity extends FragmentActivity implements // 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)) { @@ -1187,7 +1187,8 @@ public class ResolverActivity extends FragmentActivity implements createEmptyStateProvider(getWorkProfileUserHandle()), () -> mWorkProfileAvailability.isQuietModeEnabled(), selectedProfile, - getWorkProfileUserHandle()); + getWorkProfileUserHandle(), + getCloneProfileUserHandle()); } /** @@ -1210,7 +1211,8 @@ public class ResolverActivity extends FragmentActivity implements } protected final @Profile int getCurrentProfile() { - return (UserHandle.myUserId() == UserHandle.USER_SYSTEM ? PROFILE_PERSONAL : PROFILE_WORK); + return (getTabOwnerUserHandleForLaunch().equals(getPersonalProfileUserHandle()) + ? PROFILE_PERSONAL : PROFILE_WORK); } protected final AnnotatedUserHandles getAnnotatedUserHandles() { @@ -1221,10 +1223,43 @@ public class ResolverActivity extends FragmentActivity implements return getAnnotatedUserHandles().personalProfileUserHandle; } + // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`. + // @NonFinalForTesting + @Nullable + protected UserHandle getWorkProfileUserHandle() { + return getAnnotatedUserHandles().workProfileUserHandle; + } + + // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`. + @Nullable + protected UserHandle getCloneProfileUserHandle() { + return getAnnotatedUserHandles().cloneProfileUserHandle; + } + + // TODO: have tests override `getAnnotatedUserHandles()`, and make this method `final`. + protected UserHandle getTabOwnerUserHandleForLaunch() { + return getAnnotatedUserHandles().tabOwnerUserHandleForLaunch; + } + + protected UserHandle getUserHandleSharesheetLaunchedAs() { + return getAnnotatedUserHandles().userHandleSharesheetLaunchedAs; + } + + private boolean hasWorkProfile() { return getWorkProfileUserHandle() != null; } + private boolean hasCloneProfile() { + return getCloneProfileUserHandle() != null; + } + + protected final boolean isLaunchedAsCloneProfile() { + return hasCloneProfile() + && getUserHandleSharesheetLaunchedAs().equals(getCloneProfileUserHandle()); + } + + protected final boolean shouldShowTabs() { return hasWorkProfile(); } @@ -1498,6 +1533,13 @@ public class ResolverActivity extends FragmentActivity implements 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.getCurrentPage() == PROFILE_PERSONAL)) { + mAlwaysButton.setEnabled(false); + return; + } boolean enabled = false; ResolveInfo ri = null; if (hasValidSelection) { @@ -1572,6 +1614,16 @@ public class ResolverActivity extends FragmentActivity implements } } + /** 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); + } + /** * Start activity as a fixed user handle. * @param cti TargetInfo to be launched. @@ -1594,7 +1646,8 @@ public class ResolverActivity extends FragmentActivity implements } } - 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 @@ -2172,16 +2225,10 @@ public class ResolverActivity extends FragmentActivity implements public final 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; } /** @@ -2200,7 +2247,14 @@ public class ResolverActivity extends FragmentActivity implements 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())); } private boolean inactiveListAdapterHasItems() { @@ -2307,4 +2361,44 @@ public class ResolverActivity extends FragmentActivity implements } } } + /** + * 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) { + return mLazyAnnotatedUserHandles.get().getQueryIntentsUser(userHandle); + } + + /** + * Returns the {@link List} of {@link UserHandle} to pass on to the + * {@link ResolverRankerServiceResolverComparator} as per the provided {@code userHandle}. + */ + @VisibleForTesting(visibility = PROTECTED) + public final List getResolverRankerServiceUserHandleList(UserHandle userHandle) { + return getResolverRankerServiceUserHandleListInternal(userHandle); + } + + @VisibleForTesting + protected List getResolverRankerServiceUserHandleListInternal( + UserHandle userHandle) { + List userList = new ArrayList<>(); + userList.add(userHandle); + // Add clonedProfileUserHandle to the list only if we are: + // a. Building the Personal Tab. + // b. CloneProfile exists on the device. + if (userHandle.equals(getPersonalProfileUserHandle()) + && getCloneProfileUserHandle() != null) { + userList.add(getCloneProfileUserHandle()); + } + return userList; + } + + /** + * 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 resolveInfo.userHandle; + } } diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index eac275cc..b0586f2d 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -97,6 +97,8 @@ public class ResolverListAdapter extends BaseAdapter { private boolean mFilterLastUsed; private Runnable mPostListReadyRunnable; private boolean mIsTabLoaded; + // Represents the UserSpace in which the Initial Intents should be resolved. + private final UserHandle mInitialIntentsUserSpace; public ResolverListAdapter( Context context, @@ -108,7 +110,8 @@ public class ResolverListAdapter extends BaseAdapter { UserHandle userHandle, Intent targetIntent, ResolverListCommunicator resolverListCommunicator, - boolean isAudioCaptureDevice) { + boolean isAudioCaptureDevice, + UserHandle initialIntentsUserSpace) { mContext = context; mIntents = payloadIntents; mInitialIntents = initialIntents; @@ -125,6 +128,7 @@ public class ResolverListAdapter extends BaseAdapter { final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); mPresentationFactory = new TargetPresentationGetter.Factory(mContext, mIconDpi); + mInitialIntentsUserSpace = initialIntentsUserSpace; } public final DisplayResolveInfo getFirstDisplayResolveInfo() { @@ -176,19 +180,25 @@ public class ResolverListAdapter extends BaseAdapter { } /** - * Returns the app share score of the given {@code componentName}. + * Returns the app share score of the given {@code targetInfo}. */ - public float getScore(ComponentName componentName) { - return mResolverListController.getScore(componentName); + public float getScore(TargetInfo targetInfo) { + return mResolverListController.getScore(targetInfo); } - public void updateModel(ComponentName componentName) { - mResolverListController.updateModel(componentName); + /** + * Updates the model about the chosen {@code targetInfo}. + */ + public void updateModel(TargetInfo targetInfo) { + mResolverListController.updateModel(targetInfo); } - public void updateChooserCounts(String packageName, String action) { + /** + * Updates the model about Chooser Activity selection. + */ + public void updateChooserCounts(String packageName, String action, UserHandle userHandle) { mResolverListController.updateChooserCounts( - packageName, getUserHandle().getIdentifier(), action); + packageName, userHandle, action); } List getUnfilteredResolveList() { @@ -468,6 +478,7 @@ public class ResolverListAdapter extends BaseAdapter { ri.icon = 0; } + ri.userHandle = mInitialIntentsUserSpace; addResolveInfo(DisplayResolveInfo.newDisplayResolveInfo( ii, ri, @@ -761,8 +772,10 @@ public class ResolverListAdapter extends BaseAdapter { } Drawable loadIconForResolveInfo(ResolveInfo ri) { - // Load icons based on the current process. If in work profile icons should be badged. - return mPresentationFactory.makePresentationGetter(ri).getIcon(getUserHandle()); + // Load icons based on userHandle from ResolveInfo. If in work profile/clone profile, icons + // should be badged. + return mPresentationFactory.makePresentationGetter(ri) + .getIcon(ResolverActivity.getResolveInfoUserHandle(ri, getUserHandle())); } protected final Drawable loadIconPlaceholder() { diff --git a/java/src/com/android/intentresolver/ResolverListController.java b/java/src/com/android/intentresolver/ResolverListController.java index b4544c43..d5a5fedf 100644 --- a/java/src/com/android/intentresolver/ResolverListController.java +++ b/java/src/com/android/intentresolver/ResolverListController.java @@ -32,8 +32,8 @@ import android.os.UserHandle; import android.util.Log; import com.android.intentresolver.chooser.DisplayResolveInfo; +import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.model.AbstractResolverComparator; -import com.android.intentresolver.model.ResolverRankerServiceResolverComparator; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; @@ -58,34 +58,26 @@ public class ResolverListController { private static final String TAG = "ResolverListController"; private static final boolean DEBUG = false; + private final UserHandle mQueryIntentsAsUser; private AbstractResolverComparator mResolverComparator; private boolean isComputed = false; - public ResolverListController( - Context context, - PackageManager pm, - Intent targetIntent, - String referrerPackage, - int launchedFromUid) { - this(context, pm, targetIntent, referrerPackage, launchedFromUid, - new ResolverRankerServiceResolverComparator( - context, targetIntent, referrerPackage, null, null)); - } - public ResolverListController( Context context, PackageManager pm, Intent targetIntent, String referrerPackage, int launchedFromUid, - AbstractResolverComparator resolverComparator) { + AbstractResolverComparator resolverComparator, + UserHandle queryIntentsAsUser) { mContext = context; mpm = pm; mLaunchedFromUid = launchedFromUid; mTargetIntent = targetIntent; mReferrerPackage = referrerPackage; mResolverComparator = resolverComparator; + mQueryIntentsAsUser = queryIntentsAsUser; } @VisibleForTesting @@ -118,7 +110,8 @@ public class ResolverListController { | PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0) - | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0); + | (shouldGetActivityMetadata ? PackageManager.GET_META_DATA : 0) + | PackageManager.MATCH_CLONE_PROFILE; return getResolversForIntentAsUserInternal(intents, userHandle, baseFlags); } @@ -154,6 +147,10 @@ public class ResolverListController { final int intoCount = into.size(); for (int i = 0; i < fromCount; i++) { final ResolveInfo newInfo = from.get(i); + if (newInfo.userHandle == null) { + Log.w(TAG, "Skipping ResolveInfo with no userHandle: " + newInfo); + continue; + } 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++) { @@ -344,22 +341,28 @@ public class ResolverListController { @VisibleForTesting public float getScore(DisplayResolveInfo target) { - return mResolverComparator.getScore(target.getResolvedComponentName()); + return mResolverComparator.getScore(target); } /** * Returns the app share score of the given {@code componentName}. */ - public float getScore(ComponentName componentName) { - return mResolverComparator.getScore(componentName); + public float getScore(TargetInfo targetInfo) { + return mResolverComparator.getScore(targetInfo); } - public void updateModel(ComponentName componentName) { - mResolverComparator.updateModel(componentName); + /** + * Updates the model about the chosen {@code targetInfo}. + */ + public void updateModel(TargetInfo targetInfo) { + mResolverComparator.updateModel(targetInfo); } - public void updateChooserCounts(String packageName, int userId, String action) { - mResolverComparator.updateChooserCounts(packageName, userId, action); + /** + * Updates the model about Chooser Activity selection. + */ + public void updateChooserCounts(String packageName, UserHandle user, String action) { + mResolverComparator.updateChooserCounts(packageName, user, action); } public void destroy() { diff --git a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java index 48e3b62d..85d97ad5 100644 --- a/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/ResolverMultiProfilePagerAdapter.java @@ -44,7 +44,8 @@ public class ResolverMultiProfilePagerAdapter extends ResolverListAdapter adapter, EmptyStateProvider emptyStateProvider, Supplier workProfileQuietModeChecker, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle) { this( context, ImmutableList.of(adapter), @@ -52,6 +53,7 @@ public class ResolverMultiProfilePagerAdapter extends workProfileQuietModeChecker, /* defaultProfile= */ 0, workProfileUserHandle, + cloneProfileUserHandle, new BottomPaddingOverrideSupplier()); } @@ -61,7 +63,8 @@ public class ResolverMultiProfilePagerAdapter extends EmptyStateProvider emptyStateProvider, Supplier workProfileQuietModeChecker, @Profile int defaultProfile, - UserHandle workProfileUserHandle) { + UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle) { this( context, ImmutableList.of(personalAdapter, workAdapter), @@ -69,6 +72,7 @@ public class ResolverMultiProfilePagerAdapter extends workProfileQuietModeChecker, defaultProfile, workProfileUserHandle, + cloneProfileUserHandle, new BottomPaddingOverrideSupplier()); } @@ -79,6 +83,7 @@ public class ResolverMultiProfilePagerAdapter extends Supplier workProfileQuietModeChecker, @Profile int defaultProfile, UserHandle workProfileUserHandle, + UserHandle cloneProfileUserHandle, BottomPaddingOverrideSupplier bottomPaddingOverrideSupplier) { super( context, @@ -89,6 +94,7 @@ public class ResolverMultiProfilePagerAdapter extends workProfileQuietModeChecker, defaultProfile, workProfileUserHandle, + cloneProfileUserHandle, () -> (ViewGroup) LayoutInflater.from(context).inflate( R.layout.resolver_list_per_profile, null, false), bottomPaddingOverrideSupplier); diff --git a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java index ea767568..7357fde9 100644 --- a/java/src/com/android/intentresolver/model/AbstractResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AbstractResolverComparator.java @@ -32,11 +32,16 @@ import android.util.Log; import com.android.intentresolver.ChooserActivityLogger; import com.android.intentresolver.ResolvedComponentInfo; import com.android.intentresolver.ResolverActivity; +import com.android.intentresolver.chooser.TargetInfo; + +import com.google.android.collect.Lists; import java.text.Collator; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Used to sort resolved activities in {@link ResolverListController}. @@ -50,8 +55,8 @@ public abstract class AbstractResolverComparator implements Comparator mPmMap = new HashMap<>(); + protected final Map mUsmMap = new HashMap<>(); protected String[] mAnnotations; protected String mContentType; @@ -100,14 +105,48 @@ public abstract class AbstractResolverComparator implements Comparator resolvedActivityUserSpaceList) { String scheme = intent.getScheme(); mHttp = "http".equals(scheme) || "https".equals(scheme); mContentType = intent.getType(); getContentAnnotations(intent); - mPm = context.getPackageManager(); - mUsm = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); - mAzComparator = new AzInfoComparator(context); + for (UserHandle user : resolvedActivityUserSpaceList) { + Context userContext = launchedFromContext.createContextAsUser(user, 0); + mPmMap.put(user, userContext.getPackageManager()); + mUsmMap.put( + user, + (UsageStatsManager) userContext.getSystemService(Context.USAGE_STATS_SERVICE)); + } + mAzComparator = new AzInfoComparator(launchedFromContext); } // get annotations of content from intent. @@ -197,8 +236,8 @@ public abstract class AbstractResolverComparator implements ComparatorDefault implementation does nothing, as we could have simple model that does not train * online. * - * @param componentName the component that the user clicked + * * @param targetInfo the target that the user clicked. */ - public void updateModel(ComponentName componentName) { + public void updateModel(TargetInfo targetInfo) { } /** Called before {@link #doCompute(List)}. Sets up 500ms timeout. */ diff --git a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java index c986ef15..84dca3ff 100644 --- a/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java +++ b/java/src/com/android/intentresolver/model/AppPredictionServiceResolverComparator.java @@ -33,6 +33,9 @@ import android.util.Log; import com.android.intentresolver.ChooserActivityLogger; import com.android.intentresolver.ResolvedComponentInfo; +import com.android.intentresolver.chooser.TargetInfo; + +import com.google.android.collect.Lists; import java.util.ArrayList; import java.util.Comparator; @@ -70,7 +73,7 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp AppPredictor appPredictor, UserHandle user, ChooserActivityLogger chooserActivityLogger) { - super(context, intent); + super(context, intent, Lists.newArrayList(user)); mContext = context; mIntent = intent; mAppPredictor = appPredictor; @@ -108,9 +111,12 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp // APS for chooser is disabled. Fallback to resolver. mResolverRankerService = new ResolverRankerServiceResolverComparator( - mContext, mIntent, mReferrerPackage, + mContext, + mIntent, + mReferrerPackage, () -> mHandler.sendEmptyMessage(RANKER_SERVICE_RESULT), - getChooserActivityLogger()); + getChooserActivityLogger(), + mUser); mComparatorModel = buildUpdatedModel(); mResolverRankerService.compute(targets); } else { @@ -167,13 +173,13 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp } @Override - public float getScore(ComponentName name) { - return mComparatorModel.getScore(name); + public float getScore(TargetInfo targetInfo) { + return mComparatorModel.getScore(targetInfo); } @Override - public void updateModel(ComponentName componentName) { - mComparatorModel.notifyOnTargetSelected(componentName); + public void updateModel(TargetInfo targetInfo) { + mComparatorModel.notifyOnTargetSelected(targetInfo); } @Override @@ -246,11 +252,11 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp } @Override - public float getScore(ComponentName name) { + public float getScore(TargetInfo targetInfo) { if (mResolverRankerService != null) { - return mResolverRankerService.getScore(name); + return mResolverRankerService.getScore(targetInfo); } - Integer rank = mTargetRanks.get(name); + Integer rank = mTargetRanks.get(targetInfo.getResolvedComponentName()); if (rank == null) { Log.w(TAG, "Score requested for unknown component. Did you call compute yet?"); return 0f; @@ -260,18 +266,19 @@ public class AppPredictionServiceResolverComparator extends AbstractResolverComp } @Override - public void notifyOnTargetSelected(ComponentName componentName) { + public void notifyOnTargetSelected(TargetInfo targetInfo) { if (mResolverRankerService != null) { - mResolverRankerService.updateModel(componentName); + mResolverRankerService.updateModel(targetInfo); return; } + ComponentName targetComponent = targetInfo.getResolvedComponentName(); + AppTargetId targetId = new AppTargetId(targetComponent.toString()); + AppTarget appTarget = + new AppTarget.Builder(targetId, targetComponent.getPackageName(), mUser) + .setClassName(targetComponent.getClassName()) + .build(); mAppPredictor.notifyAppTargetEvent( - new AppTargetEvent.Builder( - new AppTarget.Builder( - new AppTargetId(componentName.toString()), - componentName.getPackageName(), mUser) - .setClassName(componentName.getClassName()).build(), - ACTION_LAUNCH).build()); + new AppTargetEvent.Builder(appTarget, ACTION_LAUNCH).build()); } } } diff --git a/java/src/com/android/intentresolver/model/ResolverComparatorModel.java b/java/src/com/android/intentresolver/model/ResolverComparatorModel.java index 3616a853..4835ea17 100644 --- a/java/src/com/android/intentresolver/model/ResolverComparatorModel.java +++ b/java/src/com/android/intentresolver/model/ResolverComparatorModel.java @@ -16,9 +16,10 @@ package com.android.intentresolver.model; -import android.content.ComponentName; import android.content.pm.ResolveInfo; +import com.android.intentresolver.chooser.TargetInfo; + import java.util.Comparator; /** @@ -44,7 +45,7 @@ interface ResolverComparatorModel { * likelihood that the user will select that component as the target. Implementations that don't * assign numerical scores are recommended to return a value of 0 for all components. */ - float getScore(ComponentName name); + float getScore(TargetInfo targetInfo); /** * Notify the model that the user selected a target. (Models may log this information, use it as @@ -52,5 +53,5 @@ interface ResolverComparatorModel { * {@code ResolverComparatorModel} instance is immutable, clients will need to get an up-to-date * instance in order to see any changes in the ranking that might result from this feedback. */ - void notifyOnTargetSelected(ComponentName componentName); + void notifyOnTargetSelected(TargetInfo targetInfo); } diff --git a/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java b/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java index 0431078c..725212e4 100644 --- a/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java +++ b/java/src/com/android/intentresolver/model/ResolverRankerServiceResolverComparator.java @@ -17,11 +17,13 @@ package com.android.intentresolver.model; +import android.annotation.Nullable; import android.app.usage.UsageStats; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -39,12 +41,16 @@ import android.util.Log; import com.android.intentresolver.ChooserActivityLogger; import com.android.intentresolver.ResolvedComponentInfo; +import com.android.intentresolver.chooser.TargetInfo; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.google.android.collect.Lists; + import java.text.Collator; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -70,10 +76,10 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom private static final int CONNECTION_COST_TIMEOUT_MILLIS = 200; private final Collator mCollator; - private final Map mStats; + private final Map> mStatsPerUser; private final long mCurrentTime; private final long mSinceTime; - private final LinkedHashMap mTargetsDict = new LinkedHashMap<>(); + private final Map> mTargetsDictPerUser; private final String mReferrerPackage; private final Object mLock = new Object(); private ArrayList mTargets; @@ -86,17 +92,48 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom private CountDownLatch mConnectSignal; private ResolverRankerServiceComparatorModel mComparatorModel; - public ResolverRankerServiceResolverComparator(Context context, Intent intent, - String referrerPackage, Runnable afterCompute, - ChooserActivityLogger chooserActivityLogger) { - super(context, intent); - mCollator = Collator.getInstance(context.getResources().getConfiguration().locale); + /** + * Constructor to initialize the comparator. + * @param launchedFromContext the activity calling this comparator + * @param intent original intent + * @param targetUserSpace the userSpace(s) used by the comparator for fetching activity stats + * and recording activity selection. The latter could be different from + * the userSpace provided by context. + */ + public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent, + String referrerPackage, Runnable afterCompute, + ChooserActivityLogger chooserActivityLogger, UserHandle targetUserSpace) { + this(launchedFromContext, intent, referrerPackage, afterCompute, chooserActivityLogger, + Lists.newArrayList(targetUserSpace)); + } + + /** + * Constructor to initialize the comparator. + * @param launchedFromContext the activity calling this comparator + * @param intent original intent + * @param targetUserSpaceList the userSpace(s) used by the comparator for fetching activity + * stats and recording activity selection. The latter could be + * different from the userSpace provided by context. + */ + public ResolverRankerServiceResolverComparator(Context launchedFromContext, Intent intent, + String referrerPackage, Runnable afterCompute, + ChooserActivityLogger chooserActivityLogger, List targetUserSpaceList) { + super(launchedFromContext, intent, targetUserSpaceList); + mCollator = Collator.getInstance( + launchedFromContext.getResources().getConfiguration().locale); mReferrerPackage = referrerPackage; - mContext = context; + mContext = launchedFromContext; mCurrentTime = System.currentTimeMillis(); mSinceTime = mCurrentTime - USAGE_STATS_PERIOD; - mStats = mUsm.queryAndAggregateUsageStats(mSinceTime, mCurrentTime); + mStatsPerUser = new HashMap<>(); + mTargetsDictPerUser = new HashMap<>(); + for (UserHandle user : targetUserSpaceList) { + mStatsPerUser.put( + user, + mUsmMap.get(user).queryAndAggregateUsageStats(mSinceTime, mCurrentTime)); + mTargetsDictPerUser.put(user, new LinkedHashMap<>()); + } mAction = intent.getAction(); mRankerServiceName = new ComponentName(mContext, this.getClass()); setCallBack(afterCompute); @@ -147,58 +184,68 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom float mostChooserScore = 1.0f; for (ResolvedComponentInfo target : targets) { + if (target.getResolveInfoAt(0) == null) { + continue; + } final ResolverTarget resolverTarget = new ResolverTarget(); - mTargetsDict.put(target.name, resolverTarget); - final UsageStats pkStats = mStats.get(target.name.getPackageName()); - if (pkStats != null) { - // Only count recency for apps that weren't the caller - // since the caller is always the most recent. - // Persistent processes muck this up, so omit them too. - if (!target.name.getPackageName().equals(mReferrerPackage) - && !isPersistentProcess(target)) { - final float recencyScore = - (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0); - resolverTarget.setRecencyScore(recencyScore); - if (recencyScore > mostRecencyScore) { - mostRecencyScore = recencyScore; + final UserHandle resolvedComponentUserSpace = + target.getResolveInfoAt(0).userHandle; + final Map targetsDict = + mTargetsDictPerUser.get(resolvedComponentUserSpace); + final Map stats = mStatsPerUser.get(resolvedComponentUserSpace); + if (targetsDict != null && stats != null) { + targetsDict.put(target.name, resolverTarget); + final UsageStats pkStats = stats.get(target.name.getPackageName()); + if (pkStats != null) { + // Only count recency for apps that weren't the caller + // since the caller is always the most recent. + // Persistent processes muck this up, so omit them too. + if (!target.name.getPackageName().equals(mReferrerPackage) + && !isPersistentProcess(target)) { + final float recencyScore = + (float) Math.max(pkStats.getLastTimeUsed() - recentSinceTime, 0); + resolverTarget.setRecencyScore(recencyScore); + if (recencyScore > mostRecencyScore) { + mostRecencyScore = recencyScore; + } + } + final float timeSpentScore = (float) pkStats.getTotalTimeInForeground(); + resolverTarget.setTimeSpentScore(timeSpentScore); + if (timeSpentScore > mostTimeSpentScore) { + mostTimeSpentScore = timeSpentScore; + } + final float launchScore = (float) pkStats.mLaunchCount; + resolverTarget.setLaunchScore(launchScore); + if (launchScore > mostLaunchScore) { + mostLaunchScore = launchScore; } - } - final float timeSpentScore = (float) pkStats.getTotalTimeInForeground(); - resolverTarget.setTimeSpentScore(timeSpentScore); - if (timeSpentScore > mostTimeSpentScore) { - mostTimeSpentScore = timeSpentScore; - } - final float launchScore = (float) pkStats.mLaunchCount; - resolverTarget.setLaunchScore(launchScore); - if (launchScore > mostLaunchScore) { - mostLaunchScore = launchScore; - } - float chooserScore = 0.0f; - if (pkStats.mChooserCounts != null && mAction != null - && pkStats.mChooserCounts.get(mAction) != null) { - chooserScore = (float) pkStats.mChooserCounts.get(mAction) - .getOrDefault(mContentType, 0); - if (mAnnotations != null) { - final int size = mAnnotations.length; - for (int i = 0; i < size; i++) { - chooserScore += (float) pkStats.mChooserCounts.get(mAction) - .getOrDefault(mAnnotations[i], 0); + float chooserScore = 0.0f; + if (pkStats.mChooserCounts != null && mAction != null + && pkStats.mChooserCounts.get(mAction) != null) { + chooserScore = (float) pkStats.mChooserCounts.get(mAction) + .getOrDefault(mContentType, 0); + if (mAnnotations != null) { + final int size = mAnnotations.length; + for (int i = 0; i < size; i++) { + chooserScore += (float) pkStats.mChooserCounts.get(mAction) + .getOrDefault(mAnnotations[i], 0); + } } } - } - if (DEBUG) { - if (mAction == null) { - Log.d(TAG, "Action type is null"); - } else { - Log.d(TAG, "Chooser Count of " + mAction + ":" - + target.name.getPackageName() + " is " - + Float.toString(chooserScore)); + if (DEBUG) { + if (mAction == null) { + Log.d(TAG, "Action type is null"); + } else { + Log.d(TAG, "Chooser Count of " + mAction + ":" + + target.name.getPackageName() + " is " + + Float.toString(chooserScore)); + } + } + resolverTarget.setChooserScore(chooserScore); + if (chooserScore > mostChooserScore) { + mostChooserScore = chooserScore; } - } - resolverTarget.setChooserScore(chooserScore); - if (chooserScore > mostChooserScore) { - mostChooserScore = chooserScore; } } } @@ -210,7 +257,10 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom + " mostChooserScore: " + mostChooserScore); } - mTargets = new ArrayList<>(mTargetsDict.values()); + mTargets = new ArrayList<>(); + for (UserHandle u : mTargetsDictPerUser.keySet()) { + mTargets.addAll(mTargetsDictPerUser.get(u).values()); + } for (ResolverTarget target : mTargets) { final float recency = target.getRecencyScore() / mostRecencyScore; setFeatures(target, recency * recency * RECENCY_MULTIPLIER, @@ -233,15 +283,15 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom } @Override - public float getScore(ComponentName name) { - return mComparatorModel.getScore(name); + public float getScore(TargetInfo targetInfo) { + return mComparatorModel.getScore(targetInfo); } // update ranking model when the connection to it is valid. @Override - public void updateModel(ComponentName componentName) { + public void updateModel(TargetInfo targetInfo) { synchronized (mLock) { - mComparatorModel.notifyOnTargetSelected(componentName); + mComparatorModel.notifyOnTargetSelected(targetInfo); } } @@ -282,7 +332,8 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom // resolve the service for ranking. private Intent resolveRankerService() { Intent intent = new Intent(ResolverRankerService.SERVICE_INTERFACE); - final List resolveInfos = mPm.queryIntentServices(intent, 0); + final List resolveInfos = mContext.getPackageManager() + .queryIntentServices(intent, 0); for (ResolveInfo resolveInfo : resolveInfos) { if (resolveInfo == null || resolveInfo.serviceInfo == null || resolveInfo.serviceInfo.applicationInfo == null) { @@ -295,7 +346,8 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom resolveInfo.serviceInfo.applicationInfo.packageName, resolveInfo.serviceInfo.name); try { - final String perm = mPm.getServiceInfo(componentName, 0).permission; + final String perm = + mContext.getPackageManager().getServiceInfo(componentName, 0).permission; if (!ResolverRankerService.BIND_PERMISSION.equals(perm)) { Log.w(TAG, "ResolverRankerService " + componentName + " does not require" + " permission " + ResolverRankerService.BIND_PERMISSION @@ -306,9 +358,9 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom + " in the manifest."); continue; } - if (PackageManager.PERMISSION_GRANTED != mPm.checkPermission( - ResolverRankerService.HOLD_PERMISSION, - resolveInfo.serviceInfo.packageName)) { + if (PackageManager.PERMISSION_GRANTED != mContext.getPackageManager() + .checkPermission(ResolverRankerService.HOLD_PERMISSION, + resolveInfo.serviceInfo.packageName)) { Log.w(TAG, "ResolverRankerService " + componentName + " does not hold" + " permission " + ResolverRankerService.HOLD_PERMISSION + " - this service will not be queried for " @@ -386,7 +438,9 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom @Override void beforeCompute() { super.beforeCompute(); - mTargetsDict.clear(); + for (UserHandle userHandle : mTargetsDictPerUser.keySet()) { + mTargetsDictPerUser.get(userHandle).clear(); + } mTargets = null; mRankerServiceName = new ComponentName(mContext, this.getClass()); mComparatorModel = buildUpdatedModel(); @@ -468,14 +522,14 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom // so the ResolverComparatorModel may provide inconsistent results. We should make immutable // copies of the data (waiting for any necessary remaining data before creating the model). return new ResolverRankerServiceComparatorModel( - mStats, - mTargetsDict, + mStatsPerUser, + mTargetsDictPerUser, mTargets, mCollator, mRanker, mRankerServiceName, (mAnnotations != null), - mPm); + mPmMap); } /** @@ -484,35 +538,36 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom * removing the complex legacy API. */ static class ResolverRankerServiceComparatorModel implements ResolverComparatorModel { - private final Map mStats; // Treat as immutable. - private final Map mTargetsDict; // Treat as immutable. + private final Map> mStatsPerUser; // Treat as immutable. + // Treat as immutable. + private final Map> mTargetsDictPerUser; private final List mTargets; // Treat as immutable. private final Collator mCollator; private final IResolverRankerService mRanker; private final ComponentName mRankerServiceName; private final boolean mAnnotationsUsed; - private final PackageManager mPm; + private final Map mPmMap; // TODO: it doesn't look like we should have to pass both targets and targetsDict, but it's // not written in a way that makes it clear whether we can derive one from the other (at // least in this constructor). ResolverRankerServiceComparatorModel( - Map stats, - Map targetsDict, + Map> statsPerUser, + Map> targetsDictPerUser, List targets, Collator collator, IResolverRankerService ranker, ComponentName rankerServiceName, boolean annotationsUsed, - PackageManager pm) { - mStats = stats; - mTargetsDict = targetsDict; + Map pmMap) { + mStatsPerUser = statsPerUser; + mTargetsDictPerUser = targetsDictPerUser; mTargets = targets; mCollator = collator; mRanker = ranker; mRankerServiceName = rankerServiceName; mAnnotationsUsed = annotationsUsed; - mPm = pm; + mPmMap = pmMap; } @Override @@ -521,25 +576,29 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom // a bug there, or do we have a way of knowing it will be non-null under certain // conditions? return (lhs, rhs) -> { - if (mStats != null) { - final ResolverTarget lhsTarget = mTargetsDict.get(new ComponentName( - lhs.activityInfo.packageName, lhs.activityInfo.name)); - final ResolverTarget rhsTarget = mTargetsDict.get(new ComponentName( - rhs.activityInfo.packageName, rhs.activityInfo.name)); - - if (lhsTarget != null && rhsTarget != null) { - final int selectProbabilityDiff = Float.compare( - rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability()); - - if (selectProbabilityDiff != 0) { - return selectProbabilityDiff > 0 ? 1 : -1; - } + final ResolverTarget lhsTarget = + getActivityResolverTargetForUser(lhs.activityInfo, lhs.userHandle); + final ResolverTarget rhsTarget = + getActivityResolverTargetForUser(rhs.activityInfo, rhs.userHandle); + + if (lhsTarget != null && rhsTarget != null) { + final int selectProbabilityDiff = Float.compare( + rhsTarget.getSelectProbability(), lhsTarget.getSelectProbability()); + + if (selectProbabilityDiff != 0) { + return selectProbabilityDiff > 0 ? 1 : -1; } } - CharSequence sa = lhs.loadLabel(mPm); + CharSequence sa = null; + if (mPmMap.containsKey(lhs.userHandle)) { + sa = lhs.loadLabel(mPmMap.get(lhs.userHandle)); + } if (sa == null) sa = lhs.activityInfo.name; - CharSequence sb = rhs.loadLabel(mPm); + CharSequence sb = null; + if (mPmMap.containsKey(rhs.userHandle)) { + sb = rhs.loadLabel(mPmMap.get(rhs.userHandle)); + } if (sb == null) sb = rhs.activityInfo.name; return mCollator.compare(sa.toString().trim(), sb.toString().trim()); @@ -547,8 +606,9 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom } @Override - public float getScore(ComponentName name) { - final ResolverTarget target = mTargetsDict.get(name); + public float getScore(TargetInfo targetInfo) { + ResolverTarget target = getResolverTargetForUserAndComponent( + targetInfo.getResolvedComponentName(), targetInfo.getResolveInfo().userHandle); if (target != null) { return target.getSelectProbability(); } @@ -556,13 +616,17 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom } @Override - public void notifyOnTargetSelected(ComponentName componentName) { + public void notifyOnTargetSelected(TargetInfo targetInfo) { if (mRanker != null) { try { - int selectedPos = new ArrayList(mTargetsDict.keySet()) - .indexOf(componentName); + int selectedPos = -1; + if (mTargetsDictPerUser.containsKey(targetInfo.getResolveInfo().userHandle)) { + selectedPos = new ArrayList<>(mTargetsDictPerUser + .get(targetInfo.getResolveInfo().userHandle).keySet()) + .indexOf(targetInfo.getResolvedComponentName()); + } if (selectedPos >= 0 && mTargets != null) { - final float selectedProbability = getScore(componentName); + final float selectedProbability = getScore(targetInfo); int order = 0; for (ResolverTarget target : mTargets) { if (target.getSelectProbability() > selectedProbability) { @@ -573,7 +637,8 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom mRanker.train(mTargets, selectedPos); } else { if (DEBUG) { - Log.d(TAG, "Selected a unknown component: " + componentName); + Log.d(TAG, "Selected a unknown component: " + targetInfo + .getResolvedComponentName()); } } } catch (RemoteException e) { @@ -597,5 +662,21 @@ public class ResolverRankerServiceResolverComparator extends AbstractResolverCom metricsLogger.write(log); } } + + @Nullable + private ResolverTarget getActivityResolverTargetForUser( + ActivityInfo activity, UserHandle user) { + return getResolverTargetForUserAndComponent( + new ComponentName(activity.packageName, activity.name), user); + } + + @Nullable + private ResolverTarget getResolverTargetForUserAndComponent( + ComponentName targetComponentName, UserHandle user) { + if ((mStatsPerUser == null) || !mTargetsDictPerUser.containsKey(user)) { + return null; + } + return mTargetsDictPerUser.get(user).get(targetComponentName); + } } } -- cgit v1.2.3-59-g8ed1b