1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *3
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.intentresolver.chooser
18 
19 import android.app.Activity
20 import android.app.prediction.AppTarget
21 import android.app.prediction.AppTargetId
22 import android.content.ComponentName
23 import android.content.Intent
24 import android.os.Bundle
25 import android.os.UserHandle
26 import androidx.test.platform.app.InstrumentationRegistry
27 import com.android.intentresolver.ResolverActivity
28 import com.android.intentresolver.ResolverDataProvider
29 import com.android.intentresolver.createShortcutInfo
30 import com.google.common.truth.Truth.assertThat
31 import org.junit.Test
32 import org.mockito.kotlin.mock
33 
34 class ImmutableTargetInfoTest {
35     private val PERSONAL_USER_HANDLE: UserHandle =
36         InstrumentationRegistry.getInstrumentation().getTargetContext().getUser()
37 
38     private val resolvedIntent = Intent("resolved")
39     private val targetIntent = Intent("target")
40     private val referrerFillInIntent = Intent("referrer_fillin")
41     private val resolvedComponentName = ComponentName("resolved", "component")
42     private val chooserTargetComponentName = ComponentName("chooser", "target")
43     private val resolveInfo = ResolverDataProvider.createResolveInfo(1, 0, PERSONAL_USER_HANDLE)
44     private val displayLabel: CharSequence = "Display Label"
45     private val extendedInfo: CharSequence = "Extended Info"
46     private val displayIconHolder: TargetInfo.IconHolder = mock()
47     private val sourceIntent1 = Intent("source1")
48     private val sourceIntent2 = Intent("source2")
49     private val displayTarget1 =
50         DisplayResolveInfo.newDisplayResolveInfo(
51             Intent("display1"),
52             ResolverDataProvider.createResolveInfo(2, 0, PERSONAL_USER_HANDLE),
53             "display1 label",
54             "display1 extended info",
55             Intent("display1_resolved")
56         )
57     private val displayTarget2 =
58         DisplayResolveInfo.newDisplayResolveInfo(
59             Intent("display2"),
60             ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE),
61             "display2 label",
62             "display2 extended info",
63             Intent("display2_resolved")
64         )
65     private val directShareShortcutInfo =
66         createShortcutInfo("shortcutid", ResolverDataProvider.createComponentName(4), 4)
67     private val directShareAppTarget =
68         AppTarget(AppTargetId("apptargetid"), "test.directshare", "target", UserHandle.CURRENT)
69     private val displayResolveInfo =
70         DisplayResolveInfo.newDisplayResolveInfo(
71             Intent("displayresolve"),
72             ResolverDataProvider.createResolveInfo(5, 0, PERSONAL_USER_HANDLE),
73             "displayresolve label",
74             "displayresolve extended info",
75             Intent("display_resolved")
76         )
77     private val hashProvider: ImmutableTargetInfo.TargetHashProvider = mock()
78 
79     @Test
testBasicPropertiesnull80     fun testBasicProperties() { // Fields that are reflected back w/o logic.
81         // TODO: we could consider passing copies of all the values into the builder so that we can
82         // verify that they're not mutated (e.g. no extras added to the intents). For now that
83         // should be obvious from the implementation.
84         val info =
85             ImmutableTargetInfo.newBuilder()
86                 .setResolvedIntent(resolvedIntent)
87                 .setTargetIntent(targetIntent)
88                 .setReferrerFillInIntent(referrerFillInIntent)
89                 .setResolvedComponentName(resolvedComponentName)
90                 .setChooserTargetComponentName(chooserTargetComponentName)
91                 .setResolveInfo(resolveInfo)
92                 .setDisplayLabel(displayLabel)
93                 .setExtendedInfo(extendedInfo)
94                 .setDisplayIconHolder(displayIconHolder)
95                 .setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2))
96                 .setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
97                 .setIsSuspended(true)
98                 .setIsPinned(true)
99                 .setModifiedScore(42.0f)
100                 .setDirectShareShortcutInfo(directShareShortcutInfo)
101                 .setDirectShareAppTarget(directShareAppTarget)
102                 .setDisplayResolveInfo(displayResolveInfo)
103                 .setHashProvider(hashProvider)
104                 .build()
105 
106         assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
107         assertThat(info.targetIntent).isEqualTo(targetIntent)
108         assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
109         assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
110         assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
111         assertThat(info.resolveInfo).isEqualTo(resolveInfo)
112         assertThat(info.displayLabel).isEqualTo(displayLabel)
113         assertThat(info.extendedInfo).isEqualTo(extendedInfo)
114         assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
115         assertThat(info.allSourceIntents)
116             .containsExactly(resolvedIntent, sourceIntent1, sourceIntent2)
117         assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
118         assertThat(info.isSuspended).isTrue()
119         assertThat(info.isPinned).isTrue()
120         assertThat(info.modifiedScore).isEqualTo(42.0f)
121         assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
122         assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
123         assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
124         assertThat(info.isEmptyTargetInfo).isFalse()
125         assertThat(info.isPlaceHolderTargetInfo).isFalse()
126         assertThat(info.isNotSelectableTargetInfo).isFalse()
127         assertThat(info.isSelectableTargetInfo).isFalse()
128         assertThat(info.isChooserTargetInfo).isFalse()
129         assertThat(info.isMultiDisplayResolveInfo).isFalse()
130         assertThat(info.isDisplayResolveInfo).isFalse()
131         assertThat(info.hashProvider).isEqualTo(hashProvider)
132     }
133 
134     @Test
testToBuilderPreservesBasicPropertiesnull135     fun testToBuilderPreservesBasicProperties() {
136         // Note this is set up exactly as in `testBasicProperties`, but the assertions will be made
137         // against a *copy* of the object instead.
138         val infoToCopyFrom =
139             ImmutableTargetInfo.newBuilder()
140                 .setResolvedIntent(resolvedIntent)
141                 .setTargetIntent(targetIntent)
142                 .setReferrerFillInIntent(referrerFillInIntent)
143                 .setResolvedComponentName(resolvedComponentName)
144                 .setChooserTargetComponentName(chooserTargetComponentName)
145                 .setResolveInfo(resolveInfo)
146                 .setDisplayLabel(displayLabel)
147                 .setExtendedInfo(extendedInfo)
148                 .setDisplayIconHolder(displayIconHolder)
149                 .setAlternateSourceIntents(listOf(sourceIntent1, sourceIntent2))
150                 .setAllDisplayTargets(listOf(displayTarget1, displayTarget2))
151                 .setIsSuspended(true)
152                 .setIsPinned(true)
153                 .setModifiedScore(42.0f)
154                 .setDirectShareShortcutInfo(directShareShortcutInfo)
155                 .setDirectShareAppTarget(directShareAppTarget)
156                 .setDisplayResolveInfo(displayResolveInfo)
157                 .setHashProvider(hashProvider)
158                 .build()
159 
160         val info = infoToCopyFrom.toBuilder().build()
161 
162         assertThat(info.resolvedIntent).isEqualTo(resolvedIntent)
163         assertThat(info.targetIntent).isEqualTo(targetIntent)
164         assertThat(info.referrerFillInIntent).isEqualTo(referrerFillInIntent)
165         assertThat(info.resolvedComponentName).isEqualTo(resolvedComponentName)
166         assertThat(info.chooserTargetComponentName).isEqualTo(chooserTargetComponentName)
167         assertThat(info.resolveInfo).isEqualTo(resolveInfo)
168         assertThat(info.displayLabel).isEqualTo(displayLabel)
169         assertThat(info.extendedInfo).isEqualTo(extendedInfo)
170         assertThat(info.displayIconHolder).isEqualTo(displayIconHolder)
171         assertThat(info.allSourceIntents)
172             .containsExactly(resolvedIntent, sourceIntent1, sourceIntent2)
173         assertThat(info.allDisplayTargets).containsExactly(displayTarget1, displayTarget2)
174         assertThat(info.isSuspended).isTrue()
175         assertThat(info.isPinned).isTrue()
176         assertThat(info.modifiedScore).isEqualTo(42.0f)
177         assertThat(info.directShareShortcutInfo).isEqualTo(directShareShortcutInfo)
178         assertThat(info.directShareAppTarget).isEqualTo(directShareAppTarget)
179         assertThat(info.displayResolveInfo).isEqualTo(displayResolveInfo)
180         assertThat(info.isEmptyTargetInfo).isFalse()
181         assertThat(info.isPlaceHolderTargetInfo).isFalse()
182         assertThat(info.isNotSelectableTargetInfo).isFalse()
183         assertThat(info.isSelectableTargetInfo).isFalse()
184         assertThat(info.isChooserTargetInfo).isFalse()
185         assertThat(info.isMultiDisplayResolveInfo).isFalse()
186         assertThat(info.isDisplayResolveInfo).isFalse()
187         assertThat(info.hashProvider).isEqualTo(hashProvider)
188     }
189 
190     @Test
testBaseIntentToSend_defaultsToResolvedIntentnull191     fun testBaseIntentToSend_defaultsToResolvedIntent() {
192         val info = ImmutableTargetInfo.newBuilder().setResolvedIntent(resolvedIntent).build()
193         assertThat(info.baseIntentToSend.filterEquals(resolvedIntent)).isTrue()
194     }
195 
196     @Test
testBaseIntentToSend_fillsInFromReferrerIntentnull197     fun testBaseIntentToSend_fillsInFromReferrerIntent() {
198         val originalIntent = Intent()
199         originalIntent.setPackage("original")
200 
201         val referrerFillInIntent = Intent("REFERRER_FILL_IN")
202         referrerFillInIntent.setPackage("referrer")
203 
204         val info =
205             ImmutableTargetInfo.newBuilder()
206                 .setResolvedIntent(originalIntent)
207                 .setReferrerFillInIntent(referrerFillInIntent)
208                 .build()
209 
210         assertThat(info.baseIntentToSend.getPackage()).isEqualTo("original") // Only fill if empty.
211         assertThat(info.baseIntentToSend.action).isEqualTo("REFERRER_FILL_IN")
212     }
213 
214     @Test
testBaseIntentToSend_fillsInFromRefinementIntentnull215     fun testBaseIntentToSend_fillsInFromRefinementIntent() {
216         val originalIntent = Intent()
217         originalIntent.putExtra("ORIGINAL", true)
218 
219         val refinementIntent = Intent()
220         refinementIntent.putExtra("REFINEMENT", true)
221 
222         val originalInfo =
223             ImmutableTargetInfo.newBuilder().setResolvedIntent(originalIntent).build()
224         val info = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinementIntent))
225 
226         assertThat(info.baseIntentToSend?.getBooleanExtra("ORIGINAL", false)).isTrue()
227         assertThat(info.baseIntentToSend?.getBooleanExtra("REFINEMENT", false)).isTrue()
228     }
229 
230     @Test
testBaseIntentToSend_twoFillInSourcesFavorsRefinementRequestnull231     fun testBaseIntentToSend_twoFillInSourcesFavorsRefinementRequest() {
232         val originalIntent = Intent("REFINE_ME")
233         originalIntent.setPackage("original")
234 
235         val referrerFillInIntent = Intent("REFERRER_FILL_IN")
236         referrerFillInIntent.setPackage("referrer_pkg")
237         referrerFillInIntent.setType("test/referrer")
238 
239         val infoWithReferrerFillIn =
240             ImmutableTargetInfo.newBuilder()
241                 .setResolvedIntent(originalIntent)
242                 .setReferrerFillInIntent(referrerFillInIntent)
243                 .build()
244 
245         val refinementIntent = Intent("REFINE_ME")
246         refinementIntent.setPackage("original") // Has to match for refinement.
247 
248         val info =
249             checkNotNull(infoWithReferrerFillIn.tryToCloneWithAppliedRefinement(refinementIntent))
250 
251         assertThat(info.baseIntentToSend?.getPackage()).isEqualTo("original") // Set all along.
252         assertThat(info.baseIntentToSend?.action).isEqualTo("REFINE_ME") // Refinement wins.
253         assertThat(info.baseIntentToSend?.type).isEqualTo("test/referrer") // Left for referrer.
254     }
255 
256     @Test
testBaseIntentToSend_doubleRefinementPreservesReferrerFillInButNotOriginalRefinementnull257     fun testBaseIntentToSend_doubleRefinementPreservesReferrerFillInButNotOriginalRefinement() {
258         val originalIntent = Intent("REFINE_ME")
259         val referrerFillInIntent = Intent("REFERRER_FILL_IN")
260         referrerFillInIntent.putExtra("TEST", "REFERRER")
261         val refinementIntent1 = Intent("REFINE_ME")
262         refinementIntent1.putExtra("TEST1", "1")
263         val refinementIntent2 = Intent("REFINE_ME")
264         refinementIntent2.putExtra("TEST2", "2")
265 
266         val originalInfo =
267             ImmutableTargetInfo.newBuilder()
268                 .setResolvedIntent(originalIntent)
269                 .setReferrerFillInIntent(referrerFillInIntent)
270                 .build()
271 
272         val refined1 = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinementIntent1))
273         // Cloned clone.
274         val refined2 = checkNotNull(refined1.tryToCloneWithAppliedRefinement(refinementIntent2))
275 
276         // Both clones get the same values filled in from the referrer intent.
277         assertThat(refined1.baseIntentToSend?.getStringExtra("TEST")).isEqualTo("REFERRER")
278         assertThat(refined2.baseIntentToSend?.getStringExtra("TEST")).isEqualTo("REFERRER")
279         // Each clone has the respective value that was set in their own refinement request.
280         assertThat(refined1.baseIntentToSend?.getStringExtra("TEST1")).isEqualTo("1")
281         assertThat(refined2.baseIntentToSend?.getStringExtra("TEST2")).isEqualTo("2")
282         // The clones don't have the data from each other's refinements, even though the intent
283         // field is empty (thus able to be populated by filling-in).
284         assertThat(refined1.baseIntentToSend?.getStringExtra("TEST2")).isNull()
285         assertThat(refined2.baseIntentToSend?.getStringExtra("TEST1")).isNull()
286     }
287 
288     @Test
testBaseIntentToSend_refinementToAlternateSourceIntentnull289     fun testBaseIntentToSend_refinementToAlternateSourceIntent() {
290         val originalIntent = Intent("DONT_REFINE_ME")
291         originalIntent.putExtra("originalIntent", true)
292         val mismatchedAlternate = Intent("DOESNT_MATCH")
293         mismatchedAlternate.putExtra("mismatchedAlternate", true)
294         val targetAlternate = Intent("REFINE_ME")
295         targetAlternate.putExtra("targetAlternate", true)
296         val extraMatch = Intent("REFINE_ME")
297         extraMatch.putExtra("extraMatch", true)
298 
299         val originalInfo =
300             ImmutableTargetInfo.newBuilder()
301                 .setResolvedIntent(originalIntent)
302                 .setAllSourceIntents(
303                     listOf(originalIntent, mismatchedAlternate, targetAlternate, extraMatch)
304                 )
305                 .build()
306 
307         val refinement = Intent("REFINE_ME") // First match is `targetAlternate`
308         refinement.putExtra("refinement", true)
309 
310         val refinedResult = checkNotNull(originalInfo.tryToCloneWithAppliedRefinement(refinement))
311         assertThat(refinedResult.baseIntentToSend?.getBooleanExtra("refinement", false)).isTrue()
312         assertThat(refinedResult.baseIntentToSend?.getBooleanExtra("targetAlternate", false))
313             .isTrue()
314         // None of the other source intents got merged in (not even the later one that matched):
315         assertThat(refinedResult.baseIntentToSend?.getBooleanExtra("originalIntent", false))
316             .isFalse()
317         assertThat(refinedResult.baseIntentToSend?.getBooleanExtra("mismatchedAlternate", false))
318             .isFalse()
319         assertThat(refinedResult.baseIntentToSend?.getBooleanExtra("extraMatch", false)).isFalse()
320     }
321 
322     @Test
testBaseIntentToSend_noSourceIntentMatchingProposedRefinementnull323     fun testBaseIntentToSend_noSourceIntentMatchingProposedRefinement() {
324         val originalIntent = Intent("DONT_REFINE_ME")
325         originalIntent.putExtra("originalIntent", true)
326         val mismatchedAlternate = Intent("DOESNT_MATCH")
327         mismatchedAlternate.putExtra("mismatchedAlternate", true)
328 
329         val originalInfo =
330             ImmutableTargetInfo.newBuilder()
331                 .setResolvedIntent(originalIntent)
332                 .setAllSourceIntents(listOf(originalIntent, mismatchedAlternate))
333                 .build()
334 
335         val refinement = Intent("PROPOSED_REFINEMENT")
336         assertThat(originalInfo.tryToCloneWithAppliedRefinement(refinement)).isNull()
337     }
338 
339     @Test
testLegacySubclassRelationships_emptynull340     fun testLegacySubclassRelationships_empty() {
341         val info =
342             ImmutableTargetInfo.newBuilder()
343                 .setLegacyType(ImmutableTargetInfo.LegacyTargetType.EMPTY_TARGET_INFO)
344                 .build()
345 
346         assertThat(info.isEmptyTargetInfo).isTrue()
347         assertThat(info.isPlaceHolderTargetInfo).isFalse()
348         assertThat(info.isNotSelectableTargetInfo).isTrue()
349         assertThat(info.isSelectableTargetInfo).isFalse()
350         assertThat(info.isChooserTargetInfo).isTrue()
351         assertThat(info.isMultiDisplayResolveInfo).isFalse()
352         assertThat(info.isDisplayResolveInfo).isFalse()
353     }
354 
355     @Test
testLegacySubclassRelationships_placeholdernull356     fun testLegacySubclassRelationships_placeholder() {
357         val info =
358             ImmutableTargetInfo.newBuilder()
359                 .setLegacyType(ImmutableTargetInfo.LegacyTargetType.PLACEHOLDER_TARGET_INFO)
360                 .build()
361 
362         assertThat(info.isEmptyTargetInfo).isFalse()
363         assertThat(info.isPlaceHolderTargetInfo).isTrue()
364         assertThat(info.isNotSelectableTargetInfo).isTrue()
365         assertThat(info.isSelectableTargetInfo).isFalse()
366         assertThat(info.isChooserTargetInfo).isTrue()
367         assertThat(info.isMultiDisplayResolveInfo).isFalse()
368         assertThat(info.isDisplayResolveInfo).isFalse()
369     }
370 
371     @Test
testLegacySubclassRelationships_selectablenull372     fun testLegacySubclassRelationships_selectable() {
373         val info =
374             ImmutableTargetInfo.newBuilder()
375                 .setLegacyType(ImmutableTargetInfo.LegacyTargetType.SELECTABLE_TARGET_INFO)
376                 .build()
377 
378         assertThat(info.isEmptyTargetInfo).isFalse()
379         assertThat(info.isPlaceHolderTargetInfo).isFalse()
380         assertThat(info.isNotSelectableTargetInfo).isFalse()
381         assertThat(info.isSelectableTargetInfo).isTrue()
382         assertThat(info.isChooserTargetInfo).isTrue()
383         assertThat(info.isMultiDisplayResolveInfo).isFalse()
384         assertThat(info.isDisplayResolveInfo).isFalse()
385     }
386 
387     @Test
testLegacySubclassRelationships_displayResolveInfonull388     fun testLegacySubclassRelationships_displayResolveInfo() {
389         val info =
390             ImmutableTargetInfo.newBuilder()
391                 .setLegacyType(ImmutableTargetInfo.LegacyTargetType.DISPLAY_RESOLVE_INFO)
392                 .build()
393 
394         assertThat(info.isEmptyTargetInfo).isFalse()
395         assertThat(info.isPlaceHolderTargetInfo).isFalse()
396         assertThat(info.isNotSelectableTargetInfo).isFalse()
397         assertThat(info.isSelectableTargetInfo).isFalse()
398         assertThat(info.isChooserTargetInfo).isFalse()
399         assertThat(info.isMultiDisplayResolveInfo).isFalse()
400         assertThat(info.isDisplayResolveInfo).isTrue()
401     }
402 
403     @Test
testLegacySubclassRelationships_multiDisplayResolveInfonull404     fun testLegacySubclassRelationships_multiDisplayResolveInfo() {
405         val info =
406             ImmutableTargetInfo.newBuilder()
407                 .setLegacyType(ImmutableTargetInfo.LegacyTargetType.MULTI_DISPLAY_RESOLVE_INFO)
408                 .build()
409 
410         assertThat(info.isEmptyTargetInfo).isFalse()
411         assertThat(info.isPlaceHolderTargetInfo).isFalse()
412         assertThat(info.isNotSelectableTargetInfo).isFalse()
413         assertThat(info.isSelectableTargetInfo).isFalse()
414         assertThat(info.isChooserTargetInfo).isFalse()
415         assertThat(info.isMultiDisplayResolveInfo).isTrue()
416         assertThat(info.isDisplayResolveInfo).isTrue()
417     }
418 
419     @Test
testActivityStarter_correctNumberOfInvocations_startAsCallernull420     fun testActivityStarter_correctNumberOfInvocations_startAsCaller() {
421         val activityStarter =
422             object : TestActivityStarter() {
423                 override fun startAsUser(
424                     target: TargetInfo,
425                     activity: Activity,
426                     options: Bundle,
427                     user: UserHandle
428                 ): Boolean {
429                     throw RuntimeException("Wrong API used: startAsUser")
430                 }
431             }
432 
433         val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
434         val activity: ResolverActivity = mock()
435         val options = Bundle()
436         options.putInt("TEST_KEY", 1)
437 
438         info.startAsCaller(activity, options, 42)
439 
440         assertThat(activityStarter.totalInvocations).isEqualTo(1)
441         assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
442         assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
443         assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
444         assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
445         assertThat(activityStarter.lastInvocationAsCaller).isTrue()
446     }
447 
448     @Test
testActivityStarter_correctNumberOfInvocations_startAsUsernull449     fun testActivityStarter_correctNumberOfInvocations_startAsUser() {
450         val activityStarter =
451             object : TestActivityStarter() {
452                 override fun startAsCaller(
453                     target: TargetInfo,
454                     activity: Activity,
455                     options: Bundle,
456                     userId: Int
457                 ): Boolean {
458                     throw RuntimeException("Wrong API used: startAsCaller")
459                 }
460             }
461 
462         val info = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
463         val activity: Activity = mock()
464         val options = Bundle()
465         options.putInt("TEST_KEY", 1)
466 
467         info.startAsUser(activity, options, UserHandle.of(42))
468 
469         assertThat(activityStarter.totalInvocations).isEqualTo(1)
470         assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info)
471         assertThat(activityStarter.lastInvocationActivity).isEqualTo(activity)
472         assertThat(activityStarter.lastInvocationOptions).isEqualTo(options)
473         assertThat(activityStarter.lastInvocationUserId).isEqualTo(42)
474         assertThat(activityStarter.lastInvocationAsCaller).isFalse()
475     }
476 
477     @Test
testActivityStarter_invokedWithRespectiveTargetInfoAfterCopynull478     fun testActivityStarter_invokedWithRespectiveTargetInfoAfterCopy() {
479         val activityStarter = TestActivityStarter()
480         val info1 = ImmutableTargetInfo.newBuilder().setActivityStarter(activityStarter).build()
481         val info2 = info1.toBuilder().build()
482 
483         info1.startAsCaller(mock(), Bundle(), 42)
484         assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info1)
485         info2.startAsCaller(mock(), Bundle(), 42)
486         assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
487         info2.startAsUser(mock(), Bundle(), UserHandle.of(42))
488         assertThat(activityStarter.lastInvocationTargetInfo).isEqualTo(info2)
489 
490         assertThat(activityStarter.totalInvocations).isEqualTo(3) // Instance is still shared.
491     }
492 }
493 
494 private open class TestActivityStarter : ImmutableTargetInfo.TargetActivityStarter {
495     var totalInvocations = 0
496     var lastInvocationTargetInfo: TargetInfo? = null
497     var lastInvocationActivity: Activity? = null
498     var lastInvocationOptions: Bundle? = null
499     var lastInvocationUserId: Int? = null
500     var lastInvocationAsCaller = false
501 
startAsCallernull502     override fun startAsCaller(
503         target: TargetInfo,
504         activity: Activity,
505         options: Bundle,
506         userId: Int
507     ): Boolean {
508         ++totalInvocations
509         lastInvocationTargetInfo = target
510         lastInvocationActivity = activity
511         lastInvocationOptions = options
512         lastInvocationUserId = userId
513         lastInvocationAsCaller = true
514         return true
515     }
516 
startAsUsernull517     override fun startAsUser(
518         target: TargetInfo,
519         activity: Activity,
520         options: Bundle,
521         user: UserHandle
522     ): Boolean {
523         ++totalInvocations
524         lastInvocationTargetInfo = target
525         lastInvocationActivity = activity
526         lastInvocationOptions = options
527         lastInvocationUserId = user.identifier
528         lastInvocationAsCaller = false
529         return true
530     }
531 }
532