1 /* <lambda>null2 * Copyright (C) 2022 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.content.ComponentName 20 import android.content.Context 21 import android.content.Intent 22 import android.content.pm.ShortcutInfo 23 import android.os.UserHandle 24 import android.service.chooser.ChooserTarget 25 import androidx.test.filters.SmallTest 26 import androidx.test.platform.app.InstrumentationRegistry 27 import com.android.intentresolver.chooser.DisplayResolveInfo 28 import com.android.intentresolver.chooser.TargetInfo 29 import org.junit.Assert.assertEquals 30 import org.junit.Assert.assertTrue 31 import org.junit.Test 32 import org.mockito.kotlin.doReturn 33 import org.mockito.kotlin.mock 34 35 private const val PACKAGE_A = "package.a" 36 private const val PACKAGE_B = "package.b" 37 private const val CLASS_NAME = "./MainActivity" 38 39 @SmallTest 40 class ShortcutSelectionLogicTest { 41 private val PERSONAL_USER_HANDLE: UserHandle = 42 InstrumentationRegistry.getInstrumentation().getTargetContext().getUser() 43 44 private val packageTargets = 45 HashMap<String, Array<ChooserTarget>>().apply { 46 arrayOf(PACKAGE_A, PACKAGE_B).forEach { pkg -> 47 // shortcuts in reverse priority order 48 val targets = 49 Array(3) { i -> 50 createChooserTarget( 51 "Shortcut $i", 52 (i + 1).toFloat() / 10f, 53 ComponentName(pkg, CLASS_NAME), 54 pkg.shortcutId(i), 55 ) 56 } 57 this[pkg] = targets 58 } 59 } 60 61 private val baseDisplayInfo = 62 DisplayResolveInfo.newDisplayResolveInfo( 63 Intent(), 64 ResolverDataProvider.createResolveInfo(3, 0, PERSONAL_USER_HANDLE), 65 "label", 66 "extended info", 67 Intent() 68 ) 69 70 private val otherBaseDisplayInfo = 71 DisplayResolveInfo.newDisplayResolveInfo( 72 Intent(), 73 ResolverDataProvider.createResolveInfo(4, 0, PERSONAL_USER_HANDLE), 74 "label 2", 75 "extended info 2", 76 Intent() 77 ) 78 79 private operator fun Map<String, Array<ChooserTarget>>.get(pkg: String, idx: Int) = 80 this[pkg]?.get(idx) ?: error("missing package $pkg") 81 82 @Test 83 fun testAddShortcuts_no_limits() { 84 val serviceResults = ArrayList<TargetInfo>() 85 val sc1 = packageTargets[PACKAGE_A, 0] 86 val sc2 = packageTargets[PACKAGE_A, 1] 87 val testSubject = 88 ShortcutSelectionLogic( 89 /* maxShortcutTargetsPerApp = */ 1, 90 /* applySharingAppLimits = */ false 91 ) 92 93 val isUpdated = 94 testSubject.addServiceResults( 95 /* origTarget = */ baseDisplayInfo, 96 /* origTargetScore = */ 0.1f, 97 /* targets = */ listOf(sc1, sc2), 98 /* isShortcutResult = */ true, 99 /* directShareToShortcutInfos = */ emptyMap(), 100 /* directShareToAppTargets = */ emptyMap(), 101 /* userContext = */ mock(), 102 /* targetIntent = */ mock(), 103 /* refererFillInIntent = */ mock(), 104 /* maxRankedTargets = */ 4, 105 /* serviceTargets = */ serviceResults 106 ) 107 108 assertTrue("Updates are expected", isUpdated) 109 assertShortcutsInOrder( 110 listOf(sc2, sc1), 111 serviceResults, 112 "Two shortcuts are expected as we do not apply per-app shortcut limit" 113 ) 114 } 115 116 @Test 117 fun testAddShortcuts_same_package_with_per_package_limit() { 118 val serviceResults = ArrayList<TargetInfo>() 119 val sc1 = packageTargets[PACKAGE_A, 0] 120 val sc2 = packageTargets[PACKAGE_A, 1] 121 val testSubject = 122 ShortcutSelectionLogic( 123 /* maxShortcutTargetsPerApp = */ 1, 124 /* applySharingAppLimits = */ true 125 ) 126 127 val isUpdated = 128 testSubject.addServiceResults( 129 /* origTarget = */ baseDisplayInfo, 130 /* origTargetScore = */ 0.1f, 131 /* targets = */ listOf(sc1, sc2), 132 /* isShortcutResult = */ true, 133 /* directShareToShortcutInfos = */ emptyMap(), 134 /* directShareToAppTargets = */ emptyMap(), 135 /* userContext = */ mock(), 136 /* targetIntent = */ mock(), 137 /* refererFillInIntent = */ mock(), 138 /* maxRankedTargets = */ 4, 139 /* serviceTargets = */ serviceResults 140 ) 141 142 assertTrue("Updates are expected", isUpdated) 143 assertShortcutsInOrder( 144 listOf(sc2), 145 serviceResults, 146 "One shortcut is expected as we apply per-app shortcut limit" 147 ) 148 } 149 150 @Test 151 fun testAddShortcuts_same_package_no_per_app_limit_with_target_limit() { 152 val serviceResults = ArrayList<TargetInfo>() 153 val sc1 = packageTargets[PACKAGE_A, 0] 154 val sc2 = packageTargets[PACKAGE_A, 1] 155 val testSubject = 156 ShortcutSelectionLogic( 157 /* maxShortcutTargetsPerApp = */ 1, 158 /* applySharingAppLimits = */ false 159 ) 160 161 val isUpdated = 162 testSubject.addServiceResults( 163 /* origTarget = */ baseDisplayInfo, 164 /* origTargetScore = */ 0.1f, 165 /* targets = */ listOf(sc1, sc2), 166 /* isShortcutResult = */ true, 167 /* directShareToShortcutInfos = */ emptyMap(), 168 /* directShareToAppTargets = */ emptyMap(), 169 /* userContext = */ mock(), 170 /* targetIntent = */ mock(), 171 /* refererFillInIntent = */ mock(), 172 /* maxRankedTargets = */ 1, 173 /* serviceTargets = */ serviceResults 174 ) 175 176 assertTrue("Updates are expected", isUpdated) 177 assertShortcutsInOrder( 178 listOf(sc2), 179 serviceResults, 180 "One shortcut is expected as we apply overall shortcut limit" 181 ) 182 } 183 184 @Test 185 fun testAddShortcuts_different_packages_with_per_package_limit() { 186 val serviceResults = ArrayList<TargetInfo>() 187 val pkgAsc1 = packageTargets[PACKAGE_A, 0] 188 val pkgAsc2 = packageTargets[PACKAGE_A, 1] 189 val pkgBsc1 = packageTargets[PACKAGE_B, 0] 190 val pkgBsc2 = packageTargets[PACKAGE_B, 1] 191 val testSubject = 192 ShortcutSelectionLogic( 193 /* maxShortcutTargetsPerApp = */ 1, 194 /* applySharingAppLimits = */ true 195 ) 196 197 testSubject.addServiceResults( 198 /* origTarget = */ baseDisplayInfo, 199 /* origTargetScore = */ 0.1f, 200 /* targets = */ listOf(pkgAsc1, pkgAsc2), 201 /* isShortcutResult = */ true, 202 /* directShareToShortcutInfos = */ emptyMap(), 203 /* directShareToAppTargets = */ emptyMap(), 204 /* userContext = */ mock(), 205 /* targetIntent = */ mock(), 206 /* refererFillInIntent = */ mock(), 207 /* maxRankedTargets = */ 4, 208 /* serviceTargets = */ serviceResults 209 ) 210 testSubject.addServiceResults( 211 /* origTarget = */ otherBaseDisplayInfo, 212 /* origTargetScore = */ 0.2f, 213 /* targets = */ listOf(pkgBsc1, pkgBsc2), 214 /* isShortcutResult = */ true, 215 /* directShareToShortcutInfos = */ emptyMap(), 216 /* directShareToAppTargets = */ emptyMap(), 217 /* userContext = */ mock(), 218 /* targetIntent = */ mock(), 219 /* refererFillInIntent = */ mock(), 220 /* maxRankedTargets = */ 4, 221 /* serviceTargets = */ serviceResults 222 ) 223 224 assertShortcutsInOrder( 225 listOf(pkgBsc2, pkgAsc2), 226 serviceResults, 227 "Two shortcuts are expected as we apply per-app shortcut limit" 228 ) 229 } 230 231 @Test 232 fun testAddShortcuts_pinned_shortcut() { 233 val serviceResults = ArrayList<TargetInfo>() 234 val sc1 = packageTargets[PACKAGE_A, 0] 235 val sc2 = packageTargets[PACKAGE_A, 1] 236 val testSubject = 237 ShortcutSelectionLogic( 238 /* maxShortcutTargetsPerApp = */ 1, 239 /* applySharingAppLimits = */ false 240 ) 241 242 val isUpdated = 243 testSubject.addServiceResults( 244 /* origTarget = */ baseDisplayInfo, 245 /* origTargetScore = */ 0.1f, 246 /* targets = */ listOf(sc1, sc2), 247 /* isShortcutResult = */ true, 248 /* directShareToShortcutInfos = */ mapOf( 249 sc1 to 250 createShortcutInfo(PACKAGE_A.shortcutId(1), sc1.componentName, 1).apply { 251 addFlags(ShortcutInfo.FLAG_PINNED) 252 } 253 ), 254 /* directShareToAppTargets = */ emptyMap(), 255 /* userContext = */ mock(), 256 /* targetIntent = */ mock(), 257 /* refererFillInIntent = */ mock(), 258 /* maxRankedTargets = */ 4, 259 /* serviceTargets = */ serviceResults 260 ) 261 262 assertTrue("Updates are expected", isUpdated) 263 assertShortcutsInOrder( 264 listOf(sc1, sc2), 265 serviceResults, 266 "Two shortcuts are expected as we do not apply per-app shortcut limit" 267 ) 268 } 269 270 @Test 271 fun test_available_caller_shortcuts_count_is_limited() { 272 val serviceResults = ArrayList<TargetInfo>() 273 val sc1 = packageTargets[PACKAGE_A, 0] 274 val sc2 = packageTargets[PACKAGE_A, 1] 275 val sc3 = packageTargets[PACKAGE_A, 2] 276 val testSubject = 277 ShortcutSelectionLogic( 278 /* maxShortcutTargetsPerApp = */ 1, 279 /* applySharingAppLimits = */ true 280 ) 281 val context = mock<Context> { on { packageManager } doReturn (mock()) } 282 283 testSubject.addServiceResults( 284 /* origTarget = */ baseDisplayInfo, 285 /* origTargetScore = */ 0f, 286 /* targets = */ listOf(sc1, sc2, sc3), 287 /* isShortcutResult = */ false, 288 /* directShareToShortcutInfos = */ emptyMap(), 289 /* directShareToAppTargets = */ emptyMap(), 290 /* userContext = */ context, 291 /* targetIntent = */ mock(), 292 /* refererFillInIntent = */ mock(), 293 /* maxRankedTargets = */ 4, 294 /* serviceTargets = */ serviceResults 295 ) 296 297 assertShortcutsInOrder( 298 listOf(sc3, sc2), 299 serviceResults, 300 "At most two caller-provided shortcuts are allowed" 301 ) 302 } 303 304 // TODO: consider renaming. Not all `ChooserTarget`s are "shortcuts" and many of our test cases 305 // add results with `isShortcutResult = false` and `directShareToShortcutInfos = emptyMap()`. 306 private fun assertShortcutsInOrder( 307 expected: List<ChooserTarget>, 308 actual: List<TargetInfo>, 309 msg: String? = "" 310 ) { 311 assertEquals(msg, expected.size, actual.size) 312 for (i in expected.indices) { 313 assertEquals( 314 "Unexpected item at position $i", 315 expected[i].componentName, 316 actual[i].chooserTargetComponentName 317 ) 318 assertEquals( 319 "Unexpected item at position $i", 320 expected[i].title, 321 actual[i].displayLabel 322 ) 323 } 324 } 325 326 private fun String.shortcutId(id: Int) = "$this.$id" 327 } 328