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