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.launcher3.widget.picker.model.data 18 19 import android.content.ComponentName 20 import android.content.Context 21 import android.os.UserHandle 22 import android.platform.test.rule.AllowedDevices 23 import android.platform.test.rule.DeviceProduct 24 import android.platform.test.rule.LimitDevicesRule 25 import androidx.test.core.app.ApplicationProvider 26 import androidx.test.ext.junit.runners.AndroidJUnit4 27 import com.android.launcher3.InvariantDeviceProfile 28 import com.android.launcher3.LauncherAppState 29 import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION 30 import com.android.launcher3.icons.IconCache 31 import com.android.launcher3.icons.cache.CachedObject 32 import com.android.launcher3.model.WidgetItem 33 import com.android.launcher3.model.data.ItemInfo 34 import com.android.launcher3.model.data.PackageItemInfo 35 import com.android.launcher3.util.ActivityContextWrapper 36 import com.android.launcher3.util.PackageUserKey 37 import com.android.launcher3.util.WidgetUtils 38 import com.android.launcher3.widget.LauncherAppWidgetProviderInfo 39 import com.android.launcher3.widget.PendingAddWidgetInfo 40 import com.android.launcher3.widget.model.WidgetsListBaseEntry 41 import com.android.launcher3.widget.model.WidgetsListContentEntry 42 import com.android.launcher3.widget.model.WidgetsListHeaderEntry 43 import com.android.launcher3.widget.picker.WidgetRecommendationCategory 44 import com.android.launcher3.widget.picker.WidgetRecommendationCategory.DEFAULT_WIDGET_RECOMMENDATION_CATEGORY 45 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findAllWidgetsForPackageUser 46 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.findContentEntryForPackageUser 47 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withRecommendedWidgets 48 import com.android.launcher3.widget.picker.model.data.WidgetPickerDataUtils.withWidgets 49 import com.google.common.truth.Truth.assertThat 50 import org.junit.Before 51 import org.junit.Rule 52 import org.junit.Test 53 import org.junit.runner.RunWith 54 import org.mockito.Mock 55 import org.mockito.invocation.InvocationOnMock 56 import org.mockito.junit.MockitoJUnit 57 import org.mockito.junit.MockitoRule 58 import org.mockito.kotlin.any 59 import org.mockito.kotlin.doAnswer 60 61 // Tests for code / classes in WidgetPickerData file. 62 63 @RunWith(AndroidJUnit4::class) 64 @AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC]) 65 class WidgetPickerDataTest { 66 @Rule @JvmField val limitDevicesRule = LimitDevicesRule() 67 @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() 68 69 @Mock private lateinit var iconCache: IconCache 70 71 private lateinit var userHandle: UserHandle 72 private lateinit var context: Context 73 private lateinit var testInvariantProfile: InvariantDeviceProfile 74 75 private lateinit var app1PackageItemInfo: PackageItemInfo 76 private lateinit var app2PackageItemInfo: PackageItemInfo 77 78 private lateinit var app1WidgetItem1: WidgetItem 79 private lateinit var app1WidgetItem2: WidgetItem 80 private lateinit var app2WidgetItem1: WidgetItem 81 82 @Before 83 fun setUp() { 84 userHandle = UserHandle.CURRENT 85 context = ActivityContextWrapper(ApplicationProvider.getApplicationContext()) 86 testInvariantProfile = LauncherAppState.getIDP(context) 87 88 doAnswer { invocation: InvocationOnMock -> 89 val componentWithLabel = invocation.getArgument<Any>(0) as CachedObject 90 componentWithLabel.getComponent().shortClassName 91 } 92 .`when`(iconCache) 93 .getTitleNoCache(any<CachedObject>()) 94 95 app1PackageItemInfo = packageItemInfoWithTitle(APP_1_PACKAGE_NAME, APP_1_PACKAGE_TITLE) 96 app2PackageItemInfo = packageItemInfoWithTitle(APP_2_PACKAGE_NAME, APP_2_PACKAGE_TITLE) 97 98 app1WidgetItem1 = createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_1_CLASS_NAME) 99 app1WidgetItem2 = createWidgetItem(APP_1_PACKAGE_NAME, APP_1_PROVIDER_2_CLASS_NAME) 100 app2WidgetItem1 = createWidgetItem(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME) 101 } 102 103 @Test 104 fun withWidgets_returnsACopyWithProvidedWidgets() { 105 // only app two 106 val widgetPickerData = WidgetPickerData(allWidgets = appTwoWidgetsListBaseEntries()) 107 108 // update: only app 1 and default list set 109 val newAllWidgets: List<WidgetsListBaseEntry> = 110 appOneWidgetsListBaseEntries(includeWidgetTwo = true) 111 val newDefaultWidgets: List<WidgetsListBaseEntry> = 112 appOneWidgetsListBaseEntries(includeWidgetTwo = false) 113 114 val newWidgetData = widgetPickerData.withWidgets(newAllWidgets, newDefaultWidgets) 115 116 assertThat(newWidgetData.allWidgets).containsExactlyElementsIn(newAllWidgets) 117 assertThat(newWidgetData.defaultWidgets).containsExactlyElementsIn(newDefaultWidgets) 118 } 119 120 @Test 121 fun withWidgets_noExplicitDefaults_unsetsOld() { 122 // only app two 123 val widgetPickerData = 124 WidgetPickerData( 125 allWidgets = appTwoWidgetsListBaseEntries(), 126 defaultWidgets = appTwoWidgetsListBaseEntries(), 127 ) 128 129 val newWidgetData = 130 widgetPickerData.withWidgets(allWidgets = appOneWidgetsListBaseEntries()) 131 132 assertThat(newWidgetData.allWidgets) 133 .containsExactlyElementsIn(appOneWidgetsListBaseEntries()) 134 assertThat(newWidgetData.defaultWidgets).isEmpty() // previous values cleared. 135 } 136 137 @Test 138 fun withRecommendedWidgets_returnsACopyWithProvidedRecommendedWidgets() { 139 val widgetPickerData = 140 WidgetPickerData( 141 allWidgets = 142 buildList { 143 addAll(appOneWidgetsListBaseEntries()) 144 addAll(appTwoWidgetsListBaseEntries()) 145 }, 146 defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }, 147 ) 148 val recommendations: List<ItemInfo> = 149 listOf( 150 PendingAddWidgetInfo( 151 app1WidgetItem1.widgetInfo, 152 CONTAINER_WIDGETS_PREDICTION, 153 CATEGORY_1, 154 ), 155 PendingAddWidgetInfo( 156 app2WidgetItem1.widgetInfo, 157 CONTAINER_WIDGETS_PREDICTION, 158 CATEGORY_2, 159 ), 160 ) 161 162 val updatedData = widgetPickerData.withRecommendedWidgets(recommendations) 163 164 assertThat(updatedData.recommendations.keys).containsExactly(CATEGORY_1, CATEGORY_2) 165 assertThat(updatedData.recommendations[CATEGORY_1]).containsExactly(app1WidgetItem1) 166 assertThat(updatedData.recommendations[CATEGORY_2]).containsExactly(app2WidgetItem1) 167 } 168 169 @Test 170 fun withRecommendedWidgets_noCategory_usesDefault() { 171 val widgetPickerData = 172 WidgetPickerData( 173 allWidgets = 174 buildList { 175 addAll(appOneWidgetsListBaseEntries()) 176 addAll(appTwoWidgetsListBaseEntries()) 177 }, 178 defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }, 179 ) 180 val recommendations: List<ItemInfo> = 181 listOf( 182 PendingAddWidgetInfo(app1WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION), 183 PendingAddWidgetInfo(app2WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION), 184 ) 185 186 val updatedData = widgetPickerData.withRecommendedWidgets(recommendations) 187 188 assertThat(updatedData.recommendations.keys) 189 .containsExactly(DEFAULT_WIDGET_RECOMMENDATION_CATEGORY) 190 assertThat(updatedData.recommendations[DEFAULT_WIDGET_RECOMMENDATION_CATEGORY]) 191 .containsExactly(app1WidgetItem1, app2WidgetItem1) 192 } 193 194 @Test 195 fun withRecommendedWidgets_emptyRecommendations_clearsOld() { 196 val widgetPickerData = 197 WidgetPickerData( 198 allWidgets = 199 buildList { 200 addAll(appOneWidgetsListBaseEntries()) 201 addAll(appTwoWidgetsListBaseEntries()) 202 }, 203 defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }, 204 recommendations = mapOf(CATEGORY_1 to listOf(app1WidgetItem1)), 205 ) 206 207 val updatedData = widgetPickerData.withRecommendedWidgets(listOf()) 208 209 assertThat(updatedData.recommendations).isEmpty() 210 } 211 212 @Test 213 fun withRecommendedWidgets_widgetNotInAllWidgets_filteredOut() { 214 val widgetPickerData = 215 WidgetPickerData( 216 allWidgets = 217 buildList { 218 addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) 219 addAll(appTwoWidgetsListBaseEntries()) 220 }, 221 defaultWidgets = buildList { appTwoWidgetsListBaseEntries() }, 222 ) 223 224 val recommendations: List<ItemInfo> = 225 listOf( 226 PendingAddWidgetInfo(app1WidgetItem2.widgetInfo, CONTAINER_WIDGETS_PREDICTION), 227 PendingAddWidgetInfo(app2WidgetItem1.widgetInfo, CONTAINER_WIDGETS_PREDICTION), 228 ) 229 val updatedData = widgetPickerData.withRecommendedWidgets(recommendations) 230 231 assertThat(updatedData.recommendations).hasSize(1) 232 // no app1widget2 233 assertThat(updatedData.recommendations.values.first()).containsExactly(app2WidgetItem1) 234 } 235 236 @Test 237 fun findContentEntryForPackageUser_returnsCorrectEntry() { 238 val widgetPickerData = 239 WidgetPickerData( 240 allWidgets = 241 buildList { 242 addAll(appOneWidgetsListBaseEntries()) 243 addAll(appTwoWidgetsListBaseEntries()) 244 }, 245 defaultWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) }, 246 ) 247 val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo) 248 249 val contentEntry = findContentEntryForPackageUser(widgetPickerData, app1PackageUserKey) 250 251 assertThat(contentEntry).isNotNull() 252 assertThat(contentEntry?.mPkgItem).isEqualTo(app1PackageItemInfo) 253 assertThat(contentEntry?.mWidgets).hasSize(2) 254 } 255 256 @Test 257 fun findContentEntryForPackageUser_fromDefaults_returnsEntryFromDefaultWidgets() { 258 val widgetPickerData = 259 WidgetPickerData( 260 allWidgets = 261 buildList { 262 addAll(appOneWidgetsListBaseEntries()) 263 addAll(appTwoWidgetsListBaseEntries()) 264 }, 265 defaultWidgets = 266 buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }, 267 ) 268 val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo) 269 270 val contentEntry = 271 findContentEntryForPackageUser( 272 widgetPickerData = widgetPickerData, 273 packageUserKey = app1PackageUserKey, 274 fromDefaultWidgets = true, 275 ) 276 277 assertThat(contentEntry).isNotNull() 278 assertThat(contentEntry?.mPkgItem).isEqualTo(app1PackageItemInfo) 279 // only one widget (since default widgets had only one widget for app A 280 assertThat(contentEntry?.mWidgets).hasSize(1) 281 } 282 283 @Test 284 fun findContentEntryForPackageUser_noMatch_returnsNull() { 285 val app2PackageUserKey = PackageUserKey.fromPackageItemInfo(app2PackageItemInfo) 286 val widgetPickerData = 287 WidgetPickerData(allWidgets = buildList { addAll(appOneWidgetsListBaseEntries()) }) 288 289 val contentEntry = findContentEntryForPackageUser(widgetPickerData, app2PackageUserKey) 290 291 assertThat(contentEntry).isNull() 292 } 293 294 @Test 295 fun findAllWidgetsForPackageUser_returnsListOfWidgets() { 296 val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo) 297 val widgetPickerData = 298 WidgetPickerData( 299 allWidgets = 300 buildList { 301 addAll(appOneWidgetsListBaseEntries()) 302 addAll(appTwoWidgetsListBaseEntries()) 303 }, 304 defaultWidgets = 305 buildList { addAll(appOneWidgetsListBaseEntries(includeWidgetTwo = false)) }, 306 ) 307 308 val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey) 309 310 // both widgets returned irrespective of default widgets list 311 assertThat(widgets).hasSize(2) 312 } 313 314 @Test 315 fun findAllWidgetsForPackageUser_noMatch_returnsEmptyList() { 316 val widgetPickerData = 317 WidgetPickerData(allWidgets = buildList { addAll(appTwoWidgetsListBaseEntries()) }) 318 val app1PackageUserKey = PackageUserKey.fromPackageItemInfo(app1PackageItemInfo) 319 320 val widgets = findAllWidgetsForPackageUser(widgetPickerData, app1PackageUserKey) 321 322 assertThat(widgets).isEmpty() 323 } 324 325 private fun packageItemInfoWithTitle(packageName: String, title: String): PackageItemInfo { 326 val packageItemInfo = PackageItemInfo(packageName, userHandle) 327 packageItemInfo.title = title 328 return packageItemInfo 329 } 330 331 private fun createWidgetItem(packageName: String, widgetProviderName: String): WidgetItem { 332 val providerInfo = 333 WidgetUtils.createAppWidgetProviderInfo( 334 ComponentName.createRelative(packageName, widgetProviderName) 335 ) 336 val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo) 337 return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context) 338 } 339 340 private fun appTwoWidgetsListBaseEntries(): List<WidgetsListBaseEntry> = buildList { 341 val widgets = listOf(app2WidgetItem1) 342 add(WidgetsListHeaderEntry.create(app2PackageItemInfo, APP_2_SECTION_NAME, widgets)) 343 add(WidgetsListContentEntry(app2PackageItemInfo, APP_2_SECTION_NAME, widgets)) 344 } 345 346 private fun appOneWidgetsListBaseEntries( 347 includeWidgetTwo: Boolean = true 348 ): List<WidgetsListBaseEntry> = buildList { 349 val widgets = 350 if (includeWidgetTwo) { 351 listOf(app1WidgetItem1, app1WidgetItem2) 352 } else { 353 listOf(app1WidgetItem1) 354 } 355 356 add(WidgetsListHeaderEntry.create(app1PackageItemInfo, APP_1_SECTION_NAME, widgets)) 357 add(WidgetsListContentEntry(app1PackageItemInfo, APP_1_SECTION_NAME, widgets)) 358 } 359 360 companion object { 361 private const val APP_1_PACKAGE_NAME = "com.example.app1" 362 private const val APP_1_PACKAGE_TITLE = "App1" 363 private const val APP_1_SECTION_NAME = "A" // for fast popup 364 private const val APP_1_PROVIDER_1_CLASS_NAME = "app1Provider1" 365 private const val APP_1_PROVIDER_2_CLASS_NAME = "app1Provider2" 366 367 private const val APP_2_PACKAGE_NAME = "com.example.app2" 368 private const val APP_2_PACKAGE_TITLE = "SomeApp2" 369 private const val APP_2_SECTION_NAME = "S" // for fast popup 370 private const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1" 371 372 private val CATEGORY_1 = 373 WidgetRecommendationCategory(/* categoryTitleRes= */ 0, /* order= */ 0) 374 private val CATEGORY_2 = 375 WidgetRecommendationCategory(/* categoryTitleRes= */ 1, /* order= */ 1) 376 } 377 } 378