From 9149dcaf414ebdac231af93d44abd5951a514a7e Mon Sep 17 00:00:00 2001 From: Hakan Seyalioglu Date: Tue, 17 Jan 2017 12:20:01 -0800 Subject: [PATCH] Add ResolverActivity testing Still very basic, but will be adding as we find more issues pop up. Test: Ran the tests Change-Id: Ib9863509d140ff425ce446ecc876f53494d4fd0e --- .../internal/app/ResolverActivity.java | 10 +- .../internal/app/ResolverListController.java | 19 +++ core/tests/coretests/AndroidManifest.xml | 1 + .../internal/app/ChooserActivityTest.java | 2 +- .../internal/app/ChooserWrapperActivity.java | 9 +- .../internal/app/ResolverActivityTest.java | 151 ++++++++++++++++++ ...rovider.java => ResolverDataProvider.java} | 5 +- .../internal/app/ResolverWrapperActivity.java | 91 +++++++++++ 8 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java rename core/tests/coretests/src/com/android/internal/app/{ChooserDataProvider.java => ResolverDataProvider.java} (94%) create mode 100644 core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index d734d17b2dc9..ab1d9b9c6f6e 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -749,10 +749,7 @@ public class ResolverActivity extends Activity { } } else { try { - AppGlobals.getPackageManager().setLastChosenActivity(intent, - intent.resolveType(getContentResolver()), - PackageManager.MATCH_DEFAULT_ONLY, - filter, bestMatch, intent.getComponent()); + mAdapter.mResolverListController.setLastChosen(intent, filter, bestMatch); } catch (RemoteException re) { Log.d(TAG, "Error calling setLastChosenActivity\n" + re); } @@ -1312,10 +1309,7 @@ public class ResolverActivity extends Activity { protected boolean rebuildList() { List currentResolveList = null; try { - final Intent primaryIntent = getTargetIntent(); - mLastChosen = AppGlobals.getPackageManager().getLastChosenActivity( - primaryIntent, primaryIntent.resolveTypeIfNeeded(getContentResolver()), - PackageManager.MATCH_DEFAULT_ONLY); + mLastChosen = mResolverListController.getLastChosen(); } catch (RemoteException re) { Log.d(TAG, "Error calling getLastChosenActivity\n" + re); } diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java index f88f6f9a07e0..00faf65d004c 100644 --- a/core/java/com/android/internal/app/ResolverListController.java +++ b/core/java/com/android/internal/app/ResolverListController.java @@ -19,13 +19,16 @@ package com.android.internal.app; import android.annotation.WorkerThread; import android.app.ActivityManager; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.os.RemoteException; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; @@ -65,6 +68,22 @@ public class ResolverListController { mReferrerPackage = referrerPackage; } + @VisibleForTesting + ResolveInfo getLastChosen() throws RemoteException { + return AppGlobals.getPackageManager().getLastChosenActivity( + mTargetIntent, mTargetIntent.resolveTypeIfNeeded(mContext.getContentResolver()), + PackageManager.MATCH_DEFAULT_ONLY); + } + + @VisibleForTesting + void setLastChosen(Intent intent, IntentFilter filter, int match) + throws RemoteException { + AppGlobals.getPackageManager().setLastChosenActivity(intent, + intent.resolveType(mContext.getContentResolver()), + PackageManager.MATCH_DEFAULT_ONLY, + filter, match, intent.getComponent()); + } + @VisibleForTesting public List getResolversForIntent( boolean shouldGetResolvedFilter, diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index cba485a606cc..91ce7a468484 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -1168,6 +1168,7 @@ + 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 aab4698fc49a..daebf888f715 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -222,7 +222,7 @@ public class ChooserActivityTest { private List createResolvedComponentsForTest(int numberOfResults) { List infoList = new ArrayList<>(numberOfResults); for (int i = 0; i < numberOfResults; i++) { - infoList.add(ChooserDataProvider.createResolvedComponentInfo(i)); + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); } return infoList; } 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 41016e12bbf9..c446f3c79ea8 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -24,11 +24,10 @@ import java.util.function.Function; import static org.mockito.Mockito.mock; - -/** - * Simple wrapper around chooser activity to be able to initiate it under test - */ public class ChooserWrapperActivity extends ChooserActivity { + /* + * Simple wrapper around chooser activity to be able to initiate it under test + */ static final OverrideData sOverrides = new OverrideData(); private UsageStatsManager mUsm; @@ -94,4 +93,4 @@ public class ChooserWrapperActivity extends ChooserActivity { resolverListController = mock(ResolverListController.class); } } -} \ No newline at end of file +} diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java new file mode 100644 index 000000000000..84b844a5b4b6 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2016 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 com.android.internal.R; +import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.UserHandle; +import android.support.test.InstrumentationRegistry; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static android.os.SystemClock.sleep; +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.withId; +import static android.support.test.espresso.matcher.ViewMatchers.withText; +import static com.android.internal.app.ResolverWrapperActivity.sOverrides; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * Resolver activity instrumentation tests + */ +@RunWith(AndroidJUnit4.class) +public class ResolverActivityTest { + @Rule + public ActivityTestRule mActivityRule = + new ActivityTestRule<>(ResolverWrapperActivity.class, false, + false); + + @Before + public void cleanOverrideData() { + sOverrides.reset(); + } + + @Test + public void twoOptionsAndUserSelectsOne() throws InterruptedException { + Intent sendIntent = createSendImageIntent(); + List resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + assertThat(activity.getAdapter().getCount(), is(2)); + + ResolveInfo[] chosen = new ResolveInfo[1]; + sOverrides.onSafelyStartCallback = targetInfo -> { + chosen[0] = targetInfo.getResolveInfo(); + return true; + }; + + ResolveInfo toChoose = resolvedComponentInfos.get(0).getResolveInfoAt(0); + onView(withText(toChoose.activityInfo.name)) + .perform(click()); + onView(withId(R.id.button_once)) + .perform(click()); + waitForIdle(); + assertThat(chosen[0], is(toChoose)); + } + + @Test + public void hasLastChosenActivity() throws Exception { + Intent sendIntent = createSendImageIntent(); + List resolvedComponentInfos = createResolvedComponentsForTest(2); + + when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(), + Mockito.anyBoolean(), + Mockito.isA(List.class))).thenReturn(resolvedComponentInfos); + when(sOverrides.resolverListController.getLastChosen()) + .thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0)); + + final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent); + waitForIdle(); + + // 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; + }; + + 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)); + } + + private Intent createSendImageIntent() { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, "testing intent sending"); + sendIntent.setType("image/jpeg"); + return sendIntent; + } + + private List createResolvedComponentsForTest(int numberOfResults) { + List infoList = new ArrayList<>(numberOfResults); + for (int i = 0; i < numberOfResults; i++) { + infoList.add(ResolverDataProvider.createResolvedComponentInfo(i)); + } + return infoList; + } + + private void waitForIdle() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + } +} \ No newline at end of file diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java similarity index 94% rename from core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java rename to core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java index f6f63f1de81e..ae063065afad 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserDataProvider.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverDataProvider.java @@ -22,16 +22,15 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.os.UserHandle; -import android.service.chooser.ChooserTarget; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** - * Utility class used by chooser tests to create mock data + * Utility class used by resolver tests to create mock data */ -class ChooserDataProvider { +class ResolverDataProvider { static ResolverActivity.ResolvedComponentInfo createResolvedComponentInfo(int i) { return new ResolverActivity.ResolvedComponentInfo(createComponentName(i), diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java new file mode 100644 index 000000000000..163211e10e6a --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/app/ResolverWrapperActivity.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2017 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 android.app.usage.UsageStatsManager; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.os.RemoteException; + +import java.util.function.Function; + +import static org.mockito.Mockito.mock; + + +/* + * Simple wrapper around chooser activity to be able to initiate it under test + */ +public class ResolverWrapperActivity extends ResolverActivity { + static final OverrideData sOverrides = new OverrideData(); + private UsageStatsManager mUsm; + + ResolveListAdapter getAdapter() { + return mAdapter; + } + + @Override + public boolean isVoiceInteraction() { + if (sOverrides.isVoiceInteraction != null) { + return sOverrides.isVoiceInteraction; + } + return super.isVoiceInteraction(); + } + + @Override + public void safelyStartActivity(TargetInfo cti) { + if (sOverrides.onSafelyStartCallback != null && + sOverrides.onSafelyStartCallback.apply(cti)) { + return; + } + super.safelyStartActivity(cti); + } + + @Override + protected ResolverListController createListController() { + return sOverrides.resolverListController; + } + + @Override + public PackageManager getPackageManager() { + if (sOverrides.createPackageManager != null) { + return sOverrides.createPackageManager.apply(super.getPackageManager()); + } + return super.getPackageManager(); + } + + /** + * We cannot directly mock the activity created since instrumentation creates it. + *

+ * Instead, we use static instances of this object to modify behavior. + */ + static class OverrideData { + @SuppressWarnings("Since15") + public Function createPackageManager; + public Function onSafelyStartCallback; + public ResolverListController resolverListController; + public Boolean isVoiceInteraction; + + public void reset() { + onSafelyStartCallback = null; + isVoiceInteraction = null; + createPackageManager = null; + resolverListController = mock(ResolverListController.class); + } + } +} \ No newline at end of file -- GitLab