Merge "Fix interaction w/ work profile & last chosen"

This commit is contained in:
TreeHugger Robot
2017-02-14 01:24:16 +00:00
committed by Android (Google) Code Review
6 changed files with 412 additions and 95 deletions

View File

@@ -340,8 +340,7 @@ public class ChooserActivity extends ResolverActivity {
}
@Override
public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
mChooserListAdapter = (ChooserListAdapter) adapter;
if (mCallerChooserTargets != null && mCallerChooserTargets.length > 0) {
@@ -367,6 +366,9 @@ public class ChooserActivity extends ResolverActivity {
@Override
public boolean shouldAutoLaunchSingleChoice(TargetInfo target) {
// Note that this is only safe because the Intent handled by the ChooserActivity is
// guaranteed to contain no extras unknown to the local ClassLoader. That is why this
// method can not be replaced in the ResolverActivity whole hog.
return getIntent().getBooleanExtra(Intent.EXTRA_AUTO_LAUNCH_SINGLE_CHOICE,
super.shouldAutoLaunchSingleChoice(target));
}

View File

@@ -20,22 +20,11 @@ import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UiThread;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.VoiceInteractor.PickOptionRequest;
import android.app.VoiceInteractor.PickOptionRequest.Option;
import android.app.VoiceInteractor.Prompt;
import android.os.AsyncTask;
import android.os.RemoteException;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Slog;
import android.widget.AbsListView;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.PackageMonitor;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -50,16 +39,23 @@ import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.MediaStore;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.Slog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
@@ -67,7 +63,9 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
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;
@@ -92,7 +90,6 @@ public class ResolverActivity extends Activity {
protected ResolveListAdapter mAdapter;
private boolean mSafeForwardingMode;
private boolean mAlwaysUseOption;
private AbsListView mAdapterView;
private Button mAlwaysButton;
private Button mOnceButton;
@@ -108,13 +105,15 @@ public class ResolverActivity extends Activity {
private CharSequence mTitle;
private int mDefaultTitleResId;
// Whether or not this activity supports choosing a default handler for the intent.
private boolean mSupportsAlwaysUseOption;
protected ResolverDrawerLayout mResolverDrawerLayout;
protected PackageManager mPm;
protected int mLaunchedFromUid;
private static final String TAG = "ResolverActivity";
private static final boolean DEBUG = false;
private Runnable mPostListBuildRunnable;
private Runnable mPostListReadyRunnable;
private boolean mRegistered;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@@ -230,13 +229,14 @@ public class ResolverActivity extends Activity {
*/
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, Intent[] initialIntents,
List<ResolveInfo> rList, boolean alwaysUseOption) {
onCreate(savedInstanceState, intent, title, 0, initialIntents, rList, alwaysUseOption);
List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
onCreate(savedInstanceState, intent, title, 0, initialIntents, rList,
supportsAlwaysUseOption);
}
protected void onCreate(Bundle savedInstanceState, Intent intent,
CharSequence title, int defaultTitleRes, Intent[] initialIntents,
List<ResolveInfo> rList, boolean alwaysUseOption) {
List<ResolveInfo> rList, boolean supportsAlwaysUseOption) {
setTheme(R.style.Theme_DeviceDefault_Resolver);
super.onCreate(savedInstanceState);
@@ -262,7 +262,7 @@ public class ResolverActivity extends Activity {
mPackageMonitor.register(this, getMainLooper(), false);
mRegistered = true;
mReferrerPackage = getReferrerPackageName();
mAlwaysUseOption = alwaysUseOption;
mSupportsAlwaysUseOption = supportsAlwaysUseOption;
final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
mIconDpi = am.getLauncherLargeIconDensity();
@@ -378,8 +378,11 @@ public class ResolverActivity extends Activity {
final DisplayResolveInfo dri = mAdapter.getOtherProfile();
if (dri != null) {
mProfileView.setVisibility(View.VISIBLE);
final TextView text = (TextView) mProfileView.findViewById(R.id.profile_button);
text.setText(dri.getDisplayLabel());
View text = mProfileView.findViewById(R.id.profile_button);
if (!(text instanceof TextView)) {
text = mProfileView.findViewById(R.id.text1);
}
((TextView) text).setText(dri.getDisplayLabel());
} else {
mProfileView.setVisibility(View.GONE);
}
@@ -421,7 +424,7 @@ public class ResolverActivity extends Activity {
final ActionTitle title = mResolvingHome ? ActionTitle.HOME : ActionTitle.forAction(action);
// While there may already be a filtered item, we can only use it in the title if the list
// is already sorted and all information relevant to it is already in the list.
final boolean named = mAdapter.getFilteredPosition() > 0;
final boolean named = mAdapter.getFilteredPosition() >= 0;
if (title == ActionTitle.DEFAULT && defaultTitleRes != 0) {
return getString(defaultTitleRes);
} else {
@@ -512,15 +515,16 @@ public class ResolverActivity extends Activity {
if (!isChangingConfigurations() && mPickOptionRequest != null) {
mPickOptionRequest.cancel();
}
if (mPostListBuildRunnable != null) {
getMainThreadHandler().removeCallbacks(mPostListBuildRunnable);
if (mPostListReadyRunnable != null) {
getMainThreadHandler().removeCallbacks(mPostListReadyRunnable);
mPostListReadyRunnable = null;
}
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (mAlwaysUseOption) {
if (mSupportsAlwaysUseOption) {
final int checkedPos = mAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
mLastSelected = checkedPos;
@@ -575,17 +579,18 @@ public class ResolverActivity extends Activity {
public void onButtonClick(View v) {
final int id = v.getId();
startSelected(mAlwaysUseOption ?
mAdapterView.getCheckedItemPosition() : mAdapter.getFilteredPosition(),
startSelected(mAdapter.hasFilteredItem() ?
mAdapter.getFilteredPosition():
mAdapterView.getCheckedItemPosition(),
id == R.id.button_always,
mAlwaysUseOption);
!mAdapter.hasFilteredItem());
}
public void startSelected(int which, boolean always, boolean filtered) {
public void startSelected(int which, boolean always, boolean hasIndexBeenFiltered) {
if (isFinishing()) {
return;
}
ResolveInfo ri = mAdapter.resolveInfoForPosition(which, filtered);
ResolveInfo ri = mAdapter.resolveInfoForPosition(which, hasIndexBeenFiltered);
if (mResolvingHome && hasManagedProfile() && !supportsManagedProfiles(ri)) {
Toast.makeText(this, String.format(getResources().getString(
com.android.internal.R.string.activity_resolver_work_profiles_support),
@@ -594,15 +599,15 @@ public class ResolverActivity extends Activity {
return;
}
TargetInfo target = mAdapter.targetInfoForPosition(which, filtered);
TargetInfo target = mAdapter.targetInfoForPosition(which, hasIndexBeenFiltered);
if (target == null) {
return;
}
if (onTargetSelected(target, always)) {
if (always && filtered) {
if (always && mSupportsAlwaysUseOption) {
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_ALWAYS);
} else if (filtered) {
} else if (mSupportsAlwaysUseOption) {
MetricsLogger.action(
this, MetricsProto.MetricsEvent.ACTION_APP_DISAMBIG_JUST_ONCE);
} else {
@@ -627,7 +632,7 @@ public class ResolverActivity extends Activity {
final ResolveInfo ri = target.getResolveInfo();
final Intent intent = target != null ? target.getResolvedIntent() : null;
if (intent != null && (mAlwaysUseOption || mAdapter.hasFilteredItem())
if (intent != null && (mSupportsAlwaysUseOption || mAdapter.hasFilteredItem())
&& mAdapter.mUnfilteredResolveList != null) {
// Build a reasonable intent filter, based on what matched.
IntentFilter filter = new IntentFilter();
@@ -714,7 +719,18 @@ public class ResolverActivity extends Activity {
if (filter != null) {
final int N = mAdapter.mUnfilteredResolveList.size();
ComponentName[] set = new ComponentName[N];
ComponentName[] set;
// If we don't add back in the component for forwarding the intent to a managed
// profile, the preferred activity may not be updated correctly (as the set of
// components we tell it we knew about will have changed).
final boolean needToAddBackProfileForwardingComponent
= mAdapter.mOtherProfile != null;
if (!needToAddBackProfileForwardingComponent) {
set = new ComponentName[N];
} else {
set = new ComponentName[N + 1];
}
int bestMatch = 0;
for (int i=0; i<N; i++) {
ResolveInfo r = mAdapter.mUnfilteredResolveList.get(i).getResolveInfoAt(0);
@@ -722,6 +738,13 @@ public class ResolverActivity extends Activity {
r.activityInfo.name);
if (r.match > bestMatch) bestMatch = r.match;
}
if (needToAddBackProfileForwardingComponent) {
set[N] = mAdapter.mOtherProfile.getResolvedComponentName();
final int otherProfileMatch = mAdapter.mOtherProfile.getResolveInfo().match;
if (otherProfileMatch > bestMatch) bestMatch = otherProfileMatch;
}
if (alwaysCheck) {
final int userId = getUserId();
final PackageManager pm = getPackageManager();
@@ -859,15 +882,15 @@ public class ResolverActivity extends Activity {
// a more complicated UI that the current voice interaction flow is not able
// to handle.
mAdapter = createAdapter(this, payloadIntents, initialIntents, rList,
mLaunchedFromUid, mAlwaysUseOption && !isVoiceInteraction());
mLaunchedFromUid, mSupportsAlwaysUseOption && !isVoiceInteraction());
boolean rebuildCompleted = mAdapter.rebuildList();
if (mAdapter.hasFilteredItem()) {
if (useLayoutWithDefault()) {
mLayoutId = R.layout.resolver_list_with_default;
mAlwaysUseOption = false;
} else {
mLayoutId = getLayoutResource();
}
setContentView(mLayoutId);
int count = mAdapter.getUnfilteredCount();
@@ -887,22 +910,21 @@ public class ResolverActivity extends Activity {
}
}
setContentView(mLayoutId);
mAdapterView = (AbsListView) findViewById(R.id.resolver_list);
if (count == 0 && mAdapter.mPlaceholderCount == 0) {
final TextView empty = (TextView) findViewById(R.id.empty);
empty.setVisibility(View.VISIBLE);
final TextView emptyView = (TextView) findViewById(R.id.empty);
emptyView.setVisibility(View.VISIBLE);
mAdapterView.setVisibility(View.GONE);
} else {
mAdapterView.setVisibility(View.VISIBLE);
onPrepareAdapterView(mAdapterView, mAdapter, mAlwaysUseOption);
onPrepareAdapterView(mAdapterView, mAdapter);
}
return false;
}
public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter,
boolean alwaysUseOption) {
public void onPrepareAdapterView(AbsListView adapterView, ResolveListAdapter adapter) {
final boolean useHeader = adapter.hasFilteredItem();
final ListView listView = adapterView instanceof ListView ? (ListView) adapterView : null;
@@ -912,7 +934,7 @@ public class ResolverActivity extends Activity {
adapterView.setOnItemClickListener(listener);
adapterView.setOnItemLongClickListener(listener);
if (alwaysUseOption) {
if (mSupportsAlwaysUseOption) {
listView.setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
@@ -969,16 +991,19 @@ public class ResolverActivity extends Activity {
}
public void resetAlwaysOrOnceButtonBar() {
if (mAlwaysUseOption || mAdapter.mLastChosen != null) {
if (mSupportsAlwaysUseOption) {
final ViewGroup buttonLayout = (ViewGroup) findViewById(R.id.button_bar);
if (buttonLayout != null) {
buttonLayout.setVisibility(View.VISIBLE);
mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
} else {
Log.e(TAG, "Layout unexpectedly does not have a button bar");
}
}
if (mAdapter.getFilteredPosition() >= 0) {
if (useLayoutWithDefault()
&& mAdapter.getFilteredPosition() != ListView.INVALID_POSITION) {
setAlwaysButtonEnabled(true, mAdapter.getFilteredPosition(), false);
mOnceButton.setEnabled(true);
return;
@@ -992,6 +1017,10 @@ public class ResolverActivity extends Activity {
}
}
private boolean useLayoutWithDefault() {
return mSupportsAlwaysUseOption && mAdapter.hasFilteredItem();
}
/**
* Check a simple match for the component of two ResolveInfos.
*/
@@ -1293,6 +1322,7 @@ public class ResolverActivity extends Activity {
mPlaceholderCount = count;
}
@Nullable
public DisplayResolveInfo getFilteredItem() {
if (mFilterLastUsed && mLastChosenPosition >= 0) {
// Not using getItem since it offsets to dodge this position for the list
@@ -1336,14 +1366,10 @@ public class ResolverActivity extends Activity {
*/
protected boolean rebuildList() {
List<ResolvedComponentInfo> currentResolveList = null;
try {
mLastChosen = mResolverListController.getLastChosen();
} catch (RemoteException re) {
Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
}
// Clear the value of mOtherProfile from previous call.
mOtherProfile = null;
mLastChosen = null;
mLastChosenPosition = -1;
mDisplayList.clear();
if (mBaseResolveList != null) {
currentResolveList = mUnfilteredResolveList = new ArrayList<>();
@@ -1366,6 +1392,30 @@ public class ResolverActivity extends Activity {
mUnfilteredResolveList = originalList;
}
}
// So far we only support a single other profile at a time.
// The first one we see gets special treatment.
for (ResolvedComponentInfo info : currentResolveList) {
if (info.getResolveInfoAt(0).targetUserId != UserHandle.USER_CURRENT) {
mOtherProfile = new DisplayResolveInfo(info.getIntentAt(0),
info.getResolveInfoAt(0),
info.getResolveInfoAt(0).loadLabel(mPm),
info.getResolveInfoAt(0).loadLabel(mPm),
getReplacementIntent(info.getResolveInfoAt(0).activityInfo,
info.getIntentAt(0)));
currentResolveList.remove(info);
break;
}
}
if (mOtherProfile == null) {
try {
mLastChosen = mResolverListController.getLastChosen();
} catch (RemoteException re) {
Log.d(TAG, "Error calling getLastChosenActivity\n" + re);
}
}
int N;
if ((currentResolveList != null) && ((N = currentResolveList.size()) > 0)) {
// We only care about fixing the unfilteredList if the current resolve list and
@@ -1402,19 +1452,7 @@ public class ResolverActivity extends Activity {
}
};
sortingTask.execute(currentResolveList);
if (mPostListBuildRunnable == null) {
mPostListBuildRunnable = new Runnable() {
@Override
public void run() {
setTitleAndIcon();
resetAlwaysOrOnceButtonBar();
onListRebuilt();
disableLastChosenIfNeeded();
mPostListBuildRunnable = null;
}
};
getMainThreadHandler().post(mPostListBuildRunnable);
}
postListReadyRunnable();
return false;
} else {
processSortedList(currentResolveList);
@@ -1426,16 +1464,6 @@ public class ResolverActivity extends Activity {
}
}
private void disableLastChosenIfNeeded() {
// Layout doesn't handle both profile button and last chosen
// so disable last chosen if profile button is present.
if (mOtherProfile != null && mLastChosenPosition >= 0) {
mLastChosenPosition = -1;
mFilterLastUsed = false;
}
}
private void processSortedList(List<ResolvedComponentInfo> sortedComponents) {
int N;
if (sortedComponents != null && (N = sortedComponents.size()) != 0) {
@@ -1503,10 +1531,27 @@ public class ResolverActivity extends Activity {
processGroup(sortedComponents, start, (N - 1), rci0, r0Label);
}
setTitleAndIcon();
resetAlwaysOrOnceButtonBar();
disableLastChosenIfNeeded();
onListRebuilt();
postListReadyRunnable();
}
/**
* Some necessary methods for creating the list are initiated in onCreate and will also
* determine the layout known. We therefore can't update the UI inline and post to the
* handler thread to update after the current task is finished.
*/
private void postListReadyRunnable() {
if (mPostListReadyRunnable == null) {
mPostListReadyRunnable = new Runnable() {
@Override
public void run() {
setTitleAndIcon();
resetAlwaysOrOnceButtonBar();
onListRebuilt();
mPostListReadyRunnable = null;
}
};
getMainThreadHandler().post(mPostListReadyRunnable);
}
}
public void onListRebuilt() {
@@ -1588,6 +1633,11 @@ public class ResolverActivity extends Activity {
}
private void updateLastChosenPosition(ResolveInfo info) {
// If another profile is present, ignore the last chosen entry.
if (mOtherProfile != null) {
mLastChosenPosition = -1;
return;
}
if (mLastChosen != null
&& mLastChosen.activityInfo.packageName.equals(info.activityInfo.packageName)
&& mLastChosen.activityInfo.name.equals(info.activityInfo.name)) {
@@ -1595,12 +1645,10 @@ public class ResolverActivity extends Activity {
}
}
// We assume that at this point we've already filtered out the only intent for a different
// targetUserId which we're going to use.
private void addResolveInfo(DisplayResolveInfo dri) {
if (dri.mResolveInfo.targetUserId != UserHandle.USER_CURRENT && mOtherProfile == null) {
// So far we only support a single other profile at a time.
// The first one we see gets special treatment.
mOtherProfile = dri;
} else {
if (dri.mResolveInfo.targetUserId == UserHandle.USER_CURRENT) {
mDisplayList.add(dri);
}
}
@@ -1841,7 +1889,8 @@ public class ResolverActivity extends Activity {
final int checkedPos = mAdapterView.getCheckedItemPosition();
final boolean hasValidSelection = checkedPos != ListView.INVALID_POSITION;
if (mAlwaysUseOption && (!hasValidSelection || mLastSelected != checkedPos)) {
if (!useLayoutWithDefault()
&& (!hasValidSelection || mLastSelected != checkedPos)) {
setAlwaysButtonEnabled(hasValidSelection, checkedPos, true);
mOnceButton.setEnabled(hasValidSelection);
if (hasValidSelection) {

View File

@@ -211,6 +211,119 @@ public class ChooserActivityTest {
assertThat(activity.isFinishing(), is(true));
}
@Test
public void hasOtherProfileOneOption() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ChooserWrapperActivity activity = mActivityRule
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
// The other entry is filtered to the other profile slot
assertThat(activity.getAdapter().getCount(), is(1));
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// 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());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@Test
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(ChooserWrapperActivity.sOverrides.resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
final ChooserWrapperActivity activity = mActivityRule
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
// The other entry is filtered to the other profile slot
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// 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();
assertThat(chosen[0], is(toChoose));
}
@Test
public void hasLastChosenActivityAndOtherProfile() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(ChooserWrapperActivity.sOverrides.resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ChooserWrapperActivity activity = mActivityRule
.launchActivity(Intent.createChooser(sendIntent, null));
waitForIdle();
// The other entry is filtered to the last used slot
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
ChooserWrapperActivity.sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// 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();
assertThat(chosen[0], is(toChoose));
}
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -227,6 +340,19 @@ public class ChooserActivityTest {
return infoList;
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
} else {
infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
}
}
return infoList;
}
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}

View File

@@ -43,6 +43,7 @@ import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static com.android.internal.app.ResolverWrapperActivity.sOverrides;
@@ -102,6 +103,7 @@ public class ResolverActivityTest {
public void hasLastChosenActivity() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos = createResolvedComponentsForTest(2);
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
@@ -121,14 +123,133 @@ public class ResolverActivityTest {
return true;
};
ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0);
onView(withId(R.id.title)).perform(click());
onView(withId(R.id.button_once)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@Test
public void hasOtherProfileOneOption() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(2);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
// The other entry is filtered to the last used slot
assertThat(activity.getAdapter().getCount(), is(1));
ResolveInfo[] chosen = new ResolveInfo[1];
sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// 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))
.perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@Test
public void hasOtherProfileTwoOptionsAndUserSelectsOne() throws Exception {
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
// The other entry is filtered to the other profile slot
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// Confirm that the button bar is disabled by default
onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
// 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)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
@Test
public void hasLastChosenActivityAndOtherProfile() throws Exception {
// In this case we prefer the other profile and don't display anything about the last
// chosen activity.
Intent sendIntent = createSendImageIntent();
List<ResolvedComponentInfo> resolvedComponentInfos =
createResolvedComponentsForTestWithOtherProfile(3);
ResolveInfo toChoose = resolvedComponentInfos.get(1).getResolveInfoAt(0);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
waitForIdle();
// The other entry is filtered to the other profile slot
assertThat(activity.getAdapter().getCount(), is(2));
ResolveInfo[] chosen = new ResolveInfo[1];
sOverrides.onSafelyStartCallback = targetInfo -> {
chosen[0] = targetInfo.getResolveInfo();
return true;
};
// Confirm that the button bar is disabled by default
onView(withId(R.id.button_once)).check(matches(not(isEnabled())));
// 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)).perform(click());
waitForIdle();
assertThat(chosen[0], is(toChoose));
}
private Intent createSendImageIntent() {
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
@@ -145,6 +266,19 @@ public class ResolverActivityTest {
return infoList;
}
private List<ResolvedComponentInfo> createResolvedComponentsForTestWithOtherProfile(
int numberOfResults) {
List<ResolvedComponentInfo> infoList = new ArrayList<>(numberOfResults);
for (int i = 0; i < numberOfResults; i++) {
if (i == 0) {
infoList.add(ResolverDataProvider.createResolvedComponentInfoWithOtherId(i));
} else {
infoList.add(ResolverDataProvider.createResolvedComponentInfo(i));
}
}
return infoList;
}
private void waitForIdle() {
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}

View File

@@ -32,9 +32,16 @@ import java.util.concurrent.atomic.AtomicInteger;
*/
class ResolverDataProvider {
static private int USER_SOMEONE_ELSE = 10;
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
createResolverIntent(i), createResolveInfo(i));
createResolverIntent(i), createResolveInfo(i, UserHandle.USER_CURRENT));
}
static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfoWithOtherId(int i) {
return new ResolverActivity.ResolvedComponentInfo(createComponentName(i),
createResolverIntent(i), createResolveInfo(i, USER_SOMEONE_ELSE));
}
static ComponentName createComponentName(int i) {
@@ -42,10 +49,10 @@ class ResolverDataProvider {
return new ComponentName("foo.bar." + name, name);
}
static ResolveInfo createResolveInfo(int i) {
static ResolveInfo createResolveInfo(int i, int userId) {
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.activityInfo = createActivityInfo(i);
resolveInfo.targetUserId = UserHandle.USER_CURRENT;
resolveInfo.targetUserId = userId;
return resolveInfo;
}

View File

@@ -120,8 +120,7 @@ public class UsbResolverActivity extends ResolverActivity {
}
CharSequence title = getResources().getText(com.android.internal.R.string.chooseUsbActivity);
super.onCreate(savedInstanceState, target, title, null, rList,
true /* Set alwaysUseOption to true to enable "always use this app" checkbox. */ );
super.onCreate(savedInstanceState, target, title, null, rList, true);
CheckBox alwaysUse = (CheckBox)findViewById(com.android.internal.R.id.alwaysUse);
if (alwaysUse != null) {