diff options
Diffstat (limited to 'java')
13 files changed, 499 insertions, 145 deletions
diff --git a/java/res/values/attrs.xml b/java/res/values/attrs.xml index 3ec7c2f3..2f2bbda2 100644 --- a/java/res/values/attrs.xml +++ b/java/res/values/attrs.xml @@ -26,6 +26,12 @@ <attr name="maxCollapsedHeightSmall" format="dimension" /> <!-- Whether the Drawer should be positioned at the top rather than at the bottom. --> <attr name="showAtTop" format="boolean" /> + <!-- By default `ResolverDrawerLayout`’s children views with `layout_ignoreOffset` property + set to true have a fixed position in the layout that won’t be affected by the drawer’s + movements. This property alternates that behavior. It specifies a child view’s id that + will push all ignoreOffset siblings below it when the drawer is moved i.e. setting the + top limit the ignoreOffset elements. --> + <attr name="ignoreOffsetTopLimit" format="reference" /> </declare-styleable> <declare-styleable name="ResolverDrawerLayout_LayoutParams"> diff --git a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java index 3e1084f4..8b0b10b0 100644 --- a/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java +++ b/java/src/com/android/intentresolver/AbstractMultiProfilePagerAdapter.java @@ -148,7 +148,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { @VisibleForTesting public UserHandle getCurrentUserHandle() { - return getActiveListAdapter().mResolverListController.getUserHandle(); + return getActiveListAdapter().getUserHandle(); } @Override @@ -263,7 +263,7 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter { } private int userHandleToPageIndex(UserHandle userHandle) { - if (userHandle.equals(getPersonalListAdapter().mResolverListController.getUserHandle())) { + if (userHandle.equals(getPersonalListAdapter().getUserHandle())) { return PROFILE_PERSONAL; } else { return PROFILE_WORK; diff --git a/java/src/com/android/intentresolver/ChooserActivity.java b/java/src/com/android/intentresolver/ChooserActivity.java index afc3b4bd..ba121050 100644 --- a/java/src/com/android/intentresolver/ChooserActivity.java +++ b/java/src/com/android/intentresolver/ChooserActivity.java @@ -147,7 +147,6 @@ import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Consumer; -import java.util.function.Supplier; /** * The Chooser Activity handles intent resolution specifically for sharing intents - @@ -350,6 +349,12 @@ public class ChooserActivity extends ResolverActivity implements mChooserRequest.getTargetIntentFilter()), mChooserRequest.getTargetIntentFilter()); + mPreviewCoordinator = new ChooserContentPreviewCoordinator( + mBackgroundThreadPoolExecutor, + this, + this::hideContentPreview, + this::setupPreDrawForSharedElementTransition); + super.onCreate( savedInstanceState, mChooserRequest.getTargetIntent(), @@ -359,12 +364,6 @@ public class ChooserActivity extends ResolverActivity implements /* rList: List<ResolveInfo> = */ null, /* supportsAlwaysUseOption = */ false); - mPreviewCoordinator = new ChooserContentPreviewCoordinator( - mBackgroundThreadPoolExecutor, - this, - this::hideContentPreview, - this::setupPreDrawForSharedElementTransition); - mChooserShownTime = System.currentTimeMillis(); final long systemCost = mChooserShownTime - intentReceivedTime; @@ -1152,7 +1151,7 @@ public class ChooserActivity extends ResolverActivity implements if (mChooserRequest.getCallerChooserTargets().size() > 0) { mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults( /* origTarget */ null, - mChooserRequest.getCallerChooserTargets(), + new ArrayList<>(mChooserRequest.getCallerChooserTargets()), TARGET_TYPE_DEFAULT, /* directShareShortcutInfoCache */ Collections.emptyMap(), /* directShareAppTargetCache */ Collections.emptyMap()); @@ -1350,11 +1349,11 @@ public class ChooserActivity extends ResolverActivity implements targetInfo.getChooserTargetComponentName().getPackageName(); ChooserListAdapter currentListAdapter = mChooserMultiProfilePagerAdapter.getActiveListAdapter(); - int maxRankedResults = Math.min(currentListAdapter.mDisplayList.size(), - MAX_LOG_RANK_POSITION); + int maxRankedResults = Math.min( + currentListAdapter.getDisplayResolveInfoCount(), MAX_LOG_RANK_POSITION); for (int i = 0; i < maxRankedResults; i++) { - if (currentListAdapter.mDisplayList.get(i) + if (currentListAdapter.getDisplayResolveInfo(i) .getResolveInfo().activityInfo.packageName.equals(targetPackageName)) { return i; } @@ -1618,9 +1617,82 @@ public class ChooserActivity extends ResolverActivity implements rList, filterLastUsed, createListController(userHandle), + userHandle, + getTargetIntent(), mChooserRequest, mMaxTargetsPerRow); - return new ChooserGridAdapter(chooserListAdapter); + + return new ChooserGridAdapter( + context, + new ChooserGridAdapter.ChooserActivityDelegate() { + @Override + public boolean shouldShowTabs() { + return ChooserActivity.this.shouldShowTabs(); + } + + @Override + public View buildContentPreview(ViewGroup parent) { + return createContentPreviewView(parent, mPreviewCoordinator); + } + + @Override + public void onTargetSelected(int itemIndex) { + startSelected(itemIndex, false, true); + } + + @Override + public void onTargetLongPressed(int selectedPosition) { + final TargetInfo longPressedTargetInfo = + mChooserMultiProfilePagerAdapter + .getActiveListAdapter() + .targetInfoForPosition( + selectedPosition, /* filtered= */ true); + // ItemViewHolder contents should always be "display resolve info" + // targets, but check just to make sure. + if (longPressedTargetInfo.isDisplayResolveInfo()) { + showTargetDetails(longPressedTargetInfo); + } + } + + @Override + public void updateProfileViewButton(View newButtonFromProfileRow) { + mProfileView = newButtonFromProfileRow; + mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick); + ChooserActivity.this.updateProfileViewButton(); + } + + @Override + public int getValidTargetCount() { + return mChooserMultiProfilePagerAdapter + .getActiveListAdapter() + .getSelectableServiceTargetCount(); + } + + @Override + public void updateDirectShareExpansion(DirectShareViewHolder directShareGroup) { + RecyclerView activeAdapterView = + mChooserMultiProfilePagerAdapter.getActiveAdapterView(); + if (mResolverDrawerLayout.isCollapsed()) { + directShareGroup.collapse(activeAdapterView); + } else { + directShareGroup.expand(activeAdapterView); + } + } + + @Override + public void handleScrollToExpandDirectShare( + DirectShareViewHolder directShareGroup, int y, int oldy) { + directShareGroup.handleScroll( + mChooserMultiProfilePagerAdapter.getActiveAdapterView(), + y, + oldy, + mMaxTargetsPerRow); + } + }, + chooserListAdapter, + shouldShowContentPreview(), + mMaxTargetsPerRow, + getNumSheetExpansions()); } @VisibleForTesting @@ -1631,6 +1703,8 @@ public class ChooserActivity extends ResolverActivity implements List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, ChooserRequestParameters chooserRequest, int maxTargetsPerRow) { return new ChooserListAdapter( @@ -1640,6 +1714,8 @@ public class ChooserActivity extends ResolverActivity implements rList, filterLastUsed, resolverListController, + userHandle, + targetIntent, this, context.getPackageManager(), getChooserActivityLogger(), @@ -1889,8 +1965,7 @@ public class ChooserActivity extends ResolverActivity implements .setupListAdapter(mChooserMultiProfilePagerAdapter.getCurrentPage()); } - if (chooserListAdapter.mDisplayList == null - || chooserListAdapter.mDisplayList.isEmpty()) { + if (chooserListAdapter.getDisplayResolveInfoCount() == 0) { chooserListAdapter.notifyDataSetChanged(); } else { chooserListAdapter.updateAlphabeticalList(); @@ -2191,15 +2266,63 @@ public class ChooserActivity extends ResolverActivity implements * handled by {@link ChooserListAdapter} */ @VisibleForTesting - public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { - private final ChooserListAdapter mChooserListAdapter; - private final LayoutInflater mLayoutInflater; - private final boolean mShowAzLabelIfPoss; - - private DirectShareViewHolder mDirectShareViewHolder; - private int mChooserTargetWidth = 0; + public static final class ChooserGridAdapter extends + RecyclerView.Adapter<RecyclerView.ViewHolder> { - private int mFooterHeight = 0; + /** + * Injectable interface for any considerations that should be delegated to other components + * in the {@link ChooserActivity}. + * TODO: determine whether any of these methods return parameters that can safely be + * precomputed; whether any should be converted to `ChooserGridAdapter` setters to be + * invoked by external callbacks; and whether any reflect requirements that should be moved + * out of `ChooserGridAdapter` altogether. + */ + interface ChooserActivityDelegate { + /** @return whether we're showing a tabbed (multi-profile) UI. */ + boolean shouldShowTabs(); + + /** + * @return a content preview {@link View} that's appropriate for the caller's share + * content, constructed for display in the provided {@code parent} group. + */ + View buildContentPreview(ViewGroup parent); + + /** Notify the client that the item with the selected {@code itemIndex} was selected. */ + void onTargetSelected(int itemIndex); + + /** + * Notify the client that the item with the selected {@code itemIndex} was + * long-pressed. + */ + void onTargetLongPressed(int itemIndex); + + /** + * Notify the client that the provided {@code View} should be configured as the new + * "profile view" button. Callers should attach their own click listeners to implement + * behaviors on this view. + */ + void updateProfileViewButton(View newButtonFromProfileRow); + + /** + * @return the number of "valid" targets in the active list adapter. + * TODO: define "valid." + */ + int getValidTargetCount(); + + /** + * Request that the client update our {@code directShareGroup} to match their desired + * state for the "expansion" UI. + */ + void updateDirectShareExpansion(DirectShareViewHolder directShareGroup); + + /** + * Request that the client handle a scroll event that should be taken as expanding the + * provided {@code directShareGroup}. Note that this currently never happens due to a + * hard-coded condition in {@link #canExpandDirectShare()}. + */ + void handleScrollToExpandDirectShare( + DirectShareViewHolder directShareGroup, int y, int oldy); + } private static final int VIEW_TYPE_DIRECT_SHARE = 0; private static final int VIEW_TYPE_NORMAL = 1; @@ -2211,12 +2334,44 @@ public class ChooserActivity extends ResolverActivity implements private static final int NUM_EXPANSIONS_TO_HIDE_AZ_LABEL = 20; - ChooserGridAdapter(ChooserListAdapter wrappedAdapter) { + private final ChooserActivityDelegate mChooserActivityDelegate; + private final ChooserListAdapter mChooserListAdapter; + private final LayoutInflater mLayoutInflater; + + private final int mMaxTargetsPerRow; + private final boolean mShouldShowContentPreview; + private final int mChooserWidthPixels; + private final int mChooserRowTextOptionTranslatePixelSize; + private final boolean mShowAzLabelIfPoss; + + private DirectShareViewHolder mDirectShareViewHolder; + private int mChooserTargetWidth = 0; + + private int mFooterHeight = 0; + + ChooserGridAdapter( + Context context, + ChooserActivityDelegate chooserActivityDelegate, + ChooserListAdapter wrappedAdapter, + boolean shouldShowContentPreview, + int maxTargetsPerRow, + int numSheetExpansions) { super(); + + mChooserActivityDelegate = chooserActivityDelegate; + mChooserListAdapter = wrappedAdapter; - mLayoutInflater = LayoutInflater.from(ChooserActivity.this); + mLayoutInflater = LayoutInflater.from(context); + + mShouldShowContentPreview = shouldShowContentPreview; + mMaxTargetsPerRow = maxTargetsPerRow; - mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL; + mChooserWidthPixels = context.getResources().getDimensionPixelSize( + R.dimen.chooser_width); + mChooserRowTextOptionTranslatePixelSize = context.getResources().getDimensionPixelSize( + R.dimen.chooser_row_text_option_translate); + + mShowAzLabelIfPoss = numSheetExpansions < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL; wrappedAdapter.registerDataSetObserver(new DataSetObserver() { @Override @@ -2249,7 +2404,7 @@ public class ChooserActivity extends ResolverActivity implements } // Limit width to the maximum width of the chooser activity - int maxWidth = getResources().getDimensionPixelSize(R.dimen.chooser_width); + int maxWidth = mChooserWidthPixels; width = Math.min(maxWidth, width); int newWidth = width / mMaxTargetsPerRow; @@ -2281,11 +2436,11 @@ public class ChooserActivity extends ResolverActivity implements public int getSystemRowCount() { // For the tabbed case we show the sticky content preview above the tabs, // please refer to shouldShowStickyContentPreview - if (shouldShowTabs()) { + if (mChooserActivityDelegate.shouldShowTabs()) { return 0; } - if (!shouldShowContentPreview()) { + if (!mShouldShowContentPreview) { return 0; } @@ -2297,7 +2452,7 @@ public class ChooserActivity extends ResolverActivity implements } public int getProfileRowCount() { - if (shouldShowTabs()) { + if (mChooserActivityDelegate.shouldShowTabs()) { return 0; } return mChooserListAdapter.getOtherProfile() == null ? 0 : 1; @@ -2316,8 +2471,7 @@ public class ChooserActivity extends ResolverActivity implements // There can be at most one row in the listview, that is internally // a ViewGroup with 2 rows public int getServiceTargetRowCount() { - if (shouldShowContentPreview() - && !ActivityManager.isLowRamDeviceStatic()) { + if (mShouldShowContentPreview && !ActivityManager.isLowRamDeviceStatic()) { return 1; } return 0; @@ -2346,7 +2500,7 @@ public class ChooserActivity extends ResolverActivity implements switch (viewType) { case VIEW_TYPE_CONTENT_PREVIEW: return new ItemViewHolder( - createContentPreviewView(parent, mPreviewCoordinator), + mChooserActivityDelegate.buildContentPreview(parent), viewType, null, null); @@ -2366,22 +2520,8 @@ public class ChooserActivity extends ResolverActivity implements return new ItemViewHolder( mChooserListAdapter.createView(parent), viewType, - selectedPosition -> startSelected( - selectedPosition, - /* always= */ false, - /* filtered= */ true), - selectedPosition -> { - final TargetInfo longPressedTargetInfo = - mChooserMultiProfilePagerAdapter - .getActiveListAdapter() - .targetInfoForPosition( - selectedPosition, /* filtered= */ true); - // ItemViewHolder contents should always be "display resolve info" - // targets, but check just to make sure. - if (longPressedTargetInfo.isDisplayResolveInfo()) { - showTargetDetails(longPressedTargetInfo); - } - }); + mChooserActivityDelegate::onTargetSelected, + mChooserActivityDelegate::onTargetLongPressed); case VIEW_TYPE_DIRECT_SHARE: case VIEW_TYPE_CALLER_AND_RANK: return createItemGroupViewHolder(viewType, parent); @@ -2441,9 +2581,7 @@ public class ChooserActivity extends ResolverActivity implements private View createProfileView(ViewGroup parent) { View profileRow = mLayoutInflater.inflate(R.layout.chooser_profile_row, parent, false); - mProfileView = profileRow.findViewById(com.android.internal.R.id.profile_button); - mProfileView.setOnClickListener(ChooserActivity.this::onProfileClick); - updateProfileViewButton(); + mChooserActivityDelegate.updateProfileViewButton(profileRow); return profileRow; } @@ -2465,15 +2603,13 @@ public class ChooserActivity extends ResolverActivity implements v.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - startSelected(holder.getItemIndex(column), false, true); + mChooserActivityDelegate.onTargetSelected(holder.getItemIndex(column)); } }); // Show menu for both direct share and app share targets after long click. v.setOnLongClickListener(v1 -> { - TargetInfo ti = mChooserListAdapter.targetInfoForPosition( - holder.getItemIndex(column), true); - showTargetDetails(ti); + mChooserActivityDelegate.onTargetLongPressed(holder.getItemIndex(column)); return true; }); @@ -2534,7 +2670,7 @@ public class ChooserActivity extends ResolverActivity implements mDirectShareViewHolder = new DirectShareViewHolder(parentGroup, Lists.newArrayList(row1, row2), mMaxTargetsPerRow, viewType, - mChooserMultiProfilePagerAdapter::getActiveListAdapter); + mChooserActivityDelegate::getValidTargetCount); loadViewsIntoGroup(mDirectShareViewHolder); return mDirectShareViewHolder; @@ -2600,9 +2736,7 @@ public class ChooserActivity extends ResolverActivity implements ValueAnimator fadeAnim = ObjectAnimator.ofFloat(textView, "alpha", 0.0f, 1.0f); fadeAnim.setInterpolator(new DecelerateInterpolator(1.0f)); - float translationInPx = getResources().getDimensionPixelSize( - R.dimen.chooser_row_text_option_translate); - textView.setTranslationY(translationInPx); + textView.setTranslationY(mChooserRowTextOptionTranslatePixelSize); ValueAnimator translateAnim = ObjectAnimator.ofFloat(textView, "translationY", 0.0f); translateAnim.setInterpolator(new DecelerateInterpolator(1.0f)); @@ -2654,9 +2788,8 @@ public class ChooserActivity extends ResolverActivity implements public void handleScroll(View v, int y, int oldy) { boolean canExpandDirectShare = canExpandDirectShare(); if (mDirectShareViewHolder != null && canExpandDirectShare) { - mDirectShareViewHolder.handleScroll( - mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy, - mMaxTargetsPerRow); + mChooserActivityDelegate.handleScrollToExpandDirectShare( + mDirectShareViewHolder, y, oldy); } } @@ -2681,13 +2814,7 @@ public class ChooserActivity extends ResolverActivity implements if (mDirectShareViewHolder == null || !canExpandDirectShare()) { return; } - RecyclerView activeAdapterView = - mChooserMultiProfilePagerAdapter.getActiveAdapterView(); - if (mResolverDrawerLayout.isCollapsed()) { - mDirectShareViewHolder.collapse(activeAdapterView); - } else { - mDirectShareViewHolder.expand(activeAdapterView); - } + mChooserActivityDelegate.updateDirectShareExpansion(mDirectShareViewHolder); } } diff --git a/java/src/com/android/intentresolver/ChooserListAdapter.java b/java/src/com/android/intentresolver/ChooserListAdapter.java index b18d2718..59d1a6e3 100644 --- a/java/src/com/android/intentresolver/ChooserListAdapter.java +++ b/java/src/com/android/intentresolver/ChooserListAdapter.java @@ -37,6 +37,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.os.AsyncTask; import android.os.Trace; +import android.os.UserHandle; import android.os.UserManager; import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; @@ -67,8 +68,6 @@ public class ChooserListAdapter extends ResolverListAdapter { private static final String TAG = "ChooserListAdapter"; private static final boolean DEBUG = false; - private boolean mEnableStackedApps = true; - public static final int NO_POSITION = -1; public static final int TARGET_BAD = -1; public static final int TARGET_CALLER = 0; @@ -95,11 +94,11 @@ public class ChooserListAdapter extends ResolverListAdapter { private final List<TargetInfo> mServiceTargets = new ArrayList<>(); private final List<DisplayResolveInfo> mCallerTargets = new ArrayList<>(); + private final ShortcutSelectionLogic mShortcutSelectionLogic; + // Sorted list of DisplayResolveInfos for the alphabetical app section. private List<DisplayResolveInfo> mSortedList = new ArrayList<>(); - private final ShortcutSelectionLogic mShortcutSelectionLogic; - // For pinned direct share labels, if the text spans multiple lines, the TextView will consume // the full width, even if the characters actually take up less than that. Measure the actual // line widths and constrain the View's width based upon that so that the pin doesn't end up @@ -138,6 +137,8 @@ public class ChooserListAdapter extends ResolverListAdapter { List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, ResolverListCommunicator resolverListCommunicator, PackageManager packageManager, ChooserActivityLogger chooserActivityLogger, @@ -145,8 +146,17 @@ public class ChooserListAdapter extends ResolverListAdapter { int maxRankedTargets) { // Don't send the initial intents through the shared ResolverActivity path, // we want to separate them into a different section. - super(context, payloadIntents, null, rList, filterLastUsed, - resolverListController, resolverListCommunicator, false); + super( + context, + payloadIntents, + null, + rList, + filterLastUsed, + resolverListController, + userHandle, + targetIntent, + resolverListCommunicator, + false); mChooserRequest = chooserRequest; mMaxRankedTargets = maxRankedTargets; @@ -334,11 +344,8 @@ public class ChooserListAdapter extends ResolverListAdapter { @Override protected List<DisplayResolveInfo> doInBackground(Void... voids) { List<DisplayResolveInfo> allTargets = new ArrayList<>(); - allTargets.addAll(mDisplayList); + allTargets.addAll(getTargetsInCurrentDisplayList()); allTargets.addAll(mCallerTargets); - if (!mEnableStackedApps) { - return allTargets; - } // Consolidate multiple targets from same app. return allTargets @@ -408,7 +415,7 @@ public class ChooserListAdapter extends ResolverListAdapter { int getAlphaTargetCount() { int groupedCount = mSortedList.size(); - int ungroupedCount = mCallerTargets.size() + mDisplayList.size(); + int ungroupedCount = mCallerTargets.size() + getDisplayResolveInfoCount(); return (ungroupedCount > mMaxRankedTargets) ? groupedCount : 0; } diff --git a/java/src/com/android/intentresolver/ResolverActivity.java b/java/src/com/android/intentresolver/ResolverActivity.java index fece8d3d..adcfecef 100644 --- a/java/src/com/android/intentresolver/ResolverActivity.java +++ b/java/src/com/android/intentresolver/ResolverActivity.java @@ -107,7 +107,6 @@ import com.android.intentresolver.AbstractMultiProfilePagerAdapter.OnSwitchOnWor import com.android.intentresolver.AbstractMultiProfilePagerAdapter.Profile; import com.android.intentresolver.AbstractMultiProfilePagerAdapter.QuietModeManager; import com.android.intentresolver.NoCrossProfileEmptyStateProvider.DevicePolicyBlockerEmptyState; -import com.android.intentresolver.chooser.ChooserTargetInfo; import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.intentresolver.widget.ResolverDrawerLayout; @@ -123,6 +122,7 @@ import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.function.Supplier; /** * This activity is displayed when the system attempts to start an Intent for @@ -223,7 +223,11 @@ public class ResolverActivity extends FragmentActivity implements private BroadcastReceiver mWorkProfileStateReceiver; private UserHandle mHeaderCreatorUser; - private UserHandle mWorkProfileUserHandle; + private Supplier<UserHandle> mLazyWorkProfileUserHandle = () -> { + final UserHandle result = fetchWorkProfileUserProfile(); + mLazyWorkProfileUserHandle = () -> result; + return result; + }; @Nullable private OnSwitchOnWorkSelectedListener mOnSwitchOnWorkSelectedListener; @@ -408,7 +412,6 @@ public class ResolverActivity extends FragmentActivity implements mDefaultTitleResId = defaultTitleRes; mSupportsAlwaysUseOption = supportsAlwaysUseOption; - mWorkProfileUserHandle = fetchWorkProfileUserProfile(); // 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 @@ -699,19 +702,25 @@ public class ResolverActivity extends FragmentActivity implements protected UserHandle getPersonalProfileUserHandle() { return UserHandle.of(ActivityManager.getCurrentUser()); } - protected @Nullable UserHandle getWorkProfileUserHandle() { - return mWorkProfileUserHandle; + + @Nullable + protected UserHandle getWorkProfileUserHandle() { + return mLazyWorkProfileUserHandle.get(); } - protected @Nullable UserHandle fetchWorkProfileUserProfile() { - mWorkProfileUserHandle = null; + @Nullable + private UserHandle fetchWorkProfileUserProfile() { UserManager userManager = getSystemService(UserManager.class); + if (userManager == null) { + return null; + } + UserHandle result = null; for (final UserInfo userInfo : userManager.getProfiles(ActivityManager.getCurrentUser())) { if (userInfo.isManagedProfile()) { - mWorkProfileUserHandle = userInfo.getUserHandle(); + result = userInfo.getUserHandle(); } } - return mWorkProfileUserHandle; + return result; } private boolean hasWorkProfile() { @@ -851,7 +860,6 @@ public class ResolverActivity extends FragmentActivity implements } } - @Override // SelectableTargetInfoCommunicator ResolverListCommunicator public Intent getTargetIntent() { return mIntents.isEmpty() ? null : mIntents.get(0); } @@ -1532,8 +1540,16 @@ public class ResolverActivity extends FragmentActivity implements Intent startIntent = getIntent(); boolean isAudioCaptureDevice = startIntent.getBooleanExtra(EXTRA_IS_AUDIO_CAPTURE_DEVICE, false); - return new ResolverListAdapter(context, payloadIntents, initialIntents, rList, - filterLastUsed, createListController(userHandle), this, + return new ResolverListAdapter( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + createListController(userHandle), + userHandle, + getTargetIntent(), + this, isAudioCaptureDevice); } @@ -1597,12 +1613,13 @@ public class ResolverActivity extends FragmentActivity implements setContentView(mLayoutId); DisplayResolveInfo sameProfileResolveInfo = - mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList.get(0); + mMultiProfilePagerAdapter.getActiveListAdapter().getFirstDisplayResolveInfo(); boolean inWorkProfile = getCurrentProfile() == PROFILE_WORK; final ResolverListAdapter inactiveAdapter = mMultiProfilePagerAdapter.getInactiveListAdapter(); - final DisplayResolveInfo otherProfileResolveInfo = inactiveAdapter.mDisplayList.get(0); + final DisplayResolveInfo otherProfileResolveInfo = + inactiveAdapter.getFirstDisplayResolveInfo(); // Load the icon asynchronously ImageView icon = findViewById(com.android.internal.R.id.icon); @@ -1653,31 +1670,29 @@ public class ResolverActivity extends FragmentActivity implements || mMultiProfilePagerAdapter.getInactiveListAdapter() == null) { return false; } - List<DisplayResolveInfo> sameProfileList = - mMultiProfilePagerAdapter.getActiveListAdapter().mDisplayList; - List<DisplayResolveInfo> otherProfileList = - mMultiProfilePagerAdapter.getInactiveListAdapter().mDisplayList; + ResolverListAdapter sameProfileAdapter = + mMultiProfilePagerAdapter.getActiveListAdapter(); + ResolverListAdapter otherProfileAdapter = + mMultiProfilePagerAdapter.getInactiveListAdapter(); - if (sameProfileList.isEmpty()) { + if (sameProfileAdapter.getDisplayResolveInfoCount() == 0) { Log.d(TAG, "No targets in the current profile"); return false; } - if (otherProfileList.size() != 1) { - Log.d(TAG, "Found " + otherProfileList.size() + " resolvers in the other profile"); + if (otherProfileAdapter.getDisplayResolveInfoCount() != 1) { + Log.d(TAG, "Other-profile count: " + otherProfileAdapter.getDisplayResolveInfoCount()); return false; } - if (otherProfileList.get(0).getResolveInfo().handleAllWebDataURI) { + if (otherProfileAdapter.allResolveInfosHandleAllWebDataUri()) { Log.d(TAG, "Other profile is a web browser"); return false; } - for (DisplayResolveInfo info : sameProfileList) { - if (!info.getResolveInfo().handleAllWebDataURI) { - Log.d(TAG, "Non-browser found in this profile"); - return false; - } + if (!sameProfileAdapter.allResolveInfosHandleAllWebDataUri()) { + Log.d(TAG, "Non-browser found in this profile"); + return false; } return true; diff --git a/java/src/com/android/intentresolver/ResolverListAdapter.java b/java/src/com/android/intentresolver/ResolverListAdapter.java index d97191c6..9f654594 100644 --- a/java/src/com/android/intentresolver/ResolverListAdapter.java +++ b/java/src/com/android/intentresolver/ResolverListAdapter.java @@ -56,6 +56,8 @@ import com.android.intentresolver.chooser.DisplayResolveInfo; import com.android.intentresolver.chooser.TargetInfo; import com.android.internal.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; + import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -65,37 +67,48 @@ import java.util.Map; public class ResolverListAdapter extends BaseAdapter { private static final String TAG = "ResolverListAdapter"; + @Nullable // TODO: other model for lazy computation? Or just precompute? + private static ColorMatrixColorFilter sSuspendedMatrixColorFilter; + + protected final Context mContext; + protected final LayoutInflater mInflater; + protected final ResolverListCommunicator mResolverListCommunicator; + protected final ResolverListController mResolverListController; + private final List<Intent> mIntents; private final Intent[] mInitialIntents; private final List<ResolveInfo> mBaseResolveList; private final PackageManager mPm; - protected final Context mContext; - private static ColorMatrixColorFilter sSuspendedMatrixColorFilter; private final int mIconDpi; - protected ResolveInfo mLastChosen; + private final boolean mIsAudioCaptureDevice; + private final UserHandle mUserHandle; + private final Intent mTargetIntent; + + private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>(); + private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>(); + + private ResolveInfo mLastChosen; private DisplayResolveInfo mOtherProfile; - ResolverListController mResolverListController; private int mPlaceholderCount; - protected final LayoutInflater mInflater; - // This one is the list that the Adapter will actually present. - List<DisplayResolveInfo> mDisplayList; + private List<DisplayResolveInfo> mDisplayList; private List<ResolvedComponentInfo> mUnfilteredResolveList; private int mLastChosenPosition = -1; private boolean mFilterLastUsed; - final ResolverListCommunicator mResolverListCommunicator; private Runnable mPostListReadyRunnable; - private final boolean mIsAudioCaptureDevice; private boolean mIsTabLoaded; - private final Map<DisplayResolveInfo, LoadIconTask> mIconLoaders = new HashMap<>(); - private final Map<DisplayResolveInfo, LoadLabelTask> mLabelLoaders = new HashMap<>(); - public ResolverListAdapter(Context context, List<Intent> payloadIntents, - Intent[] initialIntents, List<ResolveInfo> rList, + public ResolverListAdapter( + Context context, + List<Intent> payloadIntents, + Intent[] initialIntents, + List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, ResolverListCommunicator resolverListCommunicator, boolean isAudioCaptureDevice) { mContext = context; @@ -107,12 +120,22 @@ public class ResolverListAdapter extends BaseAdapter { mDisplayList = new ArrayList<>(); mFilterLastUsed = filterLastUsed; mResolverListController = resolverListController; + mUserHandle = userHandle; + mTargetIntent = targetIntent; mResolverListCommunicator = resolverListCommunicator; mIsAudioCaptureDevice = isAudioCaptureDevice; final ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE); mIconDpi = am.getLauncherLargeIconDensity(); } + public final DisplayResolveInfo getFirstDisplayResolveInfo() { + return mDisplayList.get(0); + } + + public final ImmutableList<DisplayResolveInfo> getTargetsInCurrentDisplayList() { + return ImmutableList.copyOf(mDisplayList); + } + public void handlePackagesChanged() { mResolverListCommunicator.onHandlePackagesChanged(this); } @@ -262,7 +285,7 @@ public class ResolverListAdapter extends BaseAdapter { if (mBaseResolveList != null) { List<ResolvedComponentInfo> currentResolveList = new ArrayList<>(); mResolverListController.addResolveListDedupe(currentResolveList, - mResolverListCommunicator.getTargetIntent(), + mTargetIntent, mBaseResolveList); return currentResolveList; } else { @@ -338,7 +361,12 @@ public class ResolverListAdapter extends BaseAdapter { if (otherProfileInfo != null) { mOtherProfile = makeOtherProfileDisplayResolveInfo( - mContext, otherProfileInfo, mPm, mResolverListCommunicator, mIconDpi); + mContext, + otherProfileInfo, + mPm, + mTargetIntent, + mResolverListCommunicator, + mIconDpi); } else { mOtherProfile = null; try { @@ -499,7 +527,7 @@ public class ResolverListAdapter extends BaseAdapter { final Intent replaceIntent = mResolverListCommunicator.getReplacementIntent(add.activityInfo, intent); final Intent defaultIntent = mResolverListCommunicator.getReplacementIntent( - add.activityInfo, mResolverListCommunicator.getTargetIntent()); + add.activityInfo, mTargetIntent); final DisplayResolveInfo dri = DisplayResolveInfo.newDisplayResolveInfo( intent, add, @@ -608,11 +636,15 @@ public class ResolverListAdapter extends BaseAdapter { return position; } - public int getDisplayResolveInfoCount() { + public final int getDisplayResolveInfoCount() { return mDisplayList.size(); } - public DisplayResolveInfo getDisplayResolveInfo(int index) { + public final boolean allResolveInfosHandleAllWebDataUri() { + return mDisplayList.stream().allMatch(t -> t.getResolveInfo().handleAllWebDataURI); + } + + public final DisplayResolveInfo getDisplayResolveInfo(int index) { // Used to query services. We only query services for primary targets, not alternates. return mDisplayList.get(index); } @@ -765,7 +797,7 @@ public class ResolverListAdapter extends BaseAdapter { } public UserHandle getUserHandle() { - return mResolverListController.getUserHandle(); + return mUserHandle; } protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) { @@ -821,6 +853,7 @@ public class ResolverListAdapter extends BaseAdapter { Context context, ResolvedComponentInfo resolvedComponentInfo, PackageManager pm, + Intent targetIntent, ResolverListCommunicator resolverListCommunicator, int iconDpi) { ResolveInfo resolveInfo = resolvedComponentInfo.getResolveInfoAt(0); @@ -829,8 +862,7 @@ public class ResolverListAdapter extends BaseAdapter { resolveInfo.activityInfo, resolvedComponentInfo.getIntentAt(0)); Intent replacementIntent = resolverListCommunicator.getReplacementIntent( - resolveInfo.activityInfo, - resolverListCommunicator.getTargetIntent()); + resolveInfo.activityInfo, targetIntent); ResolveInfoPresentationGetter presentationGetter = new ResolveInfoPresentationGetter(context, iconDpi, resolveInfo); @@ -871,8 +903,6 @@ public class ResolverListAdapter extends BaseAdapter { */ default boolean shouldGetOnlyDefaultActivities() { return true; }; - Intent getTargetIntent(); - void onHandlePackagesChanged(ResolverListAdapter listAdapter); } diff --git a/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java b/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java index 95c61e3a..cfd54697 100644 --- a/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java +++ b/java/src/com/android/intentresolver/grid/DirectShareViewHolder.java @@ -28,7 +28,6 @@ import android.view.animation.AccelerateInterpolator; import androidx.recyclerview.widget.RecyclerView; import com.android.intentresolver.ChooserActivity; -import com.android.intentresolver.ChooserListAdapter; import java.util.Arrays; import java.util.List; @@ -47,14 +46,14 @@ public class DirectShareViewHolder extends ItemGroupViewHolder { private final boolean[] mCellVisibility; - private final Supplier<ChooserListAdapter> mListAdapterSupplier; + private final Supplier<Integer> mDeferredTargetCountSupplier; public DirectShareViewHolder( ViewGroup parent, List<ViewGroup> rows, int cellCountPerRow, int viewType, - Supplier<ChooserListAdapter> listAdapterSupplier) { + Supplier<Integer> deferredTargetCountSupplier) { super(rows.size() * cellCountPerRow, parent, viewType); this.mParent = parent; @@ -62,7 +61,7 @@ public class DirectShareViewHolder extends ItemGroupViewHolder { this.mCellCountPerRow = cellCountPerRow; this.mCellVisibility = new boolean[rows.size() * cellCountPerRow]; Arrays.fill(mCellVisibility, true); - this.mListAdapterSupplier = listAdapterSupplier; + this.mDeferredTargetCountSupplier = deferredTargetCountSupplier; } public ViewGroup addView(int index, View v) { @@ -136,8 +135,7 @@ public class DirectShareViewHolder extends ItemGroupViewHolder { // only expand if we have more than maxTargetsPerRow, and delay that decision // until they start to scroll - ChooserListAdapter adapter = mListAdapterSupplier.get(); - int validTargets = adapter.getSelectableServiceTargetCount(); + final int validTargets = this.mDeferredTargetCountSupplier.get(); if (validTargets <= maxTargetsPerRow) { mHideDirectShareExpansion = true; return; diff --git a/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java b/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java index 29821e66..a2c5afc6 100644 --- a/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java +++ b/java/src/com/android/intentresolver/widget/ResolverDrawerLayout.java @@ -17,6 +17,9 @@ package com.android.intentresolver.widget; +import static android.content.res.Resources.ID_NULL; + +import android.annotation.IdRes; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; @@ -98,6 +101,8 @@ public class ResolverDrawerLayout extends ViewGroup { private int mTopOffset; private boolean mShowAtTop; + @IdRes + private int mIgnoreOffsetTopLimitViewId = ID_NULL; private boolean mIsDragging; private boolean mOpenOnClick; @@ -158,6 +163,10 @@ public class ResolverDrawerLayout extends ViewGroup { mIsMaxCollapsedHeightSmallExplicit = a.hasValue(R.styleable.ResolverDrawerLayout_maxCollapsedHeightSmall); mShowAtTop = a.getBoolean(R.styleable.ResolverDrawerLayout_showAtTop, false); + if (a.hasValue(R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit)) { + mIgnoreOffsetTopLimitViewId = a.getResourceId( + R.styleable.ResolverDrawerLayout_ignoreOffsetTopLimit, ID_NULL); + } a.recycle(); mScrollIndicatorDrawable = mContext.getDrawable( @@ -580,12 +589,32 @@ public class ResolverDrawerLayout extends ViewGroup { dy -= 1.0f; } + boolean isIgnoreOffsetLimitSet = false; + int ignoreOffsetLimit = 0; + View ignoreOffsetLimitView = findIgnoreOffsetLimitView(); + if (ignoreOffsetLimitView != null) { + LayoutParams lp = (LayoutParams) ignoreOffsetLimitView.getLayoutParams(); + ignoreOffsetLimit = ignoreOffsetLimitView.getBottom() + lp.bottomMargin; + isIgnoreOffsetLimitSet = true; + } final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); + if (child.getVisibility() == View.GONE) { + continue; + } final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.ignoreOffset) { child.offsetTopAndBottom((int) dy); + } else if (isIgnoreOffsetLimitSet) { + int top = child.getTop(); + int targetTop = Math.max( + (int) (ignoreOffsetLimit + lp.topMargin + dy), + lp.mFixedTop); + if (top != targetTop) { + child.offsetTopAndBottom(targetTop - top); + } + ignoreOffsetLimit = child.getBottom() + lp.bottomMargin; } } final boolean isCollapsedOld = mCollapseOffset != 0; @@ -1027,6 +1056,8 @@ public class ResolverDrawerLayout extends ViewGroup { final int rightEdge = width - getPaddingRight(); final int widthAvailable = rightEdge - leftEdge; + boolean isIgnoreOffsetLimitSet = false; + int ignoreOffsetLimit = 0; final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); @@ -1039,9 +1070,24 @@ public class ResolverDrawerLayout extends ViewGroup { continue; } + if (mIgnoreOffsetTopLimitViewId != ID_NULL && !isIgnoreOffsetLimitSet) { + if (mIgnoreOffsetTopLimitViewId == child.getId()) { + ignoreOffsetLimit = child.getBottom() + lp.bottomMargin; + isIgnoreOffsetLimitSet = true; + } + } + int top = ypos + lp.topMargin; if (lp.ignoreOffset) { - top -= mCollapseOffset; + if (!isDragging()) { + lp.mFixedTop = (int) (top - mCollapseOffset); + } + if (isIgnoreOffsetLimitSet) { + top = Math.max(ignoreOffsetLimit + lp.topMargin, (int) (top - mCollapseOffset)); + ignoreOffsetLimit = top + child.getMeasuredHeight() + lp.bottomMargin; + } else { + top -= mCollapseOffset; + } } final int bottom = top + child.getMeasuredHeight(); @@ -1105,11 +1151,23 @@ public class ResolverDrawerLayout extends ViewGroup { mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved; } + private View findIgnoreOffsetLimitView() { + if (mIgnoreOffsetTopLimitViewId == ID_NULL) { + return null; + } + View v = findViewById(mIgnoreOffsetTopLimitViewId); + if (v != null && v != this && v.getParent() == this && v.getVisibility() != View.GONE) { + return v; + } + return null; + } + public static class LayoutParams extends MarginLayoutParams { public boolean alwaysShow; public boolean ignoreOffset; public boolean hasNestedScrollIndicator; public int maxHeight; + int mFixedTop; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); diff --git a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt index d054e7fa..6b34f8b9 100644 --- a/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt +++ b/java/tests/src/com/android/intentresolver/ChooserListAdapterTest.kt @@ -56,6 +56,8 @@ class ChooserListAdapterTest { emptyList(), false, resolverListController, + null, + Intent(), mock(), packageManager, chooserActivityLogger, diff --git a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java index fe448d63..8c842786 100644 --- a/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ChooserWrapperActivity.java @@ -73,6 +73,8 @@ public class ChooserWrapperActivity List<ResolveInfo> rList, boolean filterLastUsed, ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, ChooserRequestParameters chooserRequest, int maxTargetsPerRow) { PackageManager packageManager = @@ -85,6 +87,8 @@ public class ChooserWrapperActivity rList, filterLastUsed, resolverListController, + userHandle, + targetIntent, this, packageManager, getChooserActivityLogger(), diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java index 7d4b07d8..239bffe0 100644 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/ResolverWrapperActivity.java @@ -59,8 +59,16 @@ public class ResolverWrapperActivity extends ResolverActivity { public ResolverListAdapter createResolverListAdapter(Context context, List<Intent> payloadIntents, Intent[] initialIntents, List<ResolveInfo> rList, boolean filterLastUsed, UserHandle userHandle) { - return new ResolverWrapperAdapter(context, payloadIntents, initialIntents, rList, - filterLastUsed, createListController(userHandle), this); + return new ResolverWrapperAdapter( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + createListController(userHandle), + userHandle, + payloadIntents.get(0), // TODO: extract upstream + this); } @Override diff --git a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java index 1504a8ab..a53b41d1 100644 --- a/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java +++ b/java/tests/src/com/android/intentresolver/ResolverWrapperAdapter.java @@ -19,6 +19,7 @@ package com.android.intentresolver; import android.content.Context; import android.content.Intent; import android.content.pm.ResolveInfo; +import android.os.UserHandle; import androidx.test.espresso.idling.CountingIdlingResource; @@ -31,14 +32,27 @@ public class ResolverWrapperAdapter extends ResolverListAdapter { private CountingIdlingResource mLabelIdlingResource = new CountingIdlingResource("LoadLabelTask"); - public ResolverWrapperAdapter(Context context, + public ResolverWrapperAdapter( + Context context, List<Intent> payloadIntents, Intent[] initialIntents, - List<ResolveInfo> rList, boolean filterLastUsed, + List<ResolveInfo> rList, + boolean filterLastUsed, ResolverListController resolverListController, + UserHandle userHandle, + Intent targetIntent, ResolverListCommunicator resolverListCommunicator) { - super(context, payloadIntents, initialIntents, rList, filterLastUsed, - resolverListController, resolverListCommunicator, false); + super( + context, + payloadIntents, + initialIntents, + rList, + filterLastUsed, + resolverListController, + userHandle, + targetIntent, + resolverListCommunicator, + false); } public CountingIdlingResource getLabelIdlingResource() { diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index da72a749..28b68530 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -78,6 +78,7 @@ import android.graphics.Paint; import android.graphics.drawable.Icon; import android.metrics.LogMaker; import android.net.Uri; +import android.os.Bundle; import android.os.UserHandle; import android.provider.DeviceConfig; import android.service.chooser.ChooserTarget; @@ -1636,6 +1637,90 @@ public class UnbundledChooserActivityTest { } @Test + public void testLaunchWithCallerProvidedTarget() { + setDeviceConfigProperty( + SystemUiDeviceConfigFlags.APPLY_SHARING_APP_LIMITS_IN_SYSUI, + Boolean.toString(false)); + // Set up resources + ChooserActivityOverrideData.getInstance().resources = Mockito.spy( + InstrumentationRegistry.getInstrumentation().getContext().getResources()); + when( + ChooserActivityOverrideData + .getInstance() + .resources + .getInteger(R.integer.config_maxShortcutTargetsPerApp)) + .thenReturn(1); + + // We need app targets for direct targets to get displayed + List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2); + when( + ChooserActivityOverrideData + .getInstance() + .resolverListController + .getResolversForIntent( + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))) + .thenReturn(resolvedComponentInfos); + + // set caller-provided target + Intent chooserIntent = Intent.createChooser(createSendTextIntent(), null); + String callerTargetLabel = "Caller Target"; + ChooserTarget[] targets = new ChooserTarget[] { + new ChooserTarget( + callerTargetLabel, + Icon.createWithBitmap(createBitmap()), + 0.1f, + resolvedComponentInfos.get(0).name, + new Bundle()) + }; + chooserIntent.putExtra(Intent.EXTRA_CHOOSER_TARGETS, targets); + + // create test shortcut loader factory, remember loaders and their callbacks + SparseArray<Pair<ShortcutLoader, Consumer<ShortcutLoader.Result>>> shortcutLoaders = + createShortcutLoaderFactory(); + + // Start activity + final IChooserWrapper activity = (IChooserWrapper) + mActivityRule.launchActivity(chooserIntent); + waitForIdle(); + + // verify that ShortcutLoader was queried + ArgumentCaptor<DisplayResolveInfo[]> appTargets = + ArgumentCaptor.forClass(DisplayResolveInfo[].class); + verify(shortcutLoaders.get(0).first, times(1)).queryShortcuts(appTargets.capture()); + + // send shortcuts + assertThat( + "Wrong number of app targets", + appTargets.getValue().length, + is(resolvedComponentInfos.size())); + ShortcutLoader.Result result = new ShortcutLoader.Result( + true, + appTargets.getValue(), + new ShortcutLoader.ShortcutResultInfo[0], + new HashMap<>(), + new HashMap<>()); + activity.getMainExecutor().execute(() -> shortcutLoaders.get(0).second.accept(result)); + waitForIdle(); + + final ChooserListAdapter activeAdapter = activity.getAdapter(); + assertThat( + "Chooser should have 3 targets (2 apps, 1 direct)", + activeAdapter.getCount(), + is(3)); + assertThat( + "Chooser should have exactly two selectable direct target", + activeAdapter.getSelectableServiceTargetCount(), + is(1)); + assertThat( + "The display label must match", + activeAdapter.getItem(0).getDisplayLabel(), + is(callerTargetLabel)); + } + + @Test public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException { updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4); givenAppTargets(/* appCount= */ 16); |