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