1 /* 2 * 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.launcher3.model 18 19 import android.app.PendingIntent 20 import android.content.ComponentName 21 import android.content.Intent 22 import android.content.pm.PackageInstaller.SessionInfo 23 import android.os.UserHandle 24 import androidx.test.platform.app.InstrumentationRegistry 25 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP 26 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT 27 import com.android.launcher3.model.FirstScreenBroadcastHelper.MAX_BROADCAST_SIZE 28 import com.android.launcher3.model.FirstScreenBroadcastHelper.getTotalItemCount 29 import com.android.launcher3.model.FirstScreenBroadcastHelper.truncateModelForBroadcast 30 import com.android.launcher3.model.data.FolderInfo 31 import com.android.launcher3.model.data.LauncherAppWidgetInfo 32 import com.android.launcher3.model.data.WorkspaceItemInfo 33 import com.android.launcher3.util.PackageManagerHelper 34 import com.android.launcher3.util.PackageUserKey 35 import junit.framework.Assert.assertEquals 36 import org.junit.Test 37 import org.mockito.ArgumentCaptor 38 import org.mockito.kotlin.mock 39 import org.mockito.kotlin.spy 40 import org.mockito.kotlin.verify 41 import org.mockito.kotlin.whenever 42 43 class FirstScreenBroadcastHelperTest { 44 private val context = spy(InstrumentationRegistry.getInstrumentation().targetContext) 45 private val mockPmHelper = mock<PackageManagerHelper>() 46 private val expectedAppPackage = "appPackageExpected" 47 private val expectedComponentName = ComponentName(expectedAppPackage, "expectedClass") 48 private val expectedInstallerPackage = "installerPackage" 49 private val expectedIntent = <lambda>null50 Intent().apply { 51 component = expectedComponentName 52 setPackage(expectedAppPackage) 53 } 54 private val unexpectedAppPackage = "appPackageUnexpected" 55 private val unexpectedComponentName = ComponentName(expectedAppPackage, "unexpectedClass") 56 private val firstScreenItems = 57 listOf( <lambda>null58 WorkspaceItemInfo().apply { 59 container = CONTAINER_DESKTOP 60 intent = expectedIntent 61 }, <lambda>null62 WorkspaceItemInfo().apply { 63 container = CONTAINER_HOTSEAT 64 intent = expectedIntent 65 }, <lambda>null66 LauncherAppWidgetInfo().apply { providerName = expectedComponentName } 67 ) 68 69 @Test Broadcast Models are created with Pending Items from first screennull70 fun `Broadcast Models are created with Pending Items from first screen`() { 71 // Given 72 val sessionInfoExpected = 73 SessionInfo().apply { 74 installerPackageName = expectedInstallerPackage 75 appPackageName = expectedAppPackage 76 } 77 val sessionInfoUnexpected = 78 SessionInfo().apply { 79 installerPackageName = expectedInstallerPackage 80 appPackageName = unexpectedAppPackage 81 } 82 val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> = 83 hashMapOf( 84 PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected, 85 PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected 86 ) 87 88 // When 89 val actualResult = 90 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( 91 packageManagerHelper = mockPmHelper, 92 firstScreenItems = firstScreenItems, 93 userKeyToSessionMap = sessionInfoMap, 94 allWidgets = listOf() 95 ) 96 97 // Then 98 val expectedResult = 99 listOf( 100 FirstScreenBroadcastModel( 101 installerPackage = expectedInstallerPackage, 102 pendingWorkspaceItems = mutableSetOf(expectedAppPackage), 103 pendingHotseatItems = mutableSetOf(expectedAppPackage), 104 pendingWidgetItems = mutableSetOf(expectedAppPackage) 105 ) 106 ) 107 108 assertEquals(expectedResult, actualResult) 109 } 110 111 @Test Broadcast Models are created with Installed Items from first screennull112 fun `Broadcast Models are created with Installed Items from first screen`() { 113 // Given 114 whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage)) 115 .thenReturn(expectedInstallerPackage) 116 117 // When 118 val actualResult = 119 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( 120 packageManagerHelper = mockPmHelper, 121 firstScreenItems = firstScreenItems, 122 userKeyToSessionMap = hashMapOf(), 123 allWidgets = 124 listOf( 125 LauncherAppWidgetInfo().apply { 126 providerName = expectedComponentName 127 screenId = 0 128 } 129 ) 130 ) 131 132 // Then 133 val expectedResult = 134 listOf( 135 FirstScreenBroadcastModel( 136 installerPackage = expectedInstallerPackage, 137 installedHotseatItems = mutableSetOf(expectedAppPackage), 138 installedWorkspaceItems = mutableSetOf(expectedAppPackage), 139 firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage) 140 ) 141 ) 142 assertEquals(expectedResult, actualResult) 143 } 144 145 @Test Broadcast Models are created with Installed Widgets from every screennull146 fun `Broadcast Models are created with Installed Widgets from every screen`() { 147 // Given 148 val expectedAppPackage2 = "appPackageExpected2" 149 val expectedComponentName2 = ComponentName(expectedAppPackage2, "expectedClass2") 150 whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage)) 151 .thenReturn(expectedInstallerPackage) 152 whenever(mockPmHelper.getAppInstallerPackage(expectedAppPackage2)) 153 .thenReturn(expectedInstallerPackage) 154 155 // When 156 val actualResult = 157 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( 158 packageManagerHelper = mockPmHelper, 159 firstScreenItems = listOf(), 160 userKeyToSessionMap = hashMapOf(), 161 allWidgets = 162 listOf( 163 LauncherAppWidgetInfo().apply { 164 providerName = expectedComponentName 165 screenId = 0 166 }, 167 LauncherAppWidgetInfo().apply { 168 providerName = expectedComponentName2 169 screenId = 1 170 }, 171 LauncherAppWidgetInfo().apply { 172 providerName = unexpectedComponentName 173 screenId = 0 174 } 175 ) 176 ) 177 178 // Then 179 val expectedResult = 180 listOf( 181 FirstScreenBroadcastModel( 182 installerPackage = expectedInstallerPackage, 183 installedHotseatItems = mutableSetOf(), 184 installedWorkspaceItems = mutableSetOf(), 185 firstScreenInstalledWidgets = mutableSetOf(expectedAppPackage), 186 secondaryScreenInstalledWidgets = mutableSetOf(expectedAppPackage2) 187 ) 188 ) 189 assertEquals(expectedResult, actualResult) 190 } 191 192 @Test Broadcast Models are created with Pending Items in Collections from the first screennull193 fun `Broadcast Models are created with Pending Items in Collections from the first screen`() { 194 // Given 195 val sessionInfoExpected = 196 SessionInfo().apply { 197 installerPackageName = expectedInstallerPackage 198 appPackageName = expectedAppPackage 199 } 200 val sessionInfoUnexpected = 201 SessionInfo().apply { 202 installerPackageName = expectedInstallerPackage 203 appPackageName = unexpectedAppPackage 204 } 205 val sessionInfoMap: HashMap<PackageUserKey, SessionInfo> = 206 hashMapOf( 207 PackageUserKey(unexpectedAppPackage, UserHandle(0)) to sessionInfoExpected, 208 PackageUserKey(expectedAppPackage, UserHandle(0)) to sessionInfoUnexpected, 209 ) 210 val expectedItemInfo = WorkspaceItemInfo().apply { intent = expectedIntent } 211 val expectedFolderInfo = FolderInfo().apply { add(expectedItemInfo) } 212 val firstScreenItems = listOf(expectedFolderInfo) 213 214 // When 215 val actualResult = 216 FirstScreenBroadcastHelper.createModelsForFirstScreenBroadcast( 217 packageManagerHelper = mockPmHelper, 218 firstScreenItems = firstScreenItems, 219 userKeyToSessionMap = sessionInfoMap, 220 allWidgets = listOf() 221 ) 222 223 // Then 224 val expectedResult = 225 listOf( 226 FirstScreenBroadcastModel( 227 installerPackage = expectedInstallerPackage, 228 pendingCollectionItems = mutableSetOf(expectedAppPackage) 229 ) 230 ) 231 assertEquals(expectedResult, actualResult) 232 } 233 234 @Test Models with too many items get truncated to max Broadcast sizenull235 fun `Models with too many items get truncated to max Broadcast size`() { 236 // given 237 val broadcastModel = 238 FirstScreenBroadcastModel( 239 installerPackage = expectedInstallerPackage, 240 pendingCollectionItems = 241 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 242 pendingWorkspaceItems = 243 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 244 pendingHotseatItems = 245 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 246 pendingWidgetItems = 247 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 248 installedWorkspaceItems = 249 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 250 installedHotseatItems = 251 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 252 firstScreenInstalledWidgets = 253 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 254 secondaryScreenInstalledWidgets = 255 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } } 256 ) 257 258 // When 259 broadcastModel.truncateModelForBroadcast() 260 261 // Then 262 assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) 263 } 264 265 @Test Broadcast truncates installed Hotseat items before other installed itemsnull266 fun `Broadcast truncates installed Hotseat items before other installed items`() { 267 // Given 268 val broadcastModel = 269 FirstScreenBroadcastModel( 270 installerPackage = expectedInstallerPackage, 271 installedWorkspaceItems = 272 mutableSetOf<String>().apply { repeat(50) { add(it.toString()) } }, 273 firstScreenInstalledWidgets = 274 mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } }, 275 secondaryScreenInstalledWidgets = 276 mutableSetOf<String>().apply { repeat(10) { add((it + 10).toString()) } }, 277 installedHotseatItems = 278 mutableSetOf<String>().apply { repeat(10) { add(it.toString()) } }, 279 ) 280 281 // When 282 broadcastModel.truncateModelForBroadcast() 283 284 // Then 285 assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) 286 assertEquals(50, broadcastModel.installedWorkspaceItems.size) 287 assertEquals(10, broadcastModel.firstScreenInstalledWidgets.size) 288 assertEquals(10, broadcastModel.secondaryScreenInstalledWidgets.size) 289 assertEquals(0, broadcastModel.installedHotseatItems.size) 290 } 291 292 @Test Broadcast truncates Widgets before the rest of the first screen itemsnull293 fun `Broadcast truncates Widgets before the rest of the first screen items`() { 294 // Given 295 val broadcastModel = 296 FirstScreenBroadcastModel( 297 installerPackage = expectedInstallerPackage, 298 installedWorkspaceItems = 299 mutableSetOf<String>().apply { repeat(70) { add(it.toString()) } }, 300 firstScreenInstalledWidgets = 301 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 302 secondaryScreenInstalledWidgets = 303 mutableSetOf<String>().apply { repeat(20) { add(it.toString()) } }, 304 ) 305 306 // When 307 broadcastModel.truncateModelForBroadcast() 308 309 // Then 310 assertEquals(MAX_BROADCAST_SIZE, broadcastModel.getTotalItemCount()) 311 assertEquals(70, broadcastModel.installedWorkspaceItems.size) 312 assertEquals(0, broadcastModel.firstScreenInstalledWidgets.size) 313 assertEquals(0, broadcastModel.secondaryScreenInstalledWidgets.size) 314 } 315 316 @Test Broadcasts are correctly formed with Extras for each Installernull317 fun `Broadcasts are correctly formed with Extras for each Installer`() { 318 // Given 319 val broadcastModels: List<FirstScreenBroadcastModel> = 320 listOf( 321 FirstScreenBroadcastModel( 322 installerPackage = expectedInstallerPackage, 323 pendingCollectionItems = mutableSetOf("pendingCollectionItem"), 324 pendingWorkspaceItems = mutableSetOf("pendingWorkspaceItem"), 325 pendingHotseatItems = mutableSetOf("pendingHotseatItems"), 326 pendingWidgetItems = mutableSetOf("pendingWidgetItems"), 327 installedWorkspaceItems = mutableSetOf("installedWorkspaceItems"), 328 installedHotseatItems = mutableSetOf("installedHotseatItems"), 329 firstScreenInstalledWidgets = mutableSetOf("firstScreenInstalledWidgetItems"), 330 secondaryScreenInstalledWidgets = mutableSetOf("secondaryInstalledWidgetItems") 331 ) 332 ) 333 val expectedPendingIntent = 334 PendingIntent.getActivity( 335 context, 336 0 /* requestCode */, 337 Intent(), 338 PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE 339 ) 340 341 // When 342 FirstScreenBroadcastHelper.sendBroadcastsForModels(context, broadcastModels) 343 344 // Then 345 val argumentCaptor = ArgumentCaptor.forClass(Intent::class.java) 346 verify(context).sendBroadcast(argumentCaptor.capture()) 347 348 assertEquals( 349 "com.android.launcher3.action.FIRST_SCREEN_ACTIVE_INSTALLS", 350 argumentCaptor.value.action 351 ) 352 assertEquals(expectedInstallerPackage, argumentCaptor.value.`package`) 353 assertEquals( 354 expectedPendingIntent, 355 argumentCaptor.value.getParcelableExtra("verificationToken") 356 ) 357 assertEquals( 358 arrayListOf("pendingCollectionItem"), 359 argumentCaptor.value.getStringArrayListExtra("folderItem") 360 ) 361 assertEquals( 362 arrayListOf("pendingWorkspaceItem"), 363 argumentCaptor.value.getStringArrayListExtra("workspaceItem") 364 ) 365 assertEquals( 366 arrayListOf("pendingHotseatItems"), 367 argumentCaptor.value.getStringArrayListExtra("hotseatItem") 368 ) 369 assertEquals( 370 arrayListOf("pendingWidgetItems"), 371 argumentCaptor.value.getStringArrayListExtra("widgetItem") 372 ) 373 assertEquals( 374 arrayListOf("installedWorkspaceItems"), 375 argumentCaptor.value.getStringArrayListExtra("workspaceInstalledItems") 376 ) 377 assertEquals( 378 arrayListOf("installedHotseatItems"), 379 argumentCaptor.value.getStringArrayListExtra("hotseatInstalledItems") 380 ) 381 assertEquals( 382 arrayListOf("firstScreenInstalledWidgetItems", "secondaryInstalledWidgetItems"), 383 argumentCaptor.value.getStringArrayListExtra("widgetInstalledItems") 384 ) 385 } 386 } 387