1 /* <lambda>null2 * Copyright (C) 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 * 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.ui 18 19 import android.app.PendingIntent 20 import android.compat.testing.PlatformCompatChangeRule 21 import android.content.ComponentName 22 import android.content.Intent 23 import android.os.Process 24 import android.service.chooser.ChooserResult 25 import androidx.test.platform.app.InstrumentationRegistry 26 import com.android.intentresolver.ui.model.ShareAction 27 import com.google.common.truth.Truth.assertThat 28 import com.google.common.truth.Truth.assertWithMessage 29 import kotlinx.coroutines.CompletableDeferred 30 import kotlinx.coroutines.ExperimentalCoroutinesApi 31 import kotlinx.coroutines.test.UnconfinedTestDispatcher 32 import kotlinx.coroutines.test.runCurrent 33 import kotlinx.coroutines.test.runTest 34 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges 35 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges 36 import org.junit.Rule 37 import org.junit.Test 38 import org.junit.rules.TestRule 39 40 @OptIn(ExperimentalCoroutinesApi::class) 41 class ShareResultSenderImplTest { 42 43 private val context = InstrumentationRegistry.getInstrumentation().context 44 45 @get:Rule val compatChangeRule: TestRule = PlatformCompatChangeRule() 46 47 @OptIn(ExperimentalCoroutinesApi::class) 48 @EnableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 49 @Test 50 fun onComponentSelected_chooserResultEnabled() = runTest { 51 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 52 val deferred = CompletableDeferred<Intent>() 53 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 54 55 val resultSender = 56 ShareResultSenderImpl( 57 scope = this, 58 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 59 callerUid = Process.myUid(), 60 resultSender = pi.intentSender, 61 intentDispatcher = intentDispatcher 62 ) 63 64 resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, false) 65 runCurrent() 66 67 val intentReceived = deferred.await() 68 val chooserResult = 69 intentReceived.getParcelableExtra( 70 Intent.EXTRA_CHOOSER_RESULT, 71 ChooserResult::class.java 72 ) 73 assertThat(chooserResult).isNotNull() 74 assertThat(chooserResult?.type).isEqualTo(ChooserResult.CHOOSER_RESULT_SELECTED_COMPONENT) 75 assertThat(chooserResult?.selectedComponent).isEqualTo(ComponentName("example.com", "Foo")) 76 assertThat(chooserResult?.isShortcut).isTrue() 77 } 78 79 @OptIn(ExperimentalCoroutinesApi::class) 80 @EnableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 81 @Test 82 fun onComponentSelected_crossProfile_chooserResultEnabled() = runTest { 83 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 84 val deferred = CompletableDeferred<Intent>() 85 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 86 87 val resultSender = 88 ShareResultSenderImpl( 89 scope = this, 90 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 91 callerUid = Process.myUid(), 92 resultSender = pi.intentSender, 93 intentDispatcher = intentDispatcher 94 ) 95 96 // Invoke as in the previous test, but this time say that the selection was cross-profile. 97 resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, true) 98 runCurrent() 99 100 val intentReceived = deferred.await() 101 val chooserResult = 102 intentReceived.getParcelableExtra( 103 Intent.EXTRA_CHOOSER_RESULT, 104 ChooserResult::class.java 105 ) 106 assertThat(chooserResult).isNotNull() 107 assertThat(chooserResult?.type).isEqualTo(ChooserResult.CHOOSER_RESULT_UNKNOWN) 108 assertThat(chooserResult?.selectedComponent).isNull() 109 assertThat(chooserResult?.isShortcut).isTrue() 110 assertThat(intentReceived.hasExtra(Intent.EXTRA_CHOSEN_COMPONENT)).isFalse() 111 } 112 113 @DisableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 114 @Test 115 fun onComponentSelected_chooserResultDisabled() = runTest { 116 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 117 val deferred = CompletableDeferred<Intent>() 118 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 119 120 val resultSender = 121 ShareResultSenderImpl( 122 scope = this, 123 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 124 callerUid = Process.myUid(), 125 resultSender = pi.intentSender, 126 intentDispatcher = intentDispatcher 127 ) 128 129 resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, false) 130 runCurrent() 131 132 val intentReceived = deferred.await() 133 val componentName = 134 intentReceived.getParcelableExtra( 135 Intent.EXTRA_CHOSEN_COMPONENT, 136 ComponentName::class.java 137 ) 138 139 assertWithMessage("EXTRA_CHOSEN_COMPONENT from received intent") 140 .that(componentName) 141 .isEqualTo(ComponentName("example.com", "Foo")) 142 143 assertWithMessage("received intent has EXTRA_CHOOSER_RESULT") 144 .that(intentReceived.hasExtra(Intent.EXTRA_CHOOSER_RESULT)) 145 .isFalse() 146 } 147 148 @DisableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 149 @Test 150 fun onComponentSelected_crossProfile_chooserResultDisabled() = runTest { 151 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 152 val deferred = CompletableDeferred<Intent>() 153 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 154 155 val resultSender = 156 ShareResultSenderImpl( 157 scope = this, 158 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 159 callerUid = Process.myUid(), 160 resultSender = pi.intentSender, 161 intentDispatcher = intentDispatcher 162 ) 163 164 // Invoke as in the previous test, but this time say that the selection was cross-profile. 165 resultSender.onComponentSelected(ComponentName("example.com", "Foo"), true, true) 166 runCurrent() 167 168 // In the pre-ChooserResult API, no callback intent is sent for cross-profile selections. 169 assertWithMessage("deferred result isComplete").that(deferred.isCompleted).isFalse() 170 } 171 172 @EnableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 173 @Test 174 fun onActionSelected_chooserResultEnabled() = runTest { 175 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 176 val deferred = CompletableDeferred<Intent>() 177 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 178 179 val resultSender = 180 ShareResultSenderImpl( 181 scope = this, 182 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 183 callerUid = Process.myUid(), 184 resultSender = pi.intentSender, 185 intentDispatcher = intentDispatcher 186 ) 187 188 resultSender.onActionSelected(ShareAction.SYSTEM_COPY) 189 runCurrent() 190 191 val intentReceived = deferred.await() 192 val chosenComponent = 193 intentReceived.getParcelableExtra( 194 Intent.EXTRA_CHOSEN_COMPONENT, 195 ChooserResult::class.java 196 ) 197 assertThat(chosenComponent).isNull() 198 199 val chooserResult = 200 intentReceived.getParcelableExtra( 201 Intent.EXTRA_CHOOSER_RESULT, 202 ChooserResult::class.java 203 ) 204 assertThat(chooserResult).isNotNull() 205 assertThat(chooserResult?.type).isEqualTo(ChooserResult.CHOOSER_RESULT_COPY) 206 assertThat(chooserResult?.selectedComponent).isNull() 207 assertThat(chooserResult?.isShortcut).isFalse() 208 } 209 210 @DisableCompatChanges(ChooserResult.SEND_CHOOSER_RESULT) 211 @Test 212 fun onActionSelected_chooserResultDisabled() = runTest { 213 val pi = PendingIntent.getBroadcast(context, 0, Intent(), PendingIntent.FLAG_IMMUTABLE) 214 val deferred = CompletableDeferred<Intent>() 215 val intentDispatcher = IntentSenderDispatcher { _, intent -> deferred.complete(intent) } 216 217 val resultSender = 218 ShareResultSenderImpl( 219 scope = this, 220 backgroundDispatcher = UnconfinedTestDispatcher(testScheduler), 221 callerUid = Process.myUid(), 222 resultSender = pi.intentSender, 223 intentDispatcher = intentDispatcher 224 ) 225 226 resultSender.onActionSelected(ShareAction.SYSTEM_COPY) 227 runCurrent() 228 229 // No result should have been sent, this should never complete 230 assertWithMessage("deferred result isComplete").that(deferred.isCompleted).isFalse() 231 } 232 } 233