• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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  *      https://www.apache.org/licenses/LICENSE-2.0
9  *
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.interactive.domain.interactor
18 
19 import android.content.ComponentName
20 import android.content.Intent
21 import android.content.Intent.ACTION_QUICK_VIEW
22 import android.content.Intent.ACTION_RUN
23 import android.content.Intent.ACTION_SEND
24 import android.content.Intent.ACTION_VIEW
25 import android.content.Intent.EXTRA_ALTERNATE_INTENTS
26 import android.content.Intent.EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER
27 import android.content.Intent.EXTRA_CHOOSER_RESULT_INTENT_SENDER
28 import android.content.Intent.EXTRA_CHOOSER_TARGETS
29 import android.content.Intent.EXTRA_EXCLUDE_COMPONENTS
30 import android.content.Intent.EXTRA_INITIAL_INTENTS
31 import android.content.Intent.EXTRA_REPLACEMENT_EXTRAS
32 import android.content.IntentSender
33 import android.os.Binder
34 import android.os.IBinder
35 import android.os.IBinder.DeathRecipient
36 import android.os.IInterface
37 import android.os.Parcel
38 import android.os.ResultReceiver
39 import android.os.ShellCallback
40 import android.service.chooser.ChooserTarget
41 import androidx.core.os.bundleOf
42 import androidx.lifecycle.SavedStateHandle
43 import com.android.intentresolver.IChooserController
44 import com.android.intentresolver.IChooserInteractiveSessionCallback
45 import com.android.intentresolver.contentpreview.payloadtoggle.data.repository.PendingSelectionCallbackRepository
46 import com.android.intentresolver.data.model.ChooserRequest
47 import com.android.intentresolver.data.repository.ActivityModelRepository
48 import com.android.intentresolver.data.repository.ChooserRequestRepository
49 import com.android.intentresolver.interactive.data.repository.InteractiveSessionCallbackRepository
50 import com.android.intentresolver.shared.model.ActivityModel
51 import com.google.common.truth.Correspondence
52 import com.google.common.truth.Truth.assertThat
53 import java.io.FileDescriptor
54 import kotlinx.coroutines.launch
55 import kotlinx.coroutines.test.runTest
56 import org.junit.Test
57 
58 class InteractiveSessionInteractorTest {
59     private val activityModelRepo =
<lambda>null60         ActivityModelRepository().apply {
61             initialize {
62                 ActivityModel(
63                     intent = Intent(),
64                     launchedFromUid = 12345,
65                     launchedFromPackage = "org.client.package",
66                     referrer = null,
67                     isTaskRoot = false,
68                 )
69             }
70         }
71     private val interactiveSessionCallback = FakeChooserInteractiveSessionCallback()
72     private val pendingSelectionCallbackRepo = PendingSelectionCallbackRepository()
73     private val savedStateHandle = SavedStateHandle()
74     private val interactiveCallbackRepo = InteractiveSessionCallbackRepository(savedStateHandle)
75 
76     @Test
<lambda>null77     fun testChooserLaunchedInNewTask_sessionClosed() = runTest {
78         val activityModelRepo =
79             ActivityModelRepository().apply {
80                 initialize {
81                     ActivityModel(
82                         intent = Intent(),
83                         launchedFromUid = 12345,
84                         launchedFromPackage = "org.client.package",
85                         referrer = null,
86                         isTaskRoot = true,
87                     )
88                 }
89             }
90         val chooserRequestRepository =
91             ChooserRequestRepository(
92                 initialRequest =
93                     ChooserRequest(
94                         targetIntent = Intent(ACTION_SEND),
95                         interactiveSessionCallback = interactiveSessionCallback,
96                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
97                     ),
98                 initialActions = emptyList(),
99             )
100         val testSubject =
101             InteractiveSessionInteractor(
102                 activityModelRepo = activityModelRepo,
103                 chooserRequestRepository = chooserRequestRepository,
104                 pendingSelectionCallbackRepo,
105                 interactiveCallbackRepo,
106             )
107 
108         testSubject.activate()
109 
110         assertThat(interactiveSessionCallback.registeredIntentUpdaters).containsExactly(null)
111     }
112 
113     @Test
<lambda>null114     fun testDeadBinder_sessionEnd() = runTest {
115         interactiveSessionCallback.isAlive = false
116         val chooserRequestRepository =
117             ChooserRequestRepository(
118                 initialRequest =
119                     ChooserRequest(
120                         targetIntent = Intent(ACTION_SEND),
121                         interactiveSessionCallback = interactiveSessionCallback,
122                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
123                     ),
124                 initialActions = emptyList(),
125             )
126         val testSubject =
127             InteractiveSessionInteractor(
128                 activityModelRepo = activityModelRepo,
129                 chooserRequestRepository = chooserRequestRepository,
130                 pendingSelectionCallbackRepo,
131                 interactiveCallbackRepo,
132             )
133 
134         backgroundScope.launch { testSubject.activate() }
135         this.testScheduler.runCurrent()
136 
137         assertThat(testSubject.isSessionActive.value).isFalse()
138     }
139 
140     @Test
<lambda>null141     fun testBinderDies_sessionEnd() = runTest {
142         val chooserRequestRepository =
143             ChooserRequestRepository(
144                 initialRequest =
145                     ChooserRequest(
146                         targetIntent = Intent(ACTION_SEND),
147                         interactiveSessionCallback = interactiveSessionCallback,
148                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
149                     ),
150                 initialActions = emptyList(),
151             )
152         val testSubject =
153             InteractiveSessionInteractor(
154                 activityModelRepo = activityModelRepo,
155                 chooserRequestRepository = chooserRequestRepository,
156                 pendingSelectionCallbackRepo,
157                 interactiveCallbackRepo,
158             )
159 
160         backgroundScope.launch { testSubject.activate() }
161         this.testScheduler.runCurrent()
162 
163         assertThat(testSubject.isSessionActive.value).isTrue()
164         assertThat(interactiveSessionCallback.linkedDeathRecipients).hasSize(1)
165 
166         interactiveSessionCallback.linkedDeathRecipients[0].binderDied()
167 
168         assertThat(testSubject.isSessionActive.value).isFalse()
169     }
170 
171     @Test
<lambda>null172     fun testScopeCancelled_unsubscribeFromBinder() = runTest {
173         val chooserRequestRepository =
174             ChooserRequestRepository(
175                 initialRequest =
176                     ChooserRequest(
177                         targetIntent = Intent(ACTION_SEND),
178                         interactiveSessionCallback = interactiveSessionCallback,
179                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
180                     ),
181                 initialActions = emptyList(),
182             )
183         val testSubject =
184             InteractiveSessionInteractor(
185                 activityModelRepo = activityModelRepo,
186                 chooserRequestRepository = chooserRequestRepository,
187                 pendingSelectionCallbackRepo,
188                 interactiveCallbackRepo,
189             )
190 
191         val job = backgroundScope.launch { testSubject.activate() }
192         testScheduler.runCurrent()
193 
194         assertThat(interactiveSessionCallback.linkedDeathRecipients).hasSize(1)
195         assertThat(interactiveSessionCallback.unlinkedDeathRecipients).hasSize(0)
196 
197         job.cancel()
198         testScheduler.runCurrent()
199 
200         assertThat(interactiveSessionCallback.unlinkedDeathRecipients).hasSize(1)
201     }
202 
203     @Test
<lambda>null204     fun endSession_intentUpdaterCallbackReset() = runTest {
205         val chooserRequestRepository =
206             ChooserRequestRepository(
207                 initialRequest =
208                     ChooserRequest(
209                         targetIntent = Intent(ACTION_SEND),
210                         interactiveSessionCallback = interactiveSessionCallback,
211                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
212                     ),
213                 initialActions = emptyList(),
214             )
215         val testSubject =
216             InteractiveSessionInteractor(
217                 activityModelRepo = activityModelRepo,
218                 chooserRequestRepository = chooserRequestRepository,
219                 pendingSelectionCallbackRepo,
220                 interactiveCallbackRepo,
221             )
222 
223         backgroundScope.launch { testSubject.activate() }
224         testScheduler.runCurrent()
225 
226         assertThat(interactiveSessionCallback.registeredIntentUpdaters).hasSize(1)
227 
228         testSubject.endSession()
229 
230         assertThat(interactiveSessionCallback.registeredIntentUpdaters).hasSize(2)
231         assertThat(interactiveSessionCallback.registeredIntentUpdaters[1]).isNull()
232     }
233 
234     @Test
<lambda>null235     fun nullChooserIntentReceived_sessionEnds() = runTest {
236         val chooserRequestRepository =
237             ChooserRequestRepository(
238                 initialRequest =
239                     ChooserRequest(
240                         targetIntent = Intent(ACTION_SEND),
241                         interactiveSessionCallback = interactiveSessionCallback,
242                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
243                     ),
244                 initialActions = emptyList(),
245             )
246         val testSubject =
247             InteractiveSessionInteractor(
248                 activityModelRepo = activityModelRepo,
249                 chooserRequestRepository = chooserRequestRepository,
250                 pendingSelectionCallbackRepo,
251                 interactiveCallbackRepo,
252             )
253 
254         backgroundScope.launch { testSubject.activate() }
255         testScheduler.runCurrent()
256 
257         assertThat(interactiveSessionCallback.registeredIntentUpdaters).hasSize(1)
258         interactiveSessionCallback.registeredIntentUpdaters[0]!!.updateIntent(null)
259         testScheduler.runCurrent()
260 
261         assertThat(testSubject.isSessionActive.value).isFalse()
262     }
263 
264     @Test
<lambda>null265     fun invalidChooserIntentReceived_intentIgnored() = runTest {
266         val chooserRequestRepository =
267             ChooserRequestRepository(
268                 initialRequest =
269                     ChooserRequest(
270                         targetIntent = Intent(ACTION_SEND),
271                         interactiveSessionCallback = interactiveSessionCallback,
272                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
273                     ),
274                 initialActions = emptyList(),
275             )
276         val testSubject =
277             InteractiveSessionInteractor(
278                 activityModelRepo = activityModelRepo,
279                 chooserRequestRepository = chooserRequestRepository,
280                 pendingSelectionCallbackRepo,
281                 interactiveCallbackRepo,
282             )
283 
284         backgroundScope.launch { testSubject.activate() }
285         testScheduler.runCurrent()
286 
287         assertThat(interactiveSessionCallback.registeredIntentUpdaters).hasSize(1)
288         interactiveSessionCallback.registeredIntentUpdaters[0]!!.updateIntent(Intent())
289         testScheduler.runCurrent()
290 
291         assertThat(testSubject.isSessionActive.value).isTrue()
292         assertThat(chooserRequestRepository.chooserRequest.value)
293             .isEqualTo(chooserRequestRepository.initialRequest)
294     }
295 
296     @Test
<lambda>null297     fun validChooserIntentReceived_chooserRequestUpdated() = runTest {
298         val chooserRequestRepository =
299             ChooserRequestRepository(
300                 initialRequest =
301                     ChooserRequest(
302                         targetIntent = Intent(ACTION_SEND),
303                         interactiveSessionCallback = interactiveSessionCallback,
304                         launchedFromPackage = activityModelRepo.value.launchedFromPackage,
305                     ),
306                 initialActions = emptyList(),
307             )
308         val testSubject =
309             InteractiveSessionInteractor(
310                 activityModelRepo = activityModelRepo,
311                 chooserRequestRepository = chooserRequestRepository,
312                 pendingSelectionCallbackRepo,
313                 interactiveCallbackRepo,
314             )
315 
316         backgroundScope.launch { testSubject.activate() }
317         testScheduler.runCurrent()
318 
319         assertThat(interactiveSessionCallback.registeredIntentUpdaters).hasSize(1)
320         val newTargetIntent = Intent(ACTION_VIEW).apply { type = "image/png" }
321         val newFilteredComponents = arrayOf(ComponentName.unflattenFromString("com.app/.MainA"))
322         val newCallerTargets =
323             arrayOf(
324                 ChooserTarget(
325                     "A",
326                     null,
327                     0.5f,
328                     ComponentName.unflattenFromString("org.pkg/.Activity"),
329                     null,
330                 )
331             )
332         val newAdditionalIntents = arrayOf(Intent(ACTION_RUN))
333         val newReplacementExtras = bundleOf("ONE" to 1, "TWO" to 2)
334         val newInitialIntents = arrayOf(Intent(ACTION_QUICK_VIEW))
335         val newResultSender = IntentSender(Binder())
336         val newRefinementSender = IntentSender(Binder())
337         interactiveSessionCallback.registeredIntentUpdaters[0]!!.updateIntent(
338             Intent.createChooser(newTargetIntent, "").apply {
339                 putExtra(EXTRA_EXCLUDE_COMPONENTS, newFilteredComponents)
340                 putExtra(EXTRA_CHOOSER_TARGETS, newCallerTargets)
341                 putExtra(EXTRA_ALTERNATE_INTENTS, newAdditionalIntents)
342                 putExtra(EXTRA_REPLACEMENT_EXTRAS, newReplacementExtras)
343                 putExtra(EXTRA_INITIAL_INTENTS, newInitialIntents)
344                 putExtra(EXTRA_CHOOSER_RESULT_INTENT_SENDER, newResultSender)
345                 putExtra(EXTRA_CHOOSER_REFINEMENT_INTENT_SENDER, newRefinementSender)
346             }
347         )
348         testScheduler.runCurrent()
349 
350         assertThat(testSubject.isSessionActive.value).isTrue()
351         val updatedRequest = chooserRequestRepository.chooserRequest.value
352         assertThat(updatedRequest.targetAction).isEqualTo(newTargetIntent.action)
353         assertThat(updatedRequest.targetType).isEqualTo(newTargetIntent.type)
354         assertThat(updatedRequest.filteredComponentNames).containsExactly(newFilteredComponents[0])
355         assertThat(updatedRequest.callerChooserTargets).containsExactly(newCallerTargets[0])
356         assertThat(updatedRequest.additionalTargets)
357             .comparingElementsUsing<Intent, String>(
358                 Correspondence.transforming({ it.action }, "action")
359             )
360             .containsExactly(newAdditionalIntents[0].action)
361         assertThat(updatedRequest.replacementExtras!!.keySet())
362             .containsExactlyElementsIn(newReplacementExtras.keySet())
363         assertThat(updatedRequest.initialIntents)
364             .comparingElementsUsing<Intent, String>(
365                 Correspondence.transforming({ it.action }, "action")
366             )
367             .containsExactly(newInitialIntents[0].action)
368         assertThat(updatedRequest.chosenComponentSender).isEqualTo(newResultSender)
369         assertThat(updatedRequest.refinementIntentSender).isEqualTo(newRefinementSender)
370     }
371 }
372 
373 private class FakeChooserInteractiveSessionCallback :
374     IChooserInteractiveSessionCallback, IBinder, IInterface {
375     var isAlive = true
376     val registeredIntentUpdaters = ArrayList<IChooserController?>()
377     val linkedDeathRecipients = ArrayList<DeathRecipient>()
378     val unlinkedDeathRecipients = ArrayList<DeathRecipient>()
379 
registerChooserControllernull380     override fun registerChooserController(intentUpdater: IChooserController?) {
381         registeredIntentUpdaters.add(intentUpdater)
382     }
383 
onDrawerVerticalOffsetChangednull384     override fun onDrawerVerticalOffsetChanged(offset: Int) {}
385 
asBindernull386     override fun asBinder() = this
387 
388     override fun getInterfaceDescriptor() = ""
389 
390     override fun pingBinder() = true
391 
392     override fun isBinderAlive() = isAlive
393 
394     override fun queryLocalInterface(descriptor: String): IInterface =
395         this@FakeChooserInteractiveSessionCallback
396 
397     override fun dump(fd: FileDescriptor, args: Array<out String>?) = Unit
398 
399     override fun dumpAsync(fd: FileDescriptor, args: Array<out String>?) = Unit
400 
401     override fun shellCommand(
402         `in`: FileDescriptor?,
403         out: FileDescriptor?,
404         err: FileDescriptor?,
405         args: Array<out String>,
406         shellCallback: ShellCallback?,
407         resultReceiver: ResultReceiver,
408     ) = Unit
409 
410     override fun transact(code: Int, data: Parcel, reply: Parcel?, flags: Int) = true
411 
412     override fun linkToDeath(recipient: DeathRecipient, flags: Int) {
413         linkedDeathRecipients.add(recipient)
414     }
415 
unlinkToDeathnull416     override fun unlinkToDeath(recipient: DeathRecipient, flags: Int): Boolean {
417         unlinkedDeathRecipients.add(recipient)
418         return true
419     }
420 }
421