diff options
| author | 2023-09-11 10:53:02 -0400 | |
|---|---|---|
| committer | 2023-10-10 15:51:55 -0400 | |
| commit | 35018884b00755252f5268507065a7ea6cb9404b (patch) | |
| tree | 642993f8396e95e561a58b6b9022a2614ea24122 /java/tests | |
| parent | 73bad17b4fa5a7c07409ed6d46121614b48adb33 (diff) | |
Inject ComponentNames for image editor and nearby share
Adds a SecureSettings fake
Adds test coverage for new modules
Removes another overload from ChooserActivityWrapper
Uses @BindValue in tests to alter the configured editor component
Test: atest --test-mapping packages/modules/IntentResolver
Bug: 300157408
Bug: 302113519
Change-Id: Ie7d5fe12ad0d8e7fd074154641de35fe89d50ce6
Diffstat (limited to 'java/tests')
8 files changed, 453 insertions, 28 deletions
diff --git a/java/tests/Android.bp b/java/tests/Android.bp index 5244bf7b..a17400f8 100644 --- a/java/tests/Android.bp +++ b/java/tests/Android.bp @@ -50,7 +50,8 @@ android_test { "kotlinx_coroutines_test", "mockito-target-minus-junit4", "testables", - "truth-prebuilt", + "truth", + "truth-java8-extension", "flag-junit", "platform-test-annotations", ], diff --git a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java index 73977f86..53a505df 100644 --- a/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/UnbundledChooserActivityTest.java @@ -2751,7 +2751,7 @@ public class UnbundledChooserActivityTest { final ChooserActivity activity = mActivityRule.launchActivity( Intent.createChooser(new Intent("ACTION_FOO"), "foo")); waitForIdle(); - assertThat(activity).isInstanceOf(com.android.intentresolver.ChooserWrapperActivity.class); + assertThat(activity).isInstanceOf(ChooserWrapperActivity.class); } private ResolveInfo createFakeResolveInfo() { diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt new file mode 100644 index 00000000..a1a9bc92 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/ChooserActionFactoryTest.kt @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2023 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.intentresolver.v2 + +import android.app.Activity +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Context.RECEIVER_EXPORTED +import android.content.Intent +import android.content.IntentFilter +import android.content.res.Resources +import android.graphics.drawable.Icon +import android.service.chooser.ChooserAction +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.intentresolver.ChooserRequestParameters +import com.android.intentresolver.logging.EventLog +import com.android.intentresolver.mock +import com.android.intentresolver.whenever +import com.google.common.collect.ImmutableList +import com.google.common.truth.Truth.assertThat +import java.util.Optional +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.function.Consumer +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito + +@RunWith(AndroidJUnit4::class) +class ChooserActionFactoryTest { + private val context = InstrumentationRegistry.getInstrumentation().context + + private val logger = mock<EventLog>() + private val actionLabel = "Action label" + private val modifyShareLabel = "Modify share" + private val testAction = "com.android.intentresolver.testaction" + private val countdown = CountDownLatch(1) + private val testReceiver: BroadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + // Just doing at most a single countdown per test. + countdown.countDown() + } + } + private val resultConsumer = + object : Consumer<Int> { + var latestReturn = Integer.MIN_VALUE + + override fun accept(resultCode: Int) { + latestReturn = resultCode + } + } + + @Before + fun setup() { + context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED) + } + + @After + fun teardown() { + context.unregisterReceiver(testReceiver) + } + + @Test + fun testCreateCustomActions() { + val factory = createFactory() + + val customActions = factory.createCustomActions() + + assertThat(customActions.size).isEqualTo(1) + assertThat(customActions[0].label).isEqualTo(actionLabel) + + // click it + customActions[0].onClicked.run() + + Mockito.verify(logger).logCustomActionSelected(eq(0)) + assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) + // Verify the pending intent has been called + assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) + } + + @Test + fun testNoModifyShareAction() { + val factory = createFactory(includeModifyShare = false) + + assertThat(factory.modifyShareAction).isNull() + } + + @Test + fun testModifyShareAction() { + val factory = createFactory(includeModifyShare = true) + + val action = factory.modifyShareAction ?: error("Modify share action should not be null") + action.onClicked.run() + + Mockito.verify(logger).logActionSelected(eq(EventLog.SELECTION_TYPE_MODIFY_SHARE)) + assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) + // Verify the pending intent has been called + assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) + } + + @Test + fun nonSendAction_noCopyRunnable() { + val targetIntent = + Intent(Intent.ACTION_SEND_MULTIPLE).apply { + putExtra(Intent.EXTRA_TEXT, "Text to show") + } + + val chooserRequest = + mock<ChooserRequestParameters> { + whenever(this.targetIntent).thenReturn(targetIntent) + whenever(chooserActions).thenReturn(ImmutableList.of()) + } + val testSubject = + ChooserActionFactory( + context, + chooserRequest, + Optional.empty(), + logger, + {}, + { null }, + mock(), + {}, + ) + assertThat(testSubject.copyButtonRunnable).isNull() + } + + @Test + fun sendActionNoText_noCopyRunnable() { + val targetIntent = Intent(Intent.ACTION_SEND) + + val chooserRequest = + mock<ChooserRequestParameters> { + whenever(this.targetIntent).thenReturn(targetIntent) + whenever(chooserActions).thenReturn(ImmutableList.of()) + } + val testSubject = + ChooserActionFactory( + context, + chooserRequest, + Optional.empty(), + logger, + {}, + { null }, + mock(), + {}, + ) + assertThat(testSubject.copyButtonRunnable).isNull() + } + + @Test + fun sendActionWithText_nonNullCopyRunnable() { + val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") } + + val chooserRequest = + mock<ChooserRequestParameters> { + whenever(this.targetIntent).thenReturn(targetIntent) + whenever(chooserActions).thenReturn(ImmutableList.of()) + } + val testSubject = + ChooserActionFactory( + context, + chooserRequest, + Optional.empty(), + logger, + {}, + { null }, + mock(), + {}, + ) + assertThat(testSubject.copyButtonRunnable).isNotNull() + } + + private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory { + val testPendingIntent = + PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE) + val targetIntent = Intent() + val action = + ChooserAction.Builder( + Icon.createWithResource("", Resources.ID_NULL), + actionLabel, + testPendingIntent + ) + .build() + val chooserRequest = mock<ChooserRequestParameters>() + whenever(chooserRequest.targetIntent).thenReturn(targetIntent) + whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action)) + + if (includeModifyShare) { + val modifyShare = + ChooserAction.Builder( + Icon.createWithResource("", Resources.ID_NULL), + modifyShareLabel, + testPendingIntent + ) + .build() + whenever(chooserRequest.modifyShareAction).thenReturn(modifyShare) + } + + return ChooserActionFactory( + context, + chooserRequest, + Optional.empty(), + logger, + {}, + { null }, + mock(), + resultConsumer + ) + } +} diff --git a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java index 41b31d01..65d33485 100644 --- a/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java +++ b/java/tests/src/com/android/intentresolver/v2/ChooserWrapperActivity.java @@ -19,7 +19,6 @@ package com.android.intentresolver.v2; import android.annotation.Nullable; import android.app.prediction.AppPredictor; import android.app.usage.UsageStatsManager; -import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -35,7 +34,6 @@ import android.os.UserHandle; import androidx.lifecycle.ViewModelProvider; import com.android.intentresolver.AnnotatedUserHandles; -import com.android.intentresolver.ChooserIntegratedDeviceComponents; import com.android.intentresolver.ChooserListAdapter; import com.android.intentresolver.ChooserRequestParameters; import com.android.intentresolver.IChooserWrapper; @@ -128,16 +126,6 @@ public class ChooserWrapperActivity extends ChooserActivity implements IChooserW } @Override - protected ChooserIntegratedDeviceComponents getIntegratedDeviceComponents() { - return new ChooserIntegratedDeviceComponents( - /* editSharingComponent=*/ null, - // An arbitrary pre-installed activity that handles this type of intent: - /* nearbySharingComponent=*/ new ComponentName( - "com.google.android.apps.messaging", - ".ui.conversationlist.ShareIntentActivity")); - } - - @Override public UsageStatsManager getUsageStatsManager() { if (mUsm == null) { mUsm = getSystemService(UsageStatsManager.class); diff --git a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java index 1e74c7a5..4a8a5568 100644 --- a/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java +++ b/java/tests/src/com/android/intentresolver/v2/UnbundledChooserActivityTest.java @@ -17,6 +17,7 @@ package com.android.intentresolver.v2; import static android.app.Activity.RESULT_OK; + import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.action.ViewActions.longClick; @@ -29,16 +30,20 @@ import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static androidx.test.espresso.matcher.ViewMatchers.withText; + +import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET; +import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_DEFAULT; +import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; +import static com.android.intentresolver.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; import static com.android.intentresolver.ChooserListAdapter.CALLER_TARGET_SCORE_BOOST; import static com.android.intentresolver.ChooserListAdapter.SHORTCUT_TARGET_SCORE_BOOST; import static com.android.intentresolver.MatcherUtils.first; -import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_CHOOSER_TARGET; -import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_DEFAULT; -import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_PREDICTION_SERVICE; -import static com.android.intentresolver.v2.ChooserActivity.TARGET_TYPE_SHORTCUTS_FROM_SHORTCUT_MANAGER; + import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; + import static junit.framework.Assert.assertNull; + import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; @@ -126,9 +131,16 @@ import com.android.intentresolver.contentpreview.ImageLoader; import com.android.intentresolver.logging.EventLog; import com.android.intentresolver.logging.FakeEventLog; import com.android.intentresolver.shortcuts.ShortcutLoader; +import com.android.intentresolver.v2.platform.ImageEditor; +import com.android.intentresolver.v2.platform.ImageEditorModule; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import dagger.hilt.android.testing.BindValue; +import dagger.hilt.android.testing.HiltAndroidRule; +import dagger.hilt.android.testing.HiltAndroidTest; +import dagger.hilt.android.testing.UninstallModules; + import org.hamcrest.Description; import org.hamcrest.Matcher; import org.hamcrest.Matchers; @@ -148,6 +160,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -157,17 +170,14 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; -import dagger.hilt.android.testing.HiltAndroidRule; -import dagger.hilt.android.testing.HiltAndroidTest; - /** * Instrumentation tests for ChooserActivity. * <p> * Legacy test suite migrated from framework CoreTests. - * <p> */ @RunWith(Parameterized.class) @HiltAndroidTest +@UninstallModules(ImageEditorModule.class) public class UnbundledChooserActivityTest { private static FakeEventLog getEventLog(ChooserWrapperActivity activity) { @@ -228,6 +238,13 @@ public class UnbundledChooserActivityTest { private final Function<PackageManager, PackageManager> mPackageManagerOverride; + /** An arbitrary pre-installed activity that handles this type of intent. */ + @BindValue + @ImageEditor + final Optional<ComponentName> mImageEditor = Optional.ofNullable( + ComponentName.unflattenFromString("com.google.android.apps.messaging/" + + ".ui.conversationlist.ShareIntentActivity")); + public UnbundledChooserActivityTest( Function<PackageManager, PackageManager> packageManagerOverride) { mPackageManagerOverride = packageManagerOverride; @@ -897,10 +914,9 @@ public class UnbundledChooserActivityTest { // TODO(b/211669337): Determine the expected SHARESHEET_DIRECT_LOAD_COMPLETE events. } - - @Test @Ignore public void testEditImageLogs() { + Uri uri = createTestContentProviderUri("image/png", null); Intent sendIntent = createSendImageIntent(uri); ChooserActivityOverrideData.getInstance().imageLoader = @@ -2195,17 +2211,17 @@ public class UnbundledChooserActivityTest { mActivityRule.launchActivity(Intent.createChooser(sendIntent, "Scrollable preview test")); waitForIdle(); - onView(withId(R.id.scrollable_image_preview)) + onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) .check(matches(isDisplayed())); onView(withId(com.android.internal.R.id.contentPanel)).perform(swipeUp()); waitForIdle(); - onView(withId(R.id.chooser_headline_row_container)) + onView(withId(com.android.intentresolver.R.id.chooser_headline_row_container)) .check(matches(isCompletelyDisplayed())); - onView(withId(R.id.headline)) + onView(withId(com.android.intentresolver.R.id.headline)) .check(matches(isDisplayed())); - onView(withId(R.id.scrollable_image_preview)) + onView(withId(com.android.intentresolver.R.id.scrollable_image_preview)) .check(matches(not(isDisplayed()))); } diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt new file mode 100644 index 00000000..4e279623 --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettings.kt @@ -0,0 +1,44 @@ +package com.android.intentresolver.v2.platform + +/** + * Creates a SecureSettings instance with predefined values: + * + * val settings = fakeSecureSettings { + * putString("stringValue", "example") + * putInt("intValue", 42) + * } + */ +fun fakeSecureSettings(block: FakeSecureSettings.Builder.() -> Unit): SecureSettings { + return FakeSecureSettings.Builder().apply(block).build() +} + +/** An in memory implementation of [SecureSettings]. */ +class FakeSecureSettings private constructor(private val map: Map<String, String>) : + SecureSettings { + + override fun getString(name: String): String? = map[name] + override fun getInt(name: String): Int? = getString(name)?.toIntOrNull() + override fun getLong(name: String): Long? = getString(name)?.toLongOrNull() + override fun getFloat(name: String): Float? = getString(name)?.toFloatOrNull() + + class Builder { + private val map = mutableMapOf<String, String>() + + fun putString(name: String, value: String) { + map[name] = value + } + fun putInt(name: String, value: Int) { + map[name] = value.toString() + } + fun putLong(name: String, value: Long) { + map[name] = value.toString() + } + fun putFloat(name: String, value: Float) { + map[name] = value.toString() + } + + fun build(): SecureSettings { + return FakeSecureSettings(map.toMap()) + } + } +} diff --git a/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt new file mode 100644 index 00000000..04c7093d --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/platform/FakeSecureSettingsTest.kt @@ -0,0 +1,61 @@ +package com.android.intentresolver.v2.platform + +import com.google.common.truth.Truth.assertThat + +class FakeSecureSettingsTest { + + private val secureSettings = fakeSecureSettings { + putInt(intKey, intVal) + putString(stringKey, stringVal) + putFloat(floatKey, floatVal) + putLong(longKey, longVal) + } + + fun testExpectedValues_returned() { + assertThat(secureSettings.getInt(intKey)).isEqualTo(intVal) + assertThat(secureSettings.getString(stringKey)).isEqualTo(stringVal) + assertThat(secureSettings.getFloat(floatKey)).isEqualTo(floatVal) + assertThat(secureSettings.getLong(longKey)).isEqualTo(longVal) + } + + fun testUndefinedValues_returnNull() { + assertThat(secureSettings.getInt("unknown")).isNull() + assertThat(secureSettings.getString("unknown")).isNull() + assertThat(secureSettings.getFloat("unknown")).isNull() + assertThat(secureSettings.getLong("unknown")).isNull() + } + + /** + * FakeSecureSettings models the real secure settings by storing values in String form. The + * value is returned if/when it can be parsed from the string value, otherwise null. + */ + fun testMismatchedTypes() { + assertThat(secureSettings.getString(intKey)).isEqualTo(intVal.toString()) + assertThat(secureSettings.getString(floatKey)).isEqualTo(floatVal.toString()) + assertThat(secureSettings.getString(longKey)).isEqualTo(longVal.toString()) + + assertThat(secureSettings.getInt(stringKey)).isNull() + assertThat(secureSettings.getLong(stringKey)).isNull() + assertThat(secureSettings.getFloat(stringKey)).isNull() + + assertThat(secureSettings.getInt(longKey)).isNull() + assertThat(secureSettings.getFloat(longKey)).isNull() // TODO: verify Long.MAX > Float.MAX ? + + assertThat(secureSettings.getLong(floatKey)).isNull() // TODO: or is Float.MAX > Long.MAX? + assertThat(secureSettings.getInt(floatKey)).isNull() + } + + companion object Data { + const val intKey = "int" + const val intVal = Int.MAX_VALUE + + const val stringKey = "string" + const val stringVal = "String" + + const val floatKey = "float" + const val floatVal = Float.MAX_VALUE + + const val longKey = "long" + const val longVal = Long.MAX_VALUE + } +} diff --git a/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt b/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt new file mode 100644 index 00000000..fd5c8b3f --- /dev/null +++ b/java/tests/src/com/android/intentresolver/v2/platform/NearbyShareModuleTest.kt @@ -0,0 +1,83 @@ +package com.android.intentresolver.v2.platform + +import android.content.ComponentName +import android.content.Context +import android.content.res.Configuration +import android.provider.Settings +import android.testing.TestableResources + +import androidx.test.platform.app.InstrumentationRegistry + +import com.android.intentresolver.R + +import com.google.common.truth.Truth8.assertThat + +import org.junit.Before +import org.junit.Test + +class NearbyShareModuleTest { + + lateinit var context: Context + + /** Create Resources with overridden values. */ + private fun Context.fakeResources( + config: Configuration? = null, + block: TestableResources.() -> Unit + ) = + TestableResources(resources) + .apply { config?.let { overrideConfiguration(it) } } + .apply(block) + .resources + + @Before + fun setup() { + val instr = InstrumentationRegistry.getInstrumentation() + context = instr.context + } + + @Test + fun valueIsAbsent_whenUnset() { + val secureSettings = fakeSecureSettings {} + val resources = + context.fakeResources { addOverride(R.string.config_defaultNearbySharingComponent, "") } + + val componentName = NearbyShareModule.nearbyShareComponent(resources, secureSettings) + assertThat(componentName).isEmpty() + } + + @Test + fun defaultValue_readFromResources() { + val secureSettings = fakeSecureSettings {} + val resources = + context.fakeResources { + addOverride( + R.string.config_defaultNearbySharingComponent, + "com.example/.ComponentName" + ) + } + + val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings) + + assertThat(nearbyShareComponent).hasValue( + ComponentName.unflattenFromString("com.example/.ComponentName")) + } + + @Test + fun secureSettings_overridesDefault() { + val secureSettings = fakeSecureSettings { + putString(Settings.Secure.NEARBY_SHARING_COMPONENT, "com.example/.BComponent") + } + val resources = + context.fakeResources { + addOverride( + R.string.config_defaultNearbySharingComponent, + "com.example/.AComponent" + ) + } + + val nearbyShareComponent = NearbyShareModule.nearbyShareComponent(resources, secureSettings) + + assertThat(nearbyShareComponent).hasValue( + ComponentName.unflattenFromString("com.example/.BComponent")) + } +} |