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.PendingIntent
21 import android.content.BroadcastReceiver
22 import android.content.Context
23 import android.content.Context.RECEIVER_EXPORTED
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.content.res.Resources
27 import android.graphics.drawable.Icon
28 import android.service.chooser.ChooserAction
29 import androidx.test.ext.junit.runners.AndroidJUnit4
30 import androidx.test.platform.app.InstrumentationRegistry
31 import com.android.intentresolver.logging.EventLog
32 import com.android.intentresolver.ui.ShareResultSender
33 import com.android.intentresolver.ui.model.ShareAction
34 import com.google.common.truth.Truth.assertThat
35 import java.util.Optional
36 import java.util.concurrent.CountDownLatch
37 import java.util.concurrent.TimeUnit
38 import java.util.function.Consumer
39 import org.junit.After
40 import org.junit.Assert.assertEquals
41 import org.junit.Assert.assertTrue
42 import org.junit.Before
43 import org.junit.Test
44 import org.junit.runner.RunWith
45 import org.mockito.kotlin.eq
46 import org.mockito.kotlin.mock
47 import org.mockito.kotlin.verify
48 
49 @RunWith(AndroidJUnit4::class)
50 class ChooserActionFactoryTest {
51     private val context = InstrumentationRegistry.getInstrumentation().context
52 
53     private val logger = mock<EventLog>()
54     private val actionLabel = "Action label"
55     private val testAction = "com.android.intentresolver.testaction"
56     private val countdown = CountDownLatch(1)
57     private val testReceiver: BroadcastReceiver =
58         object : BroadcastReceiver() {
onReceivenull59             override fun onReceive(context: Context, intent: Intent) {
60                 // Just doing at most a single countdown per test.
61                 countdown.countDown()
62             }
63         }
64     private val resultConsumer =
65         object : Consumer<Int> {
66             var latestReturn = Integer.MIN_VALUE
67 
acceptnull68             override fun accept(resultCode: Int) {
69                 latestReturn = resultCode
70             }
71         }
72     private val featureFlags =
<lambda>null73         FakeFeatureFlagsImpl().apply { setFlag(Flags.FLAG_FIX_PARTIAL_IMAGE_EDIT_TRANSITION, true) }
74 
75     @Before
setupnull76     fun setup() {
77         context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED)
78     }
79 
80     @After
teardownnull81     fun teardown() {
82         context.unregisterReceiver(testReceiver)
83     }
84 
85     @Test
testCreateCustomActionsnull86     fun testCreateCustomActions() {
87         val factory = createFactory()
88 
89         val customActions = factory.createCustomActions()
90 
91         assertThat(customActions.size).isEqualTo(1)
92         assertThat(customActions[0].label).isEqualTo(actionLabel)
93 
94         // click it
95         customActions[0].onClicked.run()
96 
97         verify(logger).logCustomActionSelected(eq(0))
98         assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
99         // Verify the pending intent has been called
100         assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS))
101     }
102 
103     @Test
nonSendAction_noCopyRunnablenull104     fun nonSendAction_noCopyRunnable() {
105         val targetIntent =
106             Intent(Intent.ACTION_SEND_MULTIPLE).apply {
107                 putExtra(Intent.EXTRA_TEXT, "Text to show")
108             }
109 
110         val testSubject =
111             ChooserActionFactory(
112                 /* context = */ context,
113                 /* targetIntent = */ targetIntent,
114                 /* referrerPackageName = */ null,
115                 /* chooserActions = */ emptyList(),
116                 /* imageEditor = */ Optional.empty(),
117                 /* log = */ logger,
118                 /* onUpdateSharedTextIsExcluded = */ {},
119                 /* firstVisibleImageQuery = */ { null },
120                 /* activityStarter = */ mock(),
121                 /* shareResultSender = */ null,
122                 /* finishCallback = */ {},
123                 /* clipboardManager = */ mock(),
124                 /* featureFlags = */ featureFlags,
125             )
126         assertThat(testSubject.copyButtonRunnable).isNull()
127     }
128 
129     @Test
sendActionNoText_noCopyRunnablenull130     fun sendActionNoText_noCopyRunnable() {
131         val targetIntent = Intent(Intent.ACTION_SEND)
132         val testSubject =
133             ChooserActionFactory(
134                 /* context = */ context,
135                 /* targetIntent = */ targetIntent,
136                 /* referrerPackageName = */ "com.example",
137                 /* chooserActions = */ emptyList(),
138                 /* imageEditor = */ Optional.empty(),
139                 /* log = */ logger,
140                 /* onUpdateSharedTextIsExcluded = */ {},
141                 /* firstVisibleImageQuery = */ { null },
142                 /* activityStarter = */ mock(),
143                 /* shareResultSender = */ null,
144                 /* finishCallback = */ {},
145                 /* clipboardManager = */ mock(),
146                 /* featureFlags = */ featureFlags,
147             )
148         assertThat(testSubject.copyButtonRunnable).isNull()
149     }
150 
151     @Test
sendActionWithTextCopyRunnablenull152     fun sendActionWithTextCopyRunnable() {
153         val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") }
154         val resultSender = mock<ShareResultSender>()
155         val testSubject =
156             ChooserActionFactory(
157                 /* context = */ context,
158                 /* targetIntent = */ targetIntent,
159                 /* referrerPackageName = */ "com.example",
160                 /* chooserActions = */ emptyList(),
161                 /* imageEditor = */ Optional.empty(),
162                 /* log = */ logger,
163                 /* onUpdateSharedTextIsExcluded = */ {},
164                 /* firstVisibleImageQuery = */ { null },
165                 /* activityStarter = */ mock(),
166                 /* shareResultSender = */ resultSender,
167                 /* finishCallback = */ {},
168                 /* clipboardManager = */ mock(),
169                 /* featureFlags = */ featureFlags,
170             )
171         assertThat(testSubject.copyButtonRunnable).isNotNull()
172 
173         testSubject.copyButtonRunnable?.run()
174 
175         verify(resultSender) { 1 * { onActionSelected(ShareAction.SYSTEM_COPY) } }
176     }
177 
createFactorynull178     private fun createFactory(): ChooserActionFactory {
179         val testPendingIntent =
180             PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE)
181         val targetIntent = Intent()
182         val action =
183             ChooserAction.Builder(
184                     Icon.createWithResource("", Resources.ID_NULL),
185                     actionLabel,
186                     testPendingIntent
187                 )
188                 .build()
189         return ChooserActionFactory(
190             /* context = */ context,
191             /* targetIntent = */ targetIntent,
192             /* referrerPackageName = */ "com.example",
193             /* chooserActions = */ listOf(action),
194             /* imageEditor = */ Optional.empty(),
195             /* log = */ logger,
196             /* onUpdateSharedTextIsExcluded = */ {},
197             /* firstVisibleImageQuery = */ { null },
198             /* activityStarter = */ mock(),
199             /* shareResultSender = */ null,
200             /* finishCallback = */ resultConsumer,
201             /* clipboardManager = */ mock(),
202             /* featureFlags = */ featureFlags,
203         )
204     }
205 }
206