ea078cb647
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
262 lines
10 KiB
Kotlin
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
|
|
}
|
|
}
|