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 73 @Before setupnull74 fun setup() { 75 context.registerReceiver(testReceiver, IntentFilter(testAction), RECEIVER_EXPORTED) 76 } 77 78 @After teardownnull79 fun teardown() { 80 context.unregisterReceiver(testReceiver) 81 } 82 83 @Test testCreateCustomActionsnull84 fun testCreateCustomActions() { 85 val factory = createFactory() 86 87 val customActions = factory.createCustomActions() 88 89 assertThat(customActions.size).isEqualTo(1) 90 assertThat(customActions[0].label).isEqualTo(actionLabel) 91 92 // click it 93 customActions[0].onClicked.run() 94 95 verify(logger).logCustomActionSelected(eq(0)) 96 assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn) 97 // Verify the pending intent has been called 98 assertTrue("Timed out waiting for broadcast", countdown.await(2500, TimeUnit.MILLISECONDS)) 99 } 100 101 @Test nonSendAction_noCopyRunnablenull102 fun nonSendAction_noCopyRunnable() { 103 val targetIntent = 104 Intent(Intent.ACTION_SEND_MULTIPLE).apply { 105 putExtra(Intent.EXTRA_TEXT, "Text to show") 106 } 107 108 val testSubject = 109 ChooserActionFactory( 110 /* context = */ context, 111 /* targetIntent = */ targetIntent, 112 /* referrerPackageName = */ null, 113 /* chooserActions = */ emptyList(), 114 /* imageEditor = */ Optional.empty(), 115 /* log = */ logger, 116 /* onUpdateSharedTextIsExcluded = */ {}, 117 /* firstVisibleImageQuery = */ { null }, 118 /* activityStarter = */ mock(), 119 /* shareResultSender = */ null, 120 /* finishCallback = */ {}, 121 /* clipboardManager = */ mock(), 122 ) 123 assertThat(testSubject.copyButtonRunnable).isNull() 124 } 125 126 @Test sendActionNoText_noCopyRunnablenull127 fun sendActionNoText_noCopyRunnable() { 128 val targetIntent = Intent(Intent.ACTION_SEND) 129 val testSubject = 130 ChooserActionFactory( 131 /* context = */ context, 132 /* targetIntent = */ targetIntent, 133 /* referrerPackageName = */ "com.example", 134 /* chooserActions = */ emptyList(), 135 /* imageEditor = */ Optional.empty(), 136 /* log = */ logger, 137 /* onUpdateSharedTextIsExcluded = */ {}, 138 /* firstVisibleImageQuery = */ { null }, 139 /* activityStarter = */ mock(), 140 /* shareResultSender = */ null, 141 /* finishCallback = */ {}, 142 /* clipboardManager = */ mock(), 143 ) 144 assertThat(testSubject.copyButtonRunnable).isNull() 145 } 146 147 @Test sendActionWithTextCopyRunnablenull148 fun sendActionWithTextCopyRunnable() { 149 val targetIntent = Intent(Intent.ACTION_SEND).apply { putExtra(Intent.EXTRA_TEXT, "Text") } 150 val resultSender = mock<ShareResultSender>() 151 val testSubject = 152 ChooserActionFactory( 153 /* context = */ context, 154 /* targetIntent = */ targetIntent, 155 /* referrerPackageName = */ "com.example", 156 /* chooserActions = */ emptyList(), 157 /* imageEditor = */ Optional.empty(), 158 /* log = */ logger, 159 /* onUpdateSharedTextIsExcluded = */ {}, 160 /* firstVisibleImageQuery = */ { null }, 161 /* activityStarter = */ mock(), 162 /* shareResultSender = */ resultSender, 163 /* finishCallback = */ {}, 164 /* clipboardManager = */ mock(), 165 ) 166 assertThat(testSubject.copyButtonRunnable).isNotNull() 167 168 testSubject.copyButtonRunnable?.run() 169 170 verify(resultSender) { 1 * { onActionSelected(ShareAction.SYSTEM_COPY) } } 171 } 172 createFactorynull173 private fun createFactory(): ChooserActionFactory { 174 val testPendingIntent = 175 PendingIntent.getBroadcast(context, 0, Intent(testAction), PendingIntent.FLAG_IMMUTABLE) 176 val targetIntent = Intent() 177 val action = 178 ChooserAction.Builder( 179 Icon.createWithResource("", Resources.ID_NULL), 180 actionLabel, 181 testPendingIntent 182 ) 183 .build() 184 return ChooserActionFactory( 185 /* context = */ context, 186 /* targetIntent = */ targetIntent, 187 /* referrerPackageName = */ "com.example", 188 /* chooserActions = */ listOf(action), 189 /* imageEditor = */ Optional.empty(), 190 /* log = */ logger, 191 /* onUpdateSharedTextIsExcluded = */ {}, 192 /* firstVisibleImageQuery = */ { null }, 193 /* activityStarter = */ mock(), 194 /* shareResultSender = */ null, 195 /* finishCallback = */ resultConsumer, 196 /* clipboardManager = */ mock(), 197 ) 198 } 199 } 200