blob: e9c755d3e3f77809961078966c56f21dab5c0b13 [file] [log] [blame]
/*
* 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
*3
* 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.chooser
import android.app.Activity
import android.app.prediction.AppTarget
import android.app.prediction.AppTargetId
import android.content.ComponentName
import android.content.Intent
import android.content.pm.ResolveInfo
import android.os.Bundle
import android.os.UserHandle
import com.android.intentresolver.createShortcutInfo
import com.android.intentresolver.mock
import com.android.intentresolver.ResolverActivity
import com.android.intentresolver.ResolverDataProvider
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class ImmutableTargetInfoTest {
private val resolvedIntent = Intent("resolved")
private val targetIntent = Intent("target")
private val referrerFillInIntent = Intent("referrer_fillin")
private val resolvedComponentName = ComponentName("resolved", "component")
private val chooserTargetComponentName = ComponentName("chooser", "target")
private val resolveInfo = ResolverDataProvider.createResolveInfo(1, 0)
private val displayLabel: CharSequence = "Display Label"
private val extendedInfo: CharSequence = "Extended Info"
private val displayIconHolder: TargetInfo.IconHolder = mock()
private val sourceIntent1 = Intent("source1")
private val sourceIntent2 = Intent("source2")
private val displayTarget1 = DisplayResolveInfo.newDisplayResolveInfo(
Intent("display1"),
ResolverDataProvider.createResolveInfo(2, 0),
"display1 label",
"display1 extended info",
Intent("display1_resolved"),
/* resolveInfoPresentationGetter= */ null)
private val displayTarget2 = DisplayResolveInfo.newDisplayResolveInfo(
Intent("display2"),
ResolverDataProvider.createResolveInfo(3, 0),
"display2 label",
"display2 extended info",
Intent("display2_resolved"),
/* resolveInfoPresentationGetter= */ null)
private val directShareShortcutInfo = createShortcutInfo(
"shortcutid", ResolverDataProvider.createComponentName(4), 4)
private val directShareAppTarget = AppTarget(
AppTargetId("apptargetid"),
"test.directshare",
"target",
UserHandle.CURRENT)
private val displayResolveInfo = DisplayResolveInfo.newDisplayResolveInfo(
Intent("displayresolve"),
ResolverDataProvider.createResolveInfo(5, 0),
"displayresolve label",
"displayresolve extended info",
Intent("display_resolved"),
/* resolveInfoPresentationGetter= */ null)
private val hashProvider: ImmutableTargetInfo.TargetHashProvider = mock()
@Test
fun testBasicProperties() { // Fields that are reflected back w/o logic.
// TODO: we could consider passing copies of all the values into the builder so that we can
// verify that they're not mutated (e.g. no extras added to the intents). For now that
// should be obvious from the implementation.
val info = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(resolvedIntent)
.setTargetIntent(targetIntent)
.setReferrerFillInIntent(referrerFillInIntent)
.setResolvedComponentName(resolvedComponentName)
.setChooserTargetComponentName(chooserTargetComponentName)
.setResolveInfo(resolveInfo)
.setDisplayLabel(displayLabel)
.setExtendedInfo(extendedInfo)
.setDisplayIconHolder(displayIconHolder)
.setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2))
.setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
.setIsSuspended(true)
.setIsPinned(true)
.setModifiedScore(42.0f)
.setDirectShareShortcutInfo(directShareShortcutInfo)
.setDirectShareAppTarget(directShareAppTarget)
.setDisplayResolveInfo(displayResolveInfo)
.setHashProvider(hashProvider)
.build()
assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
assertThat(info.targetIntent).isEqualTo(targetIntent)
assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
assertThat(info.resolveInfo).isEqualTo(resolveInfo)
assertThat(info.displayLabel).isEqualTo(displayLabel)
assertThat(info.extendedInfo).isEqualTo(extendedInfo)
assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
assertThat(info.allSourceIntents).containsExactly(
resolvedIntent, sourceIntent1, sourceIntent2)
assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
assertThat(info.isSuspended).isTrue()
assertThat(info.isPinned).isTrue()
assertThat(info.modifiedScore).isEqualTo(42.0f)
assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isFalse()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isFalse()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isFalse()
assertThat(info.hashProvider).isEqualTo(hashProvider)
}
@Test
fun testToBuilderPreservesBasicProperties() {
// Note this is set up exactly as in `testBasicProperties`, but the assertions will be made
// against a *copy* of the object instead.
val infoToCopyFrom = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(resolvedIntent)
.setTargetIntent(targetIntent)
.setReferrerFillInIntent(referrerFillInIntent)
.setResolvedComponentName(resolvedComponentName)
.setChooserTargetComponentName(chooserTargetComponentName)
.setResolveInfo(resolveInfo)
.setDisplayLabel(displayLabel)
.setExtendedInfo(extendedInfo)
.setDisplayIconHolder(displayIconHolder)
.setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2))
.setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
.setIsSuspended(true)
.setIsPinned(true)
.setModifiedScore(42.0f)
.setDirectShareShortcutInfo(directShareShortcutInfo)
.setDirectShareAppTarget(directShareAppTarget)
.setDisplayResolveInfo(displayResolveInfo)
.setHashProvider(hashProvider)
.build()
val info = infoToCopyFrom.toBuilder().build()
assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
assertThat(info.targetIntent).isEqualTo(targetIntent)
assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
assertThat(info.resolveInfo).isEqualTo(resolveInfo)
assertThat(info.displayLabel).isEqualTo(displayLabel)
assertThat(info.extendedInfo).isEqualTo(extendedInfo)
assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
assertThat(info.allSourceIntents).containsExactly(
resolvedIntent, sourceIntent1, sourceIntent2)
assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
assertThat(info.isSuspended).isTrue()
assertThat(info.isPinned).isTrue()
assertThat(info.modifiedScore).isEqualTo(42.0f)
assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isFalse()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isFalse()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isFalse()
assertThat(info.hashProvider).isEqualTo(hashProvider)
}
@Test
fun testBaseIntentToSend_defaultsToResolvedIntent() {
val info = ImmutableTargetInfo.newBuilder().setResolvedIntent(resolvedIntent).build()
assertThat(info.baseIntentToSend.filterEquals(resolvedIntent)).isTrue()
}
@Test
fun testBaseIntentToSend_fillsInFromReferrerIntent() {
val originalIntent = Intent()
originalIntent.setPackage("original")
val referrerFillInIntent = Intent("REFERRER_FILL_IN")
referrerFillInIntent.setPackage("referrer")
val info = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.setReferrerFillInIntent(referrerFillInIntent)
.build()
assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Only fill if empty.
assertThat(info.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN")
}
@Test
fun testBaseIntentToSend_fillsInFromRefinementIntent() {
val originalIntent = Intent()
originalIntent.putExtra("ORIGINAL", true)
val refinementIntent = Intent()
refinementIntent.putExtra("REFINEMENT", true)
val originalInfo = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.build()
val info = originalInfo.tryToCloneWithAppliedRefinement(refinementIntent)
assertThat(info.baseIntentToSend.getBooleanExtra("ORIGINAL", false)).isTrue()
assertThat(info.baseIntentToSend.getBooleanExtra("REFINEMENT", false)).isTrue()
}
@Test
fun testBaseIntentToSend_twoFillInSourcesFavorsRefinementRequest() {
val originalIntent = Intent("REFINE_ME")
originalIntent.setPackage("original")
val referrerFillInIntent = Intent("REFERRER_FILL_IN")
referrerFillInIntent.setPackage("referrer_pkg")
referrerFillInIntent.setType("test/referrer")
val infoWithReferrerFillIn = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.setReferrerFillInIntent(referrerFillInIntent)
.build()
val refinementIntent = Intent("REFINE_ME")
refinementIntent.setPackage("original") // Has to match for refinement.
val info = infoWithReferrerFillIn.tryToCloneWithAppliedRefinement(refinementIntent)
assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Set all along.
assertThat(info.baseIntentToSend.action).isEqualTo("REFINE_ME") // Refinement wins.
assertThat(info.baseIntentToSend.type).isEqualTo("test/referrer") // Left for referrer.
}
@Test
fun testBaseIntentToSend_doubleRefinementPreservesReferrerFillInButNotOriginalRefinement() {
val originalIntent = Intent("REFINE_ME")
val referrerFillInIntent = Intent("REFERRER_FILL_IN")
referrerFillInIntent.putExtra("TEST", "REFERRER")
val refinementIntent1 = Intent("REFINE_ME")
refinementIntent1.putExtra("TEST1", "1")
val refinementIntent2 = Intent("REFINE_ME")
refinementIntent2.putExtra("TEST2", "2")
val originalInfo = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.setReferrerFillInIntent(referrerFillInIntent)
.build()
val refined1 = originalInfo.tryToCloneWithAppliedRefinement(refinementIntent1)
val refined2 = refined1.tryToCloneWithAppliedRefinement(refinementIntent2) // Cloned clone.
// Both clones get the same values filled in from the referrer intent.
assertThat(refined1.baseIntentToSend.getStringExtra("TEST")).isEqualTo("REFERRER")
assertThat(refined2.baseIntentToSend.getStringExtra("TEST")).isEqualTo("REFERRER")
// Each clone has the respective value that was set in their own refinement request.
assertThat(refined1.baseIntentToSend.getStringExtra("TEST1")).isEqualTo("1")
assertThat(refined2.baseIntentToSend.getStringExtra("TEST2")).isEqualTo("2")
// The clones don't have the data from each other's refinements, even though the intent
// field is empty (thus able to be populated by filling-in).
assertThat(refined1.baseIntentToSend.getStringExtra("TEST2")).isNull()
assertThat(refined2.baseIntentToSend.getStringExtra("TEST1")).isNull()
}
@Test
fun testBaseIntentToSend_refinementToAlternateSourceIntent() {
val originalIntent = Intent("DONT_REFINE_ME")
originalIntent.putExtra("originalIntent", true)
val mismatchedAlternate = Intent("DOESNT_MATCH")
mismatchedAlternate.putExtra("mismatchedAlternate", true)
val targetAlternate = Intent("REFINE_ME")
targetAlternate.putExtra("targetAlternate", true)
val extraMatch = Intent("REFINE_ME")
extraMatch.putExtra("extraMatch", true)
val originalInfo = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.setAllSourceIntents(listOf(
originalIntent, mismatchedAlternate, targetAlternate, extraMatch))
.build()
val refinement = Intent("REFINE_ME") // First match is `targetAlternate`
refinement.putExtra("refinement", true)
val refinedResult = originalInfo.tryToCloneWithAppliedRefinement(refinement)
assertThat(refinedResult.baseIntentToSend.getBooleanExtra("refinement", false)).isTrue()
assertThat(refinedResult.baseIntentToSend.getBooleanExtra("targetAlternate", false))
.isTrue()
// None of the other source intents got merged in (not even the later one that matched):
assertThat(refinedResult.baseIntentToSend.getBooleanExtra("originalIntent", false))
.isFalse()
assertThat(refinedResult.baseIntentToSend.getBooleanExtra("mismatchedAlternate", false))
.isFalse()
assertThat(refinedResult.baseIntentToSend.getBooleanExtra("extraMatch", false)).isFalse()
}
@Test
fun testBaseIntentToSend_noSourceIntentMatchingProposedRefinement() {
val originalIntent = Intent("DONT_REFINE_ME")
originalIntent.putExtra("originalIntent", true)
val mismatchedAlternate = Intent("DOESNT_MATCH")
mismatchedAlternate.putExtra("mismatchedAlternate", true)
val originalInfo = ImmutableTargetInfo.newBuilder()
.setResolvedIntent(originalIntent)
.setAllSourceIntents(listOf(originalIntent, mismatchedAlternate))
.build()
val refinement = Intent("PROPOSED_REFINEMENT")
assertThat(originalInfo.tryToCloneWithAppliedRefinement(refinement)).isNull()
}
@Test
fun testLegacySubclassRelationships_empty() {
val info = ImmutableTargetInfo.newBuilder()
.setLegacyType(ImmutableTargetInfo.LegacyTargetType.EMPTY_TARGET_INFO)
.build()
assertThat(info.isEmptyTargetInfo).isTrue()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isTrue()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isTrue()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isFalse()
}
@Test
fun testLegacySubclassRelationships_placeholder() {
val info = ImmutableTargetInfo.newBuilder()
.setLegacyType(ImmutableTargetInfo.LegacyTargetType.PLACEHOLDER_TARGET_INFO)
.build()
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isTrue()
assertThat(info.isNotSelectableTargetInfo).isTrue()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isTrue()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isFalse()
}
@Test
fun testLegacySubclassRelationships_selectable() {
val info = ImmutableTargetInfo.newBuilder()
.setLegacyType(ImmutableTargetInfo.LegacyTargetType.SELECTABLE_TARGET_INFO)
.build()
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isFalse()
assertThat(info.isSelectableTargetInfo).isTrue()
assertThat(info.isChooserTargetInfo).isTrue()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isFalse()
}
@Test
fun testLegacySubclassRelationships_displayResolveInfo() {
val info = ImmutableTargetInfo.newBuilder()
.setLegacyType(ImmutableTargetInfo.LegacyTargetType.DISPLAY_RESOLVE_INFO)
.build()
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isFalse()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isFalse()
assertThat(info.isMultiDisplayResolveInfo).isFalse()
assertThat(info.isDisplayResolveInfo).isTrue()
}
@Test
fun testLegacySubclassRelationships_multiDisplayResolveInfo() {
val info = ImmutableTargetInfo.newBuilder()
.setLegacyType(ImmutableTargetInfo.LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO)
.build()
assertThat(info.isEmptyTargetInfo).isFalse()
assertThat(info.isPlaceHolderTargetInfo).isFalse()
assertThat(info.isNotSelectableTargetInfo).isFalse()
assertThat(info.isSelectableTargetInfo).isFalse()
assertThat(info.isChooserTargetInfo).isFalse()
assertThat(info.isMultiDisplayResolveInfo).isTrue()
assertThat(info.isDisplayResolveInfo).isTrue()
}
@Test
fun testActivityStarter_correctNumberOfInvocations_startAsCaller() {
val activityStarter = object : TestActivityStarter() {
override fun startAsUser(
target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle
): Boolean {
throw RuntimeException("Wrong API used: startAsUser")
}
}
val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
val activity: ResolverActivity = mock()
val options = Bundle()
options.putInt("TEST_KEY", 1)
info.startAsCaller(activity, options, 42)
assertThat(activityStarter.totalInvocations).isEqualTo(1)
assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
assertThat(activityStarter.lastInvocationAsCaller).isTrue()
}
@Test
fun testActivityStarter_correctNumberOfInvocations_startAsUser() {
val activityStarter = object : TestActivityStarter() {
override fun startAsCaller(
target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean {
throw RuntimeException("Wrong API used: startAsCaller")
}
}
val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
val activity: Activity = mock()
val options = Bundle()
options.putInt("TEST_KEY", 1)
info.startAsUser(activity, options, UserHandle.of(42))
assertThat(activityStarter.totalInvocations).isEqualTo(1)
assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
assertThat(activityStarter.lastInvocationAsCaller).isFalse()
}
@Test
fun testActivityStarter_invokedWithRespectiveTargetInfoAfterCopy() {
val activityStarter = TestActivityStarter()
val info1 = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
val info2 = info1.toBuilder().build()
info1.startAsCaller(mock(), Bundle(), 42)
assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info1)
info2.startAsCaller(mock(), Bundle(), 42)
assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
info2.startAsUser(mock(), Bundle(), UserHandle.of(42))
assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
assertThat(activityStarter.totalInvocations).isEqualTo(3) // Instance is still shared.
}
}
private open class TestActivityStarter : ImmutableTargetInfo.TargetActivityStarter {
var totalInvocations = 0
var lastInvocationTargetInfo: TargetInfo? = null
var lastInvocationActivity: Activity? = null
var lastInvocationOptions: Bundle? = null
var lastInvocationUserId: Integer? = null
var lastInvocationAsCaller = false
override fun startAsCaller(
target: TargetInfo, activity: Activity, options: Bundle, userId: Int): Boolean {
++totalInvocations
lastInvocationTargetInfo = target
lastInvocationActivity = activity
lastInvocationOptions = options
lastInvocationUserId = Integer(userId)
lastInvocationAsCaller = true
return true
}
override fun startAsUser(
target: TargetInfo, activity: Activity, options: Bundle, user: UserHandle): Boolean {
++totalInvocations
lastInvocationTargetInfo = target
lastInvocationActivity = activity
lastInvocationOptions = options
lastInvocationUserId = Integer(user.identifier)
lastInvocationAsCaller = false
return true
}
}