Files
Lawnchair/tests/multivalentTests/src/com/android/launcher3/model/WidgetsModelTest.kt
T
Shamali P ea078cb647 Add a resource override for providing default widgets filter.
The implementation in this CL is no-op i.e. all widgets will be shown

Bug: 356127021
Flag: com.android.launcher3.enable_tiered_widgets_by_default_in_picker
Test: Verified with other child cls
Change-Id: I0a6f8973dc8ec58fd87d29b3f291b5e290ea988d
2024-11-04 22:17:23 +00:00

262 lines
10 KiB
Kotlin

/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.model
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.os.UserHandle
import android.platform.test.rule.AllowedDevices
import android.platform.test.rule.DeviceProduct
import android.platform.test.rule.LimitDevicesRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.data.PackageItemInfo
import com.android.launcher3.pm.UserCache
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.Executors
import com.android.launcher3.util.IntSet
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
import com.android.launcher3.widget.WidgetSections
import com.android.launcher3.widget.WidgetSections.NO_CATEGORY
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.function.Predicate
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
@AllowedDevices(allowed = [DeviceProduct.ROBOLECTRIC])
@RunWith(AndroidJUnit4::class)
class WidgetsModelTest {
@Rule @JvmField val limitDevicesRule = LimitDevicesRule()
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var appWidgetManager: AppWidgetManager
@Mock private lateinit var app: LauncherAppState
@Mock private lateinit var iconCacheMock: IconCache
@Mock private lateinit var widgetsFilterDataProvider: WidgetsFilterDataProvider
private lateinit var context: Context
private lateinit var idp: InvariantDeviceProfile
private lateinit var underTest: WidgetsModel
private var widgetSectionCategory: Int = 0
private lateinit var appAPackage: String
@Before
fun setUp() {
val appContext: Context = ApplicationProvider.getApplicationContext()
idp = InvariantDeviceProfile.INSTANCE[appContext]
context =
object : ActivityContextWrapper(ApplicationProvider.getApplicationContext()) {
override fun getSystemService(name: String): Any? {
if (name == "appwidget") {
return appWidgetManager
}
return super.getSystemService(name)
}
override fun getDeviceProfile(): DeviceProfile {
return idp.getDeviceProfile(applicationContext).copy(applicationContext)
}
}
whenever(iconCacheMock.getTitleNoCache(any<LauncherAppWidgetProviderInfo>()))
.thenReturn("title")
whenever(app.iconCache).thenReturn(iconCacheMock)
whenever(app.context).thenReturn(context)
whenever(app.invariantDeviceProfile).thenReturn(idp)
val widgetToCategoryEntry: Map.Entry<ComponentName, IntSet> =
WidgetSections.getWidgetsToCategory(context).entries.first()
widgetSectionCategory = widgetToCategoryEntry.value.first()
val appAWidgetComponent = widgetToCategoryEntry.key
appAPackage = appAWidgetComponent.packageName
whenever(appWidgetManager.getInstalledProvidersForProfile(any()))
.thenReturn(
listOf(
// First widget from widget sections xml
createAppWidgetProviderInfo(appAWidgetComponent),
// A widget that belongs to same package as the widget from widget sections
// xml, but, because it's not mentioned in xml, it would be included in its
// own package section.
createAppWidgetProviderInfo(
ComponentName.createRelative(appAPackage, APP_A_TEST_WIDGET_NAME)
),
// A widget in different package (none of that app's widgets are in widget
// sections xml)
createAppWidgetProviderInfo(AppBTestWidgetComponent),
)
)
val userCache = spy(UserCache.INSTANCE.get(context))
whenever(userCache.userProfiles).thenReturn(listOf(UserHandle.CURRENT))
underTest = WidgetsModel()
}
@Test
fun widgetsByPackage_treatsWidgetSectionsAsSeparatePackageItems() {
loadWidgets()
val packages: Map<PackageItemInfo, List<WidgetItem>> = underTest.widgetsByPackageItem
// expect 3 package items
// one for the custom section with widget from appA
// one for package section for second widget from appA (that wasn't listed in xml)
// and one for package section for appB
assertThat(packages).hasSize(3)
// Each package item when used as a key is distinct (i.e. even if appA is split into custom
// package and owner package section, each of them is a distinct key). This ensures that
// clicking on a custom widget section doesn't take user to app package section.
val distinctPackageUserKeys =
packages.map { PackageUserKey.fromPackageItemInfo(it.key) }.distinct()
assertThat(distinctPackageUserKeys).hasSize(3)
val customSections = packages.filter { it.key.widgetCategory == widgetSectionCategory }
assertThat(customSections).hasSize(1)
val widgetsInCustomSection = customSections.entries.first().value
assertThat(widgetsInCustomSection).hasSize(1)
val packageSections = packages.filter { it.key.widgetCategory == NO_CATEGORY }
assertThat(packageSections).hasSize(2)
// App A's package section
val appAPackageSection = packageSections.filter { it.key.packageName == appAPackage }
assertThat(appAPackageSection).hasSize(1)
val widgetsInAppASection = appAPackageSection.entries.first().value
assertThat(widgetsInAppASection).hasSize(1)
// App B's package section
val appBPackageSection =
packageSections.filter { it.key.packageName == AppBTestWidgetComponent.packageName }
assertThat(appBPackageSection).hasSize(1)
val widgetsInAppBSection = appBPackageSection.entries.first().value
assertThat(widgetsInAppBSection).hasSize(1)
}
@Test
fun widgetComponentMap_returnsWidgets() {
loadWidgets()
val widgetsByComponentKey: Map<ComponentKey, WidgetItem> = underTest.widgetsByComponentKey
assertThat(widgetsByComponentKey).hasSize(3)
widgetsByComponentKey.forEach { entry ->
assertThat(entry.key).isEqualTo(entry.value as ComponentKey)
}
}
@Test
fun widgets_noData_returnsEmpty() {
// no loadWidgets()
assertThat(underTest.widgetsByComponentKey).isEmpty()
}
@Test
fun getWidgetsByPackageItem_returnsACopyOfMap() {
loadWidgets()
val latch = CountDownLatch(1)
Executors.MODEL_EXECUTOR.execute {
var update = true
// each "widgetsByPackageItem" read returns a different copy of the map held internally.
// Modifying one shouldn't impact another.
for ((_, _) in underTest.widgetsByPackageItem.entries) {
underTest.widgetsByPackageItem.clear()
if (update) { // trigger update
update = false
// Similarly, model could update its code independently while a client is
// iterating on the list.
underTest.update(app, /* packageUser= */ null)
}
}
latch.countDown()
}
if (!latch.await(LOAD_WIDGETS_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
fail("Timed out waiting for test")
}
// No exception
}
@Test
fun updateWidgetFilters_setsFiltersCorrectly() {
val testDefaultWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
whenever(widgetsFilterDataProvider.getDefaultWidgetsFilter())
.thenReturn(testDefaultWidgetFilter)
val testPredicatedWidgetFilter = Predicate<WidgetItem> { w -> w.widgetInfo != null }
whenever(widgetsFilterDataProvider.getPredictedWidgetsFilter())
.thenReturn(testPredicatedWidgetFilter)
underTest.updateWidgetFilters(widgetsFilterDataProvider)
assertThat(underTest.defaultWidgetsFilter).isEqualTo(testDefaultWidgetFilter)
assertThat(underTest.predictedWidgetsFilter).isEqualTo(testPredicatedWidgetFilter)
}
@Test
fun widgetFilters_nullInitially() {
assertThat(underTest.defaultWidgetsFilter).isNull()
assertThat(underTest.predictedWidgetsFilter).isNull()
}
private fun loadWidgets() {
val latch = CountDownLatch(1)
Executors.MODEL_EXECUTOR.execute {
underTest.update(app, /* packageUser= */ null)
latch.countDown()
}
if (!latch.await(LOAD_WIDGETS_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
fail("Timed out waiting widgets to load")
}
}
companion object {
// Another widget within app A
private const val APP_A_TEST_WIDGET_NAME = "MyProvider"
private val AppBTestWidgetComponent: ComponentName =
ComponentName.createRelative("com.test.package", "TestProvider")
private const val LOAD_WIDGETS_TIMEOUT_SECONDS = 2L
}
}