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  *
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
18 
19 import android.app.Activity
20 import android.app.Application
21 import android.content.Intent
22 import android.content.IntentSender
23 import android.os.Bundle
24 import android.os.Handler
25 import android.os.Looper
26 import android.os.Message
27 import android.os.ResultReceiver
28 import androidx.lifecycle.Observer
29 import androidx.test.annotation.UiThreadTest
30 import androidx.test.ext.junit.runners.AndroidJUnit4
31 import com.android.intentresolver.ChooserRefinementManager.RefinementCompletion
32 import com.android.intentresolver.ChooserRefinementManager.RefinementType
33 import com.android.intentresolver.chooser.ImmutableTargetInfo
34 import com.google.common.truth.Truth.assertThat
35 import java.util.concurrent.CountDownLatch
36 import java.util.concurrent.TimeUnit
37 import org.junit.Before
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.mockito.kotlin.any
41 import org.mockito.kotlin.argumentCaptor
42 import org.mockito.kotlin.eq
43 import org.mockito.kotlin.mock
44 import org.mockito.kotlin.verify
45 
46 @RunWith(AndroidJUnit4::class)
47 @UiThreadTest
48 class ChooserRefinementManagerTest {
49     private val refinementManager = ChooserRefinementManager()
50     private val intentSender = mock<IntentSender>()
51     private val application = mock<Application>()
52     private val exampleSourceIntents =
53         listOf(Intent(Intent.ACTION_VIEW), Intent(Intent.ACTION_EDIT))
54     private val exampleTargetInfo =
55         ImmutableTargetInfo.newBuilder().setAllSourceIntents(exampleSourceIntents).build()
56 
57     private val completionObserver =
58         object : Observer<RefinementCompletion> {
59             val failureCountDown = CountDownLatch(1)
60             val successCountDown = CountDownLatch(1)
61             var latestRefinedIntent: Intent? = null
62 
onChangednull63             override fun onChanged(completion: RefinementCompletion) {
64                 if (completion.consume()) {
65                     val refinedIntent = completion.refinedIntent
66                     if (refinedIntent == null) {
67                         failureCountDown.countDown()
68                     } else {
69                         latestRefinedIntent = refinedIntent
70                         successCountDown.countDown()
71                     }
72                 }
73             }
74         }
75 
76     /** Synchronously executes post() calls. */
77     private class FakeHandler(looper: Looper) : Handler(looper) {
sendMessageAtTimenull78         override fun sendMessageAtTime(msg: Message, uptimeMillis: Long): Boolean {
79             dispatchMessage(msg)
80             return true
81         }
82     }
83 
84     @Before
setupnull85     fun setup() {
86         refinementManager.refinementCompletion.observeForever(completionObserver)
87     }
88 
89     @Test
testTypicalRefinementFlownull90     fun testTypicalRefinementFlow() {
91         assertThat(
92                 refinementManager.maybeHandleSelection(
93                     exampleTargetInfo,
94                     intentSender,
95                     application,
96                     FakeHandler(checkNotNull(Looper.myLooper()))
97                 )
98             )
99             .isTrue()
100 
101         val intentCaptor = argumentCaptor<Intent>()
102         verify(intentSender).sendIntent(any(), eq(0), intentCaptor.capture(), eq(null), eq(null))
103 
104         val intent = intentCaptor.firstValue
105         assertThat(intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
106             .isEqualTo(exampleSourceIntents[0])
107 
108         val alternates =
109             intent.getParcelableArrayExtra(Intent.EXTRA_ALTERNATE_INTENTS, Intent::class.java)
110         assertThat(alternates?.size).isEqualTo(1)
111         assertThat(alternates?.get(0)).isEqualTo(exampleSourceIntents[1])
112 
113         // Complete the refinement
114         val receiver =
115             intent.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver::class.java)
116         val bundle = Bundle().apply { putParcelable(Intent.EXTRA_INTENT, exampleSourceIntents[0]) }
117         receiver?.send(Activity.RESULT_OK, bundle)
118 
119         assertThat(completionObserver.successCountDown.await(1000, TimeUnit.MILLISECONDS)).isTrue()
120         assertThat(completionObserver.latestRefinedIntent?.action).isEqualTo(Intent.ACTION_VIEW)
121     }
122 
123     @Test
testRefinementCancellednull124     fun testRefinementCancelled() {
125         assertThat(
126                 refinementManager.maybeHandleSelection(
127                     exampleTargetInfo,
128                     intentSender,
129                     application,
130                     FakeHandler(checkNotNull(Looper.myLooper()))
131                 )
132             )
133             .isTrue()
134 
135         val intentCaptor = argumentCaptor<Intent>()
136         verify(intentSender).sendIntent(any(), eq(0), intentCaptor.capture(), eq(null), eq(null))
137 
138         val intent = intentCaptor.firstValue
139 
140         // Complete the refinement
141         val receiver =
142             intent?.getParcelableExtra(Intent.EXTRA_RESULT_RECEIVER, ResultReceiver::class.java)
143         val bundle = Bundle().apply { putParcelable(Intent.EXTRA_INTENT, exampleSourceIntents[0]) }
144         receiver?.send(Activity.RESULT_CANCELED, bundle)
145 
146         assertThat(completionObserver.failureCountDown.await(1000, TimeUnit.MILLISECONDS)).isTrue()
147     }
148 
149     @Test
testMaybeHandleSelection_noSourceIntentsnull150     fun testMaybeHandleSelection_noSourceIntents() {
151         assertThat(
152                 refinementManager.maybeHandleSelection(
153                     ImmutableTargetInfo.newBuilder().build(),
154                     intentSender,
155                     application,
156                     FakeHandler(checkNotNull(Looper.myLooper()))
157                 )
158             )
159             .isFalse()
160     }
161 
162     @Test
testMaybeHandleSelection_suspendednull163     fun testMaybeHandleSelection_suspended() {
164         val targetInfo =
165             ImmutableTargetInfo.newBuilder()
166                 .setAllSourceIntents(exampleSourceIntents)
167                 .setIsSuspended(true)
168                 .build()
169 
170         assertThat(
171                 refinementManager.maybeHandleSelection(
172                     targetInfo,
173                     intentSender,
174                     application,
175                     FakeHandler(checkNotNull(Looper.myLooper()))
176                 )
177             )
178             .isFalse()
179     }
180 
181     @Test
testMaybeHandleSelection_noIntentSendernull182     fun testMaybeHandleSelection_noIntentSender() {
183         assertThat(
184                 refinementManager.maybeHandleSelection(
185                     exampleTargetInfo,
186                     /* IntentSender */ null,
187                     application,
188                     FakeHandler(checkNotNull(Looper.myLooper()))
189                 )
190             )
191             .isFalse()
192     }
193 
194     @Test
testConfigurationChangeDuringRefinementnull195     fun testConfigurationChangeDuringRefinement() {
196         assertThat(
197                 refinementManager.maybeHandleSelection(
198                     exampleTargetInfo,
199                     intentSender,
200                     application,
201                     FakeHandler(checkNotNull(Looper.myLooper()))
202                 )
203             )
204             .isTrue()
205 
206         refinementManager.onActivityStop(/* config changing = */ true)
207         refinementManager.onActivityResume()
208 
209         assertThat(completionObserver.failureCountDown.count).isEqualTo(1)
210     }
211 
212     @Test
testResumeDuringRefinementnull213     fun testResumeDuringRefinement() {
214         assertThat(
215                 refinementManager.maybeHandleSelection(
216                     exampleTargetInfo,
217                     intentSender,
218                     application,
219                     FakeHandler(checkNotNull(Looper.myLooper())!!)
220                 )
221             )
222             .isTrue()
223 
224         refinementManager.onActivityStop(/* config changing = */ false)
225         // Resume during refinement but not during a config change, so finish the activity.
226         refinementManager.onActivityResume()
227 
228         // Call should be synchronous, don't need to await for this one.
229         assertThat(completionObserver.failureCountDown.count).isEqualTo(0)
230     }
231 
232     @Test
testRefinementCompletionnull233     fun testRefinementCompletion() {
234         val refinementCompletion =
235             RefinementCompletion(RefinementType.TARGET_INFO, exampleTargetInfo, null)
236         assertThat(refinementCompletion.originalTargetInfo).isEqualTo(exampleTargetInfo)
237         assertThat(refinementCompletion.consume()).isTrue()
238         assertThat(refinementCompletion.originalTargetInfo).isEqualTo(exampleTargetInfo)
239 
240         // can only consume once.
241         assertThat(refinementCompletion.consume()).isFalse()
242     }
243 }
244