summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java42
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java65
-rw-r--r--core/java/com/android/internal/app/ChooserListAdapter.java5
-rw-r--r--core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java15
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java156
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java7
-rw-r--r--core/java/com/android/internal/app/ResolverListController.java3
-rw-r--r--core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java15
-rw-r--r--core/java/com/android/internal/app/ResolverViewPager.java (renamed from core/java/com/android/internal/app/WrapHeightViewPager.java)29
-rw-r--r--core/res/res/layout/chooser_grid.xml29
-rw-r--r--core/res/res/layout/resolver_list.xml42
-rw-r--r--core/res/res/layout/resolver_list_per_profile.xml4
-rw-r--r--core/res/res/layout/resolver_list_with_default.xml44
-rw-r--r--core/res/res/values/colors.xml2
-rw-r--r--core/res/res/values/strings.xml4
-rw-r--r--core/res/res/values/symbols.xml11
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java157
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java35
-rw-r--r--core/tests/coretests/src/com/android/internal/app/MatcherUtils.java51
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java246
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java38
22 files changed, 871 insertions, 135 deletions
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 08022e983892..b2aa0431251d 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -26,7 +26,9 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.PagerAdapter;
import com.android.internal.widget.ViewPager;
+import java.util.HashSet;
import java.util.Objects;
+import java.util.Set;
/**
* Skeletal {@link PagerAdapter} implementation of a work or personal profile page for
@@ -34,6 +36,7 @@ import java.util.Objects;
*/
public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
+ private static final String TAG = "AbstractMultiProfilePagerAdapter";
static final int PROFILE_PERSONAL = 0;
static final int PROFILE_WORK = 1;
@IntDef({PROFILE_PERSONAL, PROFILE_WORK})
@@ -41,10 +44,17 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
private final Context mContext;
private int mCurrentPage;
+ private OnProfileSelectedListener mOnProfileSelectedListener;
+ private Set<Integer> mLoadedPages;
AbstractMultiProfilePagerAdapter(Context context, int currentPage) {
mContext = Objects.requireNonNull(context);
mCurrentPage = currentPage;
+ mLoadedPages = new HashSet<>();
+ }
+
+ void setOnProfileSelectedListener(OnProfileSelectedListener listener) {
+ mOnProfileSelectedListener = listener;
}
Context getContext() {
@@ -57,15 +67,22 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* page and rebuilds the list.
*/
void setupViewPager(ViewPager viewPager) {
- viewPager.setCurrentItem(mCurrentPage);
viewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
mCurrentPage = position;
- getActiveListAdapter().rebuildList();
+ if (!mLoadedPages.contains(position)) {
+ getActiveListAdapter().rebuildList();
+ mLoadedPages.add(position);
+ }
+ if (mOnProfileSelectedListener != null) {
+ mOnProfileSelectedListener.onProfileSelected(position);
+ }
}
});
viewPager.setAdapter(this);
+ viewPager.setCurrentItem(mCurrentPage);
+ mLoadedPages.add(mCurrentPage);
}
@Override
@@ -90,7 +107,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
return mCurrentPage;
}
- UserHandle getCurrentUserHandle() {
+ @VisibleForTesting
+ public UserHandle getCurrentUserHandle() {
return getActiveListAdapter().mResolverListController.getUserHandle();
}
@@ -135,7 +153,8 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
* <p>This method is meant to be implemented with an implementation-specific return type
* depending on the adapter type.
*/
- abstract Object getAdapterForIndex(int pageIndex);
+ @VisibleForTesting
+ public abstract Object getAdapterForIndex(int pageIndex);
@VisibleForTesting
public abstract ResolverListAdapter getActiveListAdapter();
@@ -152,7 +171,9 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
abstract Object getCurrentRootAdapter();
- abstract ViewGroup getCurrentAdapterView();
+ abstract ViewGroup getActiveAdapterView();
+
+ abstract @Nullable ViewGroup getInactiveAdapterView();
protected class ProfileDescriptor {
final ViewGroup rootView;
@@ -160,4 +181,15 @@ public abstract class AbstractMultiProfilePagerAdapter extends PagerAdapter {
this.rootView = rootView;
}
}
+
+ public interface OnProfileSelectedListener {
+ /**
+ * Callback for when the user changes the active tab from personal to work or vice versa.
+ * <p>This callback is only called when the intent resolver or share sheet shows
+ * the work and personal profiles.
+ * @param profileIndex {@link #PROFILE_PERSONAL} if the personal profile was selected or
+ * {@link #PROFILE_WORK} if the work profile was selected.
+ */
+ void onProfileSelected(int profileIndex);
+ }
} \ No newline at end of file
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 9532faecb4df..8bbc343fa4ca 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -508,7 +508,6 @@ public class ChooserActivity extends ResolverActivity implements
protected void onCreate(Bundle savedInstanceState) {
final long intentReceivedTime = System.currentTimeMillis();
// This is the only place this value is being set. Effectively final.
- //TODO(arangelov) - should there be a mIsAppPredictorComponentAvailable flag for work tab?
mIsAppPredictorComponentAvailable = isAppPredictionServiceAvailable();
mIsSuccessfullySelected = false;
@@ -689,29 +688,6 @@ public class ChooserActivity extends ResolverActivity implements
mResolverDrawerLayout.setOnScrollChangeListener(this::handleScroll);
}
- final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
- final float defaultElevation = chooserHeader.getElevation();
- final float chooserHeaderScrollElevation =
- getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
-
- mChooserMultiProfilePagerAdapter.getCurrentAdapterView().addOnScrollListener(
- new RecyclerView.OnScrollListener() {
- public void onScrollStateChanged(RecyclerView view, int scrollState) {
- }
-
- public void onScrolled(RecyclerView view, int dx, int dy) {
- if (view.getChildCount() > 0) {
- View child = view.getLayoutManager().findViewByPosition(0);
- if (child == null || child.getTop() < 0) {
- chooserHeader.setElevation(chooserHeaderScrollElevation);
- return;
- }
- }
-
- chooserHeader.setElevation(defaultElevation);
- }
- });
-
mResolverDrawerLayout.setOnCollapsedChangedListener(
new ResolverDrawerLayout.OnCollapsedChangedListener() {
@@ -1330,8 +1306,8 @@ public class ChooserActivity extends ResolverActivity implements
}
@Override
- public void onPrepareAdapterView(ResolverListAdapter adapter) {
- mChooserMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
+ public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
+ mChooserMultiProfilePagerAdapter.getActiveAdapterView().setVisibility(View.VISIBLE);
if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
mChooserMultiProfilePagerAdapter.getActiveListAdapter().addServiceResults(
/* origTarget */ null,
@@ -2202,7 +2178,7 @@ public class ChooserActivity extends ResolverActivity implements
if (mChooserMultiProfilePagerAdapter == null) {
return;
}
- RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getCurrentAdapterView();
+ RecyclerView recyclerView = mChooserMultiProfilePagerAdapter.getActiveAdapterView();
ChooserGridAdapter gridAdapter = mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
if (gridAdapter == null || recyclerView == null) {
return;
@@ -2328,6 +2304,8 @@ public class ChooserActivity extends ResolverActivity implements
@Override
public void onListRebuilt(ResolverListAdapter listAdapter) {
+ setupScrollListener();
+
ChooserListAdapter chooserListAdapter = (ChooserListAdapter) listAdapter;
if (chooserListAdapter.mDisplayList == null
|| chooserListAdapter.mDisplayList.isEmpty()) {
@@ -2368,6 +2346,34 @@ public class ChooserActivity extends ResolverActivity implements
}
}
+ private void setupScrollListener() {
+ if (mResolverDrawerLayout == null) {
+ return;
+ }
+ final View chooserHeader = mResolverDrawerLayout.findViewById(R.id.chooser_header);
+ final float defaultElevation = chooserHeader.getElevation();
+ final float chooserHeaderScrollElevation =
+ getResources().getDimensionPixelSize(R.dimen.chooser_header_scroll_elevation);
+
+ mChooserMultiProfilePagerAdapter.getActiveAdapterView().addOnScrollListener(
+ new RecyclerView.OnScrollListener() {
+ public void onScrollStateChanged(RecyclerView view, int scrollState) {
+ }
+
+ public void onScrolled(RecyclerView view, int dx, int dy) {
+ if (view.getChildCount() > 0) {
+ View child = view.getLayoutManager().findViewByPosition(0);
+ if (child == null || child.getTop() < 0) {
+ chooserHeader.setElevation(chooserHeaderScrollElevation);
+ return;
+ }
+ }
+
+ chooserHeader.setElevation(defaultElevation);
+ }
+ });
+ }
+
@Override // ChooserListCommunicator
public boolean isSendAction(Intent targetIntent) {
if (targetIntent == null) {
@@ -2475,7 +2481,8 @@ public class ChooserActivity extends ResolverActivity implements
* row level by this adapter but not on the item level. Individual targets within the row are
* handled by {@link ChooserListAdapter}
*/
- final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
+ @VisibleForTesting
+ public final class ChooserGridAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private ChooserListAdapter mChooserListAdapter;
private final LayoutInflater mLayoutInflater;
@@ -2905,7 +2912,7 @@ public class ChooserActivity extends ResolverActivity implements
if (mDirectShareViewHolder != null && canExpandDirectShare) {
mDirectShareViewHolder.handleScroll(
- mChooserMultiProfilePagerAdapter.getCurrentAdapterView(), y, oldy,
+ mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
getMaxTargetsPerRow());
}
}
diff --git a/core/java/com/android/internal/app/ChooserListAdapter.java b/core/java/com/android/internal/app/ChooserListAdapter.java
index a8a676d03971..6ff844a2eaae 100644
--- a/core/java/com/android/internal/app/ChooserListAdapter.java
+++ b/core/java/com/android/internal/app/ChooserListAdapter.java
@@ -246,11 +246,6 @@ public class ChooserListAdapter extends ResolverListAdapter {
}
@Override
- public boolean shouldGetResolvedFilter() {
- return true;
- }
-
- @Override
public int getCount() {
return getRankedTargetCount() + getAlphaTargetCount()
+ getSelectableServiceTargetCount() + getCallerTargetCount();
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 7d856e1b945d..1c52d0e8f9f1 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -77,7 +78,8 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
}
@Override
- ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) {
+ @VisibleForTesting
+ public ChooserActivity.ChooserGridAdapter getAdapterForIndex(int pageIndex) {
return mItems[pageIndex].chooserGridAdapter;
}
@@ -121,10 +123,19 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
}
@Override
- RecyclerView getCurrentAdapterView() {
+ RecyclerView getActiveAdapterView() {
return getListViewForIndex(getCurrentPage());
}
+ @Override
+ @Nullable
+ RecyclerView getInactiveAdapterView() {
+ if (getCount() == 1) {
+ return null;
+ }
+ return getListViewForIndex(1 - getCurrentPage());
+ }
+
class ChooserProfileDescriptor extends ProfileDescriptor {
private ChooserActivity.ChooserGridAdapter chooserGridAdapter;
private RecyclerView recyclerView;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index b2417b2e79cc..68d6e03f654c 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -59,6 +59,7 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -67,9 +68,12 @@ import android.view.WindowInsets;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.Button;
+import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Space;
+import android.widget.TabHost;
+import android.widget.TabWidget;
import android.widget.TextView;
import android.widget.Toast;
@@ -82,6 +86,7 @@ import com.android.internal.content.PackageMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.widget.ResolverDrawerLayout;
+import com.android.internal.widget.ViewPager;
import java.util.ArrayList;
import java.util.Arrays;
@@ -147,7 +152,10 @@ public class ResolverActivity extends Activity implements
/**
* TODO(arangelov): Remove a couple of weeks after work/personal tabs are finalized.
*/
- static final boolean ENABLE_TABBED_VIEW = false;
+ @VisibleForTesting
+ public static boolean ENABLE_TABBED_VIEW = false;
+ private static final String TAB_TAG_PERSONAL = "personal";
+ private static final String TAB_TAG_WORK = "work";
private final PackageMonitor mPackageMonitor = createPackageMonitor();
@@ -418,12 +426,16 @@ public class ResolverActivity extends Activity implements
Intent[] initialIntents,
List<ResolveInfo> rList,
boolean filterLastUsed) {
+ // We only show the default app for the profile of the current user. The filterLastUsed
+ // flag determines whether to show a default app and that app is not shown in the
+ // resolver list. So filterLastUsed should be false for the other profile.
ResolverListAdapter personalAdapter = createResolverListAdapter(
/* context */ this,
/* payloadIntents */ mIntents,
initialIntents,
rList,
- filterLastUsed,
+ (filterLastUsed && UserHandle.myUserId()
+ == getPersonalProfileUserHandle().getIdentifier()),
mUseLayoutForBrowsables,
/* userHandle */ getPersonalProfileUserHandle());
ResolverListAdapter workAdapter = createResolverListAdapter(
@@ -431,7 +443,8 @@ public class ResolverActivity extends Activity implements
/* payloadIntents */ mIntents,
initialIntents,
rList,
- filterLastUsed,
+ (filterLastUsed && UserHandle.myUserId()
+ == getWorkProfileUserHandle().getIdentifier()),
mUseLayoutForBrowsables,
/* userHandle */ getWorkProfileUserHandle());
return new ResolverMultiProfilePagerAdapter(
@@ -495,12 +508,12 @@ public class ResolverActivity extends Activity implements
mFooterSpacer = new Space(getApplicationContext());
} else {
((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getCurrentAdapterView().removeFooterView(mFooterSpacer);
+ .getActiveAdapterView().removeFooterView(mFooterSpacer);
}
mFooterSpacer.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
mSystemWindowInsets.bottom));
((ResolverMultiProfilePagerAdapter) mMultiProfilePagerAdapter)
- .getCurrentAdapterView().addFooterView(mFooterSpacer);
+ .getActiveAdapterView().addFooterView(mFooterSpacer);
}
protected WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
@@ -817,7 +830,7 @@ public class ResolverActivity extends Activity implements
public void onButtonClick(View v) {
final int id = v.getId();
- ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ ListView listView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
ResolverListAdapter currentListAdapter = mMultiProfilePagerAdapter.getActiveListAdapter();
int which = currentListAdapter.hasFilteredItem()
? currentListAdapter.getFilteredPosition()
@@ -898,7 +911,10 @@ public class ResolverActivity extends Activity implements
@Override // ResolverListCommunicator
public void onPostListReady(ResolverListAdapter listAdapter) {
- setHeader();
+ if (mMultiProfilePagerAdapter.getCurrentUserHandle().getIdentifier()
+ == UserHandle.myUserId()) {
+ setHeader();
+ }
resetButtonBar();
onListRebuilt(listAdapter);
}
@@ -913,6 +929,9 @@ public class ResolverActivity extends Activity implements
finish();
}
}
+
+ final ItemClickListener listener = new ItemClickListener();
+ setupAdapterListView((ListView) mMultiProfilePagerAdapter.getActiveAdapterView(), listener);
}
protected boolean onTargetSelected(TargetInfo target, boolean alwaysCheck) {
@@ -1094,6 +1113,7 @@ public class ResolverActivity extends Activity implements
return true;
}
+ @VisibleForTesting
public void safelyStartActivity(TargetInfo cti) {
// We're dispatching intents that might be coming from legacy apps, so
// don't kill ourselves.
@@ -1222,9 +1242,6 @@ public class ResolverActivity extends Activity implements
+ "cannot be null.");
}
boolean rebuildCompleted = mMultiProfilePagerAdapter.getActiveListAdapter().rebuildList();
- if (mMultiProfilePagerAdapter.getInactiveListAdapter() != null) {
- mMultiProfilePagerAdapter.getInactiveListAdapter().rebuildList();
- }
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
} else {
@@ -1272,45 +1289,99 @@ public class ResolverActivity extends Activity implements
}
}
- setupViewVisibilities(count);
+ setupViewVisibilities();
+
+ if (hasWorkProfile() && ENABLE_TABBED_VIEW) {
+ setupProfileTabs();
+ }
+
return false;
}
- private void setupViewVisibilities(int count) {
- if (count == 0
- && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0) {
- final TextView emptyView = findViewById(R.id.empty);
- emptyView.setVisibility(View.VISIBLE);
- findViewById(R.id.profile_pager).setVisibility(View.GONE);
- } else {
- onPrepareAdapterView(mMultiProfilePagerAdapter.getActiveListAdapter());
+ private void setupProfileTabs() {
+ TabHost tabHost = findViewById(R.id.profile_tabhost);
+ tabHost.setup();
+ ViewPager viewPager = findViewById(R.id.profile_pager);
+ TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
+ .setContent(R.id.profile_pager)
+ .setIndicator(getString(R.string.resolver_personal_tab));
+ tabHost.addTab(tabSpec);
+
+ tabSpec = tabHost.newTabSpec(TAB_TAG_WORK)
+ .setContent(R.id.profile_pager)
+ .setIndicator(getString(R.string.resolver_work_tab));
+ tabHost.addTab(tabSpec);
+
+ TabWidget tabWidget = tabHost.getTabWidget();
+ tabWidget.setVisibility(View.VISIBLE);
+ resetTabsHeaderStyle(tabWidget);
+ updateActiveTabStyle(tabHost);
+
+ tabHost.setOnTabChangedListener(tabId -> {
+ resetTabsHeaderStyle(tabWidget);
+ updateActiveTabStyle(tabHost);
+ if (TAB_TAG_PERSONAL.equals(tabId)) {
+ viewPager.setCurrentItem(0);
+ } else {
+ viewPager.setCurrentItem(1);
+ }
+ setupViewVisibilities();
+ });
+
+ viewPager.setVisibility(View.VISIBLE);
+ tabHost.setCurrentTab(mMultiProfilePagerAdapter.getCurrentPage());
+ mMultiProfilePagerAdapter.setOnProfileSelectedListener(tabHost::setCurrentTab);
+ }
+
+ private void resetTabsHeaderStyle(TabWidget tabWidget) {
+ for (int i = 0; i < tabWidget.getChildCount(); i++) {
+ TextView title = tabWidget.getChildAt(i).findViewById(android.R.id.title);
+ title.setTextColor(getColor(R.color.resolver_tabs_inactive_color));
+ title.setAllCaps(false);
+ }
+ }
+
+ private void updateActiveTabStyle(TabHost tabHost) {
+ TextView title = tabHost.getTabWidget().getChildAt(tabHost.getCurrentTab())
+ .findViewById(android.R.id.title);
+ title.setTextColor(getColor(R.color.resolver_tabs_active_color));
+ }
+
+ private void setupViewVisibilities() {
+ int count = mMultiProfilePagerAdapter.getActiveListAdapter().getUnfilteredCount();
+ boolean shouldShowEmptyState = count == 0
+ && mMultiProfilePagerAdapter.getActiveListAdapter().getPlaceholderCount() == 0;
+ //TODO(arangelov): Handle empty state
+ if (!shouldShowEmptyState) {
+ addUseDifferentAppLabelIfNecessary(mMultiProfilePagerAdapter.getActiveListAdapter());
}
}
/**
- * Prepare the scrollable view which consumes data in the list adapter.
+ * Add a label to signify that the user can pick a different app.
* @param adapter The adapter used to provide data to item views.
*/
- public void onPrepareAdapterView(ResolverListAdapter adapter) {
- mMultiProfilePagerAdapter.getCurrentAdapterView().setVisibility(View.VISIBLE);
+ public void addUseDifferentAppLabelIfNecessary(ResolverListAdapter adapter) {
final boolean useHeader = adapter.hasFilteredItem();
- final ListView listView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
- final ItemClickListener listener = new ItemClickListener();
+ if (useHeader) {
+ FrameLayout stub = findViewById(R.id.stub);
+ stub.setVisibility(View.VISIBLE);
+ TextView textView = (TextView) LayoutInflater.from(this).inflate(
+ R.layout.resolver_different_item_header, null, false);
+ if (ENABLE_TABBED_VIEW) {
+ textView.setGravity(Gravity.CENTER);
+ }
+ stub.addView(textView);
+ }
+ }
+
+ private void setupAdapterListView(ListView listView, ItemClickListener listener) {
listView.setOnItemClickListener(listener);
listView.setOnItemLongClickListener(listener);
if (mSupportsAlwaysUseOption || mUseLayoutForBrowsables) {
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
-
- // In case this method is called again (due to activity recreation), avoid adding a new
- // header if one is already present.
- if (useHeader && listView.getHeaderViewsCount() == 0) {
- listView.setHeaderDividersEnabled(true);
- listView.addHeaderView(LayoutInflater.from(this).inflate(
- R.layout.resolver_different_item_header, listView, false),
- null, false);
- }
}
/**
@@ -1378,7 +1449,7 @@ public class ResolverActivity extends Activity implements
}
// When the items load in, if an item was already selected, enable the buttons
- ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ ListView currentAdapterView = (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
if (currentAdapterView != null
&& currentAdapterView.getCheckedItemPosition() != ListView.INVALID_POSITION) {
setAlwaysButtonEnabled(true, currentAdapterView.getCheckedItemPosition(), true);
@@ -1388,8 +1459,18 @@ public class ResolverActivity extends Activity implements
@Override // ResolverListCommunicator
public boolean useLayoutWithDefault() {
- return mSupportsAlwaysUseOption
- && mMultiProfilePagerAdapter.getActiveListAdapter().hasFilteredItem();
+ // 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;
}
/**
@@ -1494,9 +1575,8 @@ public class ResolverActivity extends Activity implements
.resolveInfoForPosition(position, true) == null) {
return;
}
-
ListView currentAdapterView =
- (ListView) mMultiProfilePagerAdapter.getCurrentAdapterView();
+ (ListView) mMultiProfilePagerAdapter.getActiveAdapterView();
final int checkedPos = currentAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (!useLayoutWithDefault()
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index ef7a347cf7be..d227ec5c1b38 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -193,7 +193,8 @@ public class ResolverListAdapter extends BaseAdapter {
mBaseResolveList);
} else {
currentResolveList = mUnfilteredResolveList =
- mResolverListController.getResolversForIntent(shouldGetResolvedFilter(),
+ mResolverListController.getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
mResolverListCommunicator.shouldGetActivityMetadata(),
mIntents);
if (currentResolveList == null) {
@@ -363,10 +364,6 @@ public class ResolverListAdapter extends BaseAdapter {
}
}
- public boolean shouldGetResolvedFilter() {
- return mFilterLastUsed;
- }
-
private void addResolveInfoWithAlternates(ResolvedComponentInfo rci) {
final int count = rci.getCount();
final Intent intent = rci.getIntentAt(0);
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index abd3eb2453df..0bfe9eb04d28 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -133,7 +133,8 @@ public class ResolverListController {
return resolvedComponents;
}
- UserHandle getUserHandle() {
+ @VisibleForTesting
+ public UserHandle getUserHandle() {
return mUserHandle;
}
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index d72c52bfe6b6..567ed74670bf 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.annotation.Nullable;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -81,7 +82,8 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
}
@Override
- ResolverListAdapter getAdapterForIndex(int pageIndex) {
+ @VisibleForTesting
+ public ResolverListAdapter getAdapterForIndex(int pageIndex) {
return mItems[pageIndex].resolverListAdapter;
}
@@ -106,10 +108,19 @@ public class ResolverMultiProfilePagerAdapter extends AbstractMultiProfilePagerA
}
@Override
- ListView getCurrentAdapterView() {
+ ListView getActiveAdapterView() {
return getListViewForIndex(getCurrentPage());
}
+ @Override
+ @Nullable
+ ViewGroup getInactiveAdapterView() {
+ if (getCount() == 1) {
+ return null;
+ }
+ return getListViewForIndex(1 - getCurrentPage());
+ }
+
class ResolverProfileDescriptor extends ProfileDescriptor {
private ResolverListAdapter resolverListAdapter;
final ListView listView;
diff --git a/core/java/com/android/internal/app/WrapHeightViewPager.java b/core/java/com/android/internal/app/ResolverViewPager.java
index b017bb44d751..8ec94f159725 100644
--- a/core/java/com/android/internal/app/WrapHeightViewPager.java
+++ b/core/java/com/android/internal/app/ResolverViewPager.java
@@ -18,39 +18,36 @@ package com.android.internal.app;
import android.content.Context;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import com.android.internal.widget.ViewPager;
/**
- * A {@link ViewPager} which wraps around its first child's height.
+ * A {@link ViewPager} which wraps around its first child's height and has swiping disabled.
* <p>Normally {@link ViewPager} instances expand their height to cover all remaining space in
* the layout.
- * <p>This class is used for the intent resolver picker's tabbed view to maintain
- * consistency with the previous behavior.
+ * <p>This class is used for the intent resolver and share sheet's tabbed view.
*/
-public class WrapHeightViewPager extends ViewPager {
+public class ResolverViewPager extends ViewPager {
- public WrapHeightViewPager(Context context) {
+ public ResolverViewPager(Context context) {
super(context);
}
- public WrapHeightViewPager(Context context, AttributeSet attrs) {
+ public ResolverViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
- public WrapHeightViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ResolverViewPager(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public WrapHeightViewPager(Context context, AttributeSet attrs,
+ public ResolverViewPager(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
- // TODO(arangelov): When we have multiple pages, the height should wrap to the currently
- // displayed page. Investigate whether onMeasure is called when changing a page, and instead
- // of getChildAt(0), use the currently displayed one.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -68,4 +65,14 @@ public class WrapHeightViewPager extends ViewPager {
heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
}
diff --git a/core/res/res/layout/chooser_grid.xml b/core/res/res/layout/chooser_grid.xml
index 0c45e45e7980..4e8a41f81c48 100644
--- a/core/res/res/layout/chooser_grid.xml
+++ b/core/res/res/layout/chooser_grid.xml
@@ -61,10 +61,33 @@
android:layout_height="wrap_content"
android:visibility="gone" />
- <com.android.internal.widget.ViewPager
- android:id="@+id/profile_pager"
+ <TabHost
+ android:id="@+id/profile_tabhost"
android:layout_width="match_parent"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?attr/colorBackgroundFloating">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ </TabWidget>
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.internal.app.ResolverViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
<TextView android:id="@+id/empty"
android:layout_width="match_parent"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index c5d891254227..757cd539152d 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -70,14 +70,44 @@
android:background="?attr/colorBackgroundFloating"
android:foreground="?attr/dividerVertical" />
- <com.android.internal.app.WrapHeightViewPager
- android:id="@+id/profile_pager"
+ <FrameLayout
+ android:id="@+id/stub"
+ android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:divider="?attr/dividerVertical"
- android:footerDividersEnabled="false"
- android:headerDividersEnabled="false"
- android:dividerHeight="1dp"/>
+ android:background="?attr/colorBackgroundFloating"/>
+
+ <TabHost
+ android:id="@+id/profile_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?attr/colorBackgroundFloating">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ </TabWidget>
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.internal.app.ResolverViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"
+ android:dividerHeight="1dp"/>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
<View
android:layout_alwaysShow="true"
diff --git a/core/res/res/layout/resolver_list_per_profile.xml b/core/res/res/layout/resolver_list_per_profile.xml
index 68b991755e73..6d8d3480dc8c 100644
--- a/core/res/res/layout/resolver_list_per_profile.xml
+++ b/core/res/res/layout/resolver_list_per_profile.xml
@@ -25,7 +25,7 @@
android:nestedScrollingEnabled="true"
android:scrollbarStyle="outsideOverlay"
android:scrollIndicators="top|bottom"
- android:divider="?attr/dividerVertical"
+ android:divider="@null"
android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
- android:dividerHeight="1dp" /> \ No newline at end of file
+ android:dividerHeight="0dp" /> \ No newline at end of file
diff --git a/core/res/res/layout/resolver_list_with_default.xml b/core/res/res/layout/resolver_list_with_default.xml
index 5b3d929d23a5..b54673834ea9 100644
--- a/core/res/res/layout/resolver_list_with_default.xml
+++ b/core/res/res/layout/resolver_list_with_default.xml
@@ -151,14 +151,46 @@
android:background="?attr/colorBackgroundFloating"
android:foreground="?attr/dividerVertical" />
- <com.android.internal.app.WrapHeightViewPager
- android:id="@+id/profile_pager"
+ <FrameLayout
+ android:id="@+id/stub"
+ android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:dividerHeight="1dp"
- android:divider="?attr/dividerVertical"
- android:footerDividersEnabled="false"
- android:headerDividersEnabled="false"/>
+ android:background="?attr/colorBackgroundFloating"/>
+
+ <TabHost
+ android:id="@+id/profile_tabhost"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:background="?attr/colorBackgroundFloating">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:visibility="gone">
+ </TabWidget>
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <com.android.internal.app.ResolverViewPager
+ android:id="@+id/profile_pager"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:dividerHeight="1dp"
+ android:divider="?attr/dividerVertical"
+ android:footerDividersEnabled="false"
+ android:headerDividersEnabled="false"/>
+ </FrameLayout>
+ </LinearLayout>
+ </TabHost>
+
<View
android:layout_alwaysShow="true"
android:layout_width="match_parent"
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 1dcd389d9d8f..731e2ec95f9e 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -224,4 +224,6 @@
<!-- Resolver/Chooser -->
<color name="resolver_text_color_secondary_dark">#ffC4C6C6</color>
+ <color name="resolver_tabs_active_color">#FF1A73E8</color>
+ <color name="resolver_tabs_inactive_color">#FF80868B</color>
</resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 0cdd02cc9e0f..be2b678565d3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5325,4 +5325,8 @@
<!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
<string name="as_app_forced_to_restricted_bucket">
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
+
+ <!-- ResolverActivity - profile tabs -->
+ <string name="resolver_personal_tab">Personal</string>
+ <string name="resolver_work_tab">Work</string>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 8f2174562781..ea8ca9aa1c49 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -249,6 +249,9 @@
<java-symbol type="id" name="app_ops" />
<java-symbol type="id" name="profile_pager" />
<java-symbol type="id" name="content_preview_container" />
+ <java-symbol type="id" name="profile_tabhost" />
+ <java-symbol type="id" name="tabs" />
+ <java-symbol type="id" name="tabcontent" />
<java-symbol type="attr" name="actionModeShareDrawable" />
<java-symbol type="attr" name="alertDialogCenterButtons" />
@@ -3836,4 +3839,12 @@
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" />
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" />
+
+ <!-- Intent resolver and share sheet -->
+ <java-symbol type="color" name="resolver_tabs_active_color" />
+ <java-symbol type="color" name="resolver_tabs_inactive_color" />
+ <java-symbol type="string" name="resolver_personal_tab" />
+ <java-symbol type="string" name="resolver_work_tab" />
+ <java-symbol type="id" name="stub" />
+
</resources>
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index c086421501b5..411868d8befe 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -31,6 +31,7 @@ import static com.android.internal.app.ChooserActivity.TARGET_TYPE_SHORTCUTS_FRO
import static com.android.internal.app.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST;
import static com.android.internal.app.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST;
import static com.android.internal.app.ChooserWrapperActivity.sOverrides;
+import static com.android.internal.app.MatcherUtils.first;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
@@ -63,6 +64,8 @@ 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.service.chooser.ChooserTarget;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -74,7 +77,11 @@ import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -302,6 +309,7 @@ public class ChooserActivityTest {
assertThat(activity.getIsSelected(), is(true));
}
+ @Ignore // b/148158199
@Test
public void noResultsFromPackageManager() {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
@@ -346,6 +354,9 @@ public class ChooserActivityTest {
@Test
public void hasOtherProfileOneOption() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2);
@@ -372,9 +383,7 @@ public class ChooserActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
+ waitForIdle();
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
waitForIdle();
@@ -383,6 +392,9 @@ public class ChooserActivityTest {
@Test
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
@@ -411,9 +423,6 @@ public class ChooserActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(3);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
waitForIdle();
@@ -422,6 +431,9 @@ public class ChooserActivityTest {
@Test
public void hasLastChosenActivityAndOtherProfile() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
Intent sendIntent = createSendTextIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
@@ -448,9 +460,6 @@ public class ChooserActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(3);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
waitForIdle();
@@ -1161,6 +1170,123 @@ public class ChooserActivityTest {
.getAllValues().get(2).getTaggedData(MetricsEvent.FIELD_RANKED_POSITION), is(-1));
}
+ @Test
+ public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ markWorkProfileUserAvailable();
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ }
+
+ @Test
+ public void testWorkTab_eachTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ int personalProfileTargets = 3;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(personalProfileTargets);
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(
+ workProfileTargets);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ markWorkProfileUserAvailable();
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+ // The work list adapter must only be filled when we open the work tab
+ assertThat(activity.getWorkListAdapter().getCount(), is(0));
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(personalProfileTargets));
+ assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+ }
+
+ @Test
+ public void testWorkTab_workProfileHasExpectedNumberOfTargets() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+
+ final ChooserWrapperActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+
+ assertThat(activity.getWorkListAdapter().getCount(), is(workProfileTargets));
+ }
+
+ @Ignore // b/148156663
+ @Test
+ public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ int workProfileTargets = 4;
+ List<ResolvedComponentInfo> workResolvedComponentInfos =
+ createResolvedComponentsForTest(workProfileTargets);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendTextIntent();
+ sendIntent.setType("TestType");
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, "work tab test"));
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+ waitForIdle();
+ // wait for the share sheet to expand
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+
+ onView(first(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name)))
+ .perform(click());
+ waitForIdle();
+ assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
private Intent createSendTextIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -1224,6 +1350,15 @@ public class ChooserActivityTest {
return infoList;
}
+ private List<ResolvedComponentInfo> createResolvedComponentsForTestWithUserId(
+ int numberOfResults, int userId) {
+ List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
+ for (int i = 0; i < numberOfResults; i++) {
+ infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i, userId));
+ }
+ return infoList;
+ }
+
private List<ChooserTarget> createDirectShareTargets(int numberOfResults, String packageName) {
Icon icon = Icon.createWithBitmap(createBitmap());
String testTitle = "testTitle";
@@ -1308,4 +1443,8 @@ public class ChooserActivityTest {
assertEquals(cn.flattenToString(), ct.getComponentName().flattenToString());
}
}
+
+ private void markWorkProfileUserAvailable() {
+ sOverrides.workProfileUserHandle = UserHandle.of(10);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
index 2a1044361d42..eee62bb791bf 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java
@@ -17,6 +17,7 @@
package com.android.internal.app;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.usage.UsageStatsManager;
@@ -29,6 +30,7 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
+import android.os.Bundle;
import android.os.UserHandle;
import android.util.Size;
@@ -51,6 +53,19 @@ public class ChooserWrapperActivity extends ChooserActivity {
return mChooserMultiProfilePagerAdapter.getActiveListAdapter();
}
+ ChooserListAdapter getPersonalListAdapter() {
+ return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0))
+ .getListAdapter();
+ }
+
+ ChooserListAdapter getWorkListAdapter() {
+ if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ return null;
+ }
+ return ((ChooserGridAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1))
+ .getListAdapter();
+ }
+
boolean getIsSelected() { return mIsSuccessfullySelected; }
UsageStatsManager getUsageStatsManager() {
@@ -79,7 +94,12 @@ public class ChooserWrapperActivity extends ChooserActivity {
@Override
protected ResolverListController createListController(UserHandle userHandle) {
- return sOverrides.resolverListController;
+ if (userHandle == UserHandle.SYSTEM) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ return sOverrides.resolverListController;
+ }
+ when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.workResolverListController;
}
@Override
@@ -144,6 +164,15 @@ public class ChooserWrapperActivity extends ChooserActivity {
resolveInfoPresentationGetter);
}
+ @Override
+ protected UserHandle getWorkProfileUserHandle() {
+ return sOverrides.workProfileUserHandle;
+ }
+
+ protected UserHandle getCurrentUserHandle() {
+ return mMultiProfilePagerAdapter.getCurrentUserHandle();
+ }
+
/**
* We cannot directly mock the activity created since instrumentation creates it.
* <p>
@@ -154,6 +183,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
public ResolverListController resolverListController;
+ public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
public boolean isImageType;
public Cursor resolverCursor;
@@ -162,6 +192,7 @@ public class ChooserWrapperActivity extends ChooserActivity {
public MetricsLogger metricsLogger;
public int alternateProfileSetting;
public Resources resources;
+ public UserHandle workProfileUserHandle;
public void reset() {
onSafelyStartCallback = null;
@@ -172,9 +203,11 @@ public class ChooserWrapperActivity extends ChooserActivity {
resolverCursor = null;
resolverForceException = false;
resolverListController = mock(ResolverListController.class);
+ workResolverListController = mock(ResolverListController.class);
metricsLogger = mock(MetricsLogger.class);
alternateProfileSetting = 0;
resources = null;
+ workProfileUserHandle = null;
}
}
}
diff --git a/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java
new file mode 100644
index 000000000000..a4766318a21e
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/MatcherUtils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+/**
+ * Utils for helping with more customized matching options, for example matching the first
+ * occurrence of a set criteria.
+ */
+public class MatcherUtils {
+
+ /**
+ * Returns a {@link Matcher} which only matches the first occurrence of a set criteria.
+ */
+ static <T> Matcher<T> first(final Matcher<T> matcher) {
+ return new BaseMatcher<T>() {
+ boolean isFirstMatch = true;
+
+ @Override
+ public boolean matches(final Object item) {
+ if (isFirstMatch && matcher.matches(item)) {
+ isFirstMatch = false;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("Returns the first matching item");
+ }
+ };
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 923ce3e3935d..42f7736d37b0 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -19,13 +19,17 @@ package com.android.internal.app;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isEnabled;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
+import static com.android.internal.app.MatcherUtils.first;
import static com.android.internal.app.ResolverDataProvider.createPackageManagerMockedInfo;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
+import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -33,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.content.Intent;
import android.content.pm.ResolveInfo;
+import android.os.UserHandle;
import android.text.TextUtils;
import android.view.View;
import android.widget.RelativeLayout;
@@ -49,6 +54,9 @@ import com.android.internal.app.ResolverListAdapter.ActivityInfoPresentationGett
import com.android.internal.app.ResolverListAdapter.ResolveInfoPresentationGetter;
import com.android.internal.widget.ResolverDrawerLayout;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -212,6 +220,9 @@ public class ResolverActivityTest {
@Test
public void hasOtherProfileOneOption() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2);
@@ -237,9 +248,6 @@ public class ResolverActivityTest {
// Make a stable copy of the components as the original list may be modified
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
onView(withId(R.id.button_once))
@@ -250,6 +258,9 @@ public class ResolverActivityTest {
@Test
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
@@ -279,9 +290,6 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
onView(withId(R.id.button_once)).perform(click());
@@ -292,6 +300,9 @@ public class ResolverActivityTest {
@Test
public void hasLastChosenActivityAndOtherProfile() throws Exception {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+
// In this case we prefer the other profile and don't display anything about the last
// chosen activity.
Intent sendIntent = createSendImageIntent();
@@ -325,9 +336,6 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> stableCopy =
createResolvedComponentsForTestWithOtherProfile(2);
- // Check that the "Other Profile" activity is put in the right spot
- onView(withId(R.id.profile_button)).check(matches(
- withText(stableCopy.get(0).getResolveInfoAt(0).activityInfo.name)));
onView(withText(stableCopy.get(1).getResolveInfoAt(0).activityInfo.name))
.perform(click());
onView(withId(R.id.button_once)).perform(click());
@@ -379,6 +387,222 @@ public class ResolverActivityTest {
TextUtils.isEmpty(pg.getSubLabel()));
}
+ @Test
+ public void testWorkTab_displayedWhenWorkProfileUserAvailable() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testWorkTab_hiddenWhenWorkProfileUserNotAvailable() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ Intent sendIntent = createSendImageIntent();
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ onView(withId(R.id.tabs)).check(matches(not(isDisplayed())));
+ }
+
+ @Test
+ public void testWorkTab_workTabListEmptyBeforeGoingToTab() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(0));
+ // The work list adapter must only be filled when we open the work tab
+ assertThat(activity.getWorkListAdapter().getCount(), is(0));
+ }
+
+ @Test
+ public void testWorkTab_workTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_personalTabUsesExpectedAdapter() {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ markWorkProfileUserAvailable();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab)).perform(click());
+
+ assertThat(activity.getCurrentUserHandle().getIdentifier(), is(10));
+ assertThat(activity.getPersonalListAdapter().getCount(), is(3));
+ }
+
+ @Test
+ public void testWorkTab_workProfileHasExpectedNumberOfTargets() throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+
+ waitForIdle();
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Test
+ public void testWorkTab_selectingWorkTabAppOpensAppInWorkProfile() throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> personalResolvedComponentInfos =
+ createResolvedComponentsForTest(3);
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(personalResolvedComponentInfos);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ // wait for the share sheet to expand
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+
+ waitForIdle();
+ assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
+ @Test
+ public void testWorkTab_noPersonalApps_workTabHasExpectedNumberOfTargets()
+ throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+
+ final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+
+ waitForIdle();
+ assertThat(activity.getWorkListAdapter().getCount(), is(4));
+ }
+
+ @Ignore // b/148156663
+ @Test
+ public void testWorkTab_noPersonalApps_canStartWorkApps()
+ throws InterruptedException {
+ // enable the work tab feature flag
+ ResolverActivity.ENABLE_TABBED_VIEW = true;
+ markWorkProfileUserAvailable();
+ List<ResolvedComponentInfo> workResolvedComponentInfos = createResolvedComponentsForTest(4);
+ when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
+ Intent sendIntent = createSendImageIntent();
+ ResolveInfo[] chosen = new ResolveInfo[1];
+ sOverrides.onSafelyStartCallback = targetInfo -> {
+ chosen[0] = targetInfo.getResolveInfo();
+ return true;
+ };
+
+ mActivityRule.launchActivity(sendIntent);
+ waitForIdle();
+ onView(withText(R.string.resolver_work_tab))
+ .perform(click());
+ waitForIdle();
+ // wait for the share sheet to expand
+ Thread.sleep(ChooserActivity.LIST_VIEW_UPDATE_INTERVAL_IN_MILLIS);
+ onView(first(allOf(withText(workResolvedComponentInfos.get(0)
+ .getResolveInfoAt(0).activityInfo.applicationInfo.name), isCompletelyDisplayed())))
+ .perform(click());
+ onView(withId(R.id.button_once))
+ .perform(click());
+ waitForIdle();
+
+ assertThat(chosen[0], is(workResolvedComponentInfos.get(0).getResolveInfoAt(0)));
+ }
+
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -411,4 +635,8 @@ public class ResolverActivityTest {
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
+
+ private void markWorkProfileUserAvailable() {
+ ResolverWrapperActivity.sOverrides.workProfileUserHandle = UserHandle.of(10);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
index 59634f6d261c..d7db5f8e46eb 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java
@@ -46,6 +46,12 @@ class ResolverDataProvider {
createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
}
+ static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i,
+ int userId) {
+ return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
+ createResolverIntent(i), createResolveInfo(i, userId));
+ }
+
static ComponentName createComponentName(int i) {
final String name = "component" + i;
return new ComponentName("foo.bar." + name, name);
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
index c5d2cfaa9512..36c8724e522e 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java
@@ -17,12 +17,14 @@
package com.android.internal.app;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import android.app.usage.UsageStatsManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.os.Bundle;
import android.os.UserHandle;
import com.android.internal.app.chooser.TargetInfo;
@@ -49,6 +51,17 @@ public class ResolverWrapperActivity extends ResolverActivity {
return (ResolverWrapperAdapter) mMultiProfilePagerAdapter.getActiveListAdapter();
}
+ ResolverListAdapter getPersonalListAdapter() {
+ return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(0));
+ }
+
+ ResolverListAdapter getWorkListAdapter() {
+ if (mMultiProfilePagerAdapter.getInactiveListAdapter() == null) {
+ return null;
+ }
+ return ((ResolverListAdapter) mMultiProfilePagerAdapter.getAdapterForIndex(1));
+ }
+
@Override
public boolean isVoiceInteraction() {
if (sOverrides.isVoiceInteraction != null) {
@@ -68,7 +81,12 @@ public class ResolverWrapperActivity extends ResolverActivity {
@Override
protected ResolverListController createListController(UserHandle userHandle) {
- return sOverrides.resolverListController;
+ if (userHandle == UserHandle.SYSTEM) {
+ when(sOverrides.resolverListController.getUserHandle()).thenReturn(UserHandle.SYSTEM);
+ return sOverrides.resolverListController;
+ }
+ when(sOverrides.workResolverListController.getUserHandle()).thenReturn(userHandle);
+ return sOverrides.workResolverListController;
}
@Override
@@ -79,6 +97,20 @@ public class ResolverWrapperActivity extends ResolverActivity {
return super.getPackageManager();
}
+ protected UserHandle getCurrentUserHandle() {
+ return mMultiProfilePagerAdapter.getCurrentUserHandle();
+ }
+
+ @Override
+ protected UserHandle getWorkProfileUserHandle() {
+ return sOverrides.workProfileUserHandle;
+ }
+
+ @Override
+ public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+ super.startActivityAsUser(intent, options, user);
+ }
+
/**
* We cannot directly mock the activity created since instrumentation creates it.
* <p>
@@ -89,13 +121,17 @@ public class ResolverWrapperActivity extends ResolverActivity {
public Function<PackageManager, PackageManager> createPackageManager;
public Function<TargetInfo, Boolean> onSafelyStartCallback;
public ResolverListController resolverListController;
+ public ResolverListController workResolverListController;
public Boolean isVoiceInteraction;
+ public UserHandle workProfileUserHandle;
public void reset() {
onSafelyStartCallback = null;
isVoiceInteraction = null;
createPackageManager = null;
resolverListController = mock(ResolverListController.class);
+ workResolverListController = mock(ResolverListController.class);
+ workProfileUserHandle = null;
}
}
} \ No newline at end of file