blob: c190fbbbf71817c48efd6adcfd11b2f23b0326d9 [file] [log] [blame]
/*
* Copyright 2021 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 androidx.core.widget
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT
import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH
import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT
import android.appwidget.AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH
import android.appwidget.AppWidgetProviderInfo
import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.os.Bundle
import android.widget.FrameLayout
import android.widget.RemoteViews
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.core.remoteviews.test.R
import androidx.core.util.SizeFCompat
import androidx.core.util.component1
import androidx.core.util.component2
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertFailsWith
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
import org.robolectric.annotation.internal.DoNotInstrument
import org.robolectric.util.ReflectionHelpers
@RunWith(RobolectricTestRunner::class)
@DoNotInstrument
@Config(sdk = [17])
@RequiresApi(17)
class AppWidgetManagerCompatTest {
private val context = ApplicationProvider.getApplicationContext<Context>()
private val packageName = context.packageName
private val appWidgetManager = AppWidgetManager.getInstance(context)
private val appWidgetId = 1
private val landscapeContext = configurationContext { orientation = ORIENTATION_LANDSCAPE }
private val portraitContext = configurationContext { orientation = ORIENTATION_PORTRAIT }
@Test
fun exact_oneSize_shouldUpdateRemoteViewsWithoutLandPort() {
setWidgetSize(100 x 200)
val rv = createExactSizeAppWidget(appWidgetManager, appWidgetId) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
assertThat(rv.landscape).isNull()
assertThat(rv.portrait).isNull()
}
@Test
fun exact_multipleSizes_shouldUpdateRemoteViewsWithLandPort() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
val rv = createExactSizeAppWidget(appWidgetManager, appWidgetId) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
assertThat(rv.landscape).isNotNull()
assertThat(rv.portrait).isNotNull()
}
@Test
fun exact_multipleSizes_shouldUseLandscapeSizeForLandscapeView() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
val rv = createExactSizeAppWidget(appWidgetManager, appWidgetId) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
val view = rv.apply(landscapeContext, FrameLayout(landscapeContext))
assertThat(view.findViewById<TextView>(R.id.text).text.toString())
.isEqualTo("200.0 x 100.0")
}
@Test
fun exact_multipleSizes_shouldUsePortraitSizeForPortraitView() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
val rv = createExactSizeAppWidget(appWidgetManager, appWidgetId) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
val view = rv.apply(portraitContext, FrameLayout(portraitContext))
assertThat(view.findViewById<TextView>(R.id.text).text.toString())
.isEqualTo("110.0 x 190.0")
}
@Test
fun exact_invalidWidget_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
createExactSizeAppWidget(appWidgetManager, appWidgetId) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
}
}
@Test
fun responsive_singleSizeFromLauncher_shouldChooseBestFitAsSingleRemoteViews() {
setWidgetSize(100 x 200)
val rv = createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
listOf(50 x 50, 75 x 75, 120 x 140)
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
assertThat(rv.landscape).isNull()
assertThat(rv.portrait).isNull()
val view = rv.apply(context, FrameLayout(context))
assertThat(view.findViewById<TextView>(R.id.text).text.toString())
.isEqualTo("75.0 x 75.0")
}
@Test
fun responsive_multipleSizesFromLauncher_bestFitIsSameForBoth_shouldChooseBestFitAsSingle() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
val rv = createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
listOf(50 x 50, 75 x 75, 120 x 140)
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
assertThat(rv.landscape).isNull()
assertThat(rv.portrait).isNull()
val view = rv.apply(context, FrameLayout(context))
assertThat(view.findViewById<TextView>(R.id.text).text.toString()).isEqualTo("75.0 x 75.0")
}
@Test
fun responsive_multipleSizesFromLauncher_bestFitIsDifferent_shouldChooseBestFitsAsLandPort() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
val rv = createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
listOf(50 x 50, 75 x 75, 120 x 100)
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
assertThat(rv.landscape).isNotNull()
assertThat(rv.portrait).isNotNull()
val landView = rv.apply(landscapeContext, FrameLayout(landscapeContext))
assertThat(landView.findViewById<TextView>(R.id.text).text.toString())
.isEqualTo("120.0 x 100.0")
val portView = rv.apply(portraitContext, FrameLayout(portraitContext))
assertThat(portView.findViewById<TextView>(R.id.text).text.toString())
.isEqualTo("75.0 x 75.0")
}
@Test
fun responsive_noSizes_shouldThrow() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
assertFailsWith<IllegalArgumentException> {
createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
emptyList()
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
}
}
@Test
fun responsive_tooManySizes_shouldThrow() {
setWidgetSize(landscape = 200 x 100, portrait = 110 x 190)
assertFailsWith<IllegalArgumentException> {
createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
(1..17).map { it x it }
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
}
}
@Test
fun responsive_invalidWidget_shouldThrow() {
assertFailsWith<IllegalArgumentException> {
createResponsiveSizeAppWidget(
appWidgetManager,
appWidgetId,
listOf(100 x 100)
) { (width, height) ->
remoteViews {
setTextViewText(R.id.text, "$width x $height")
}
}
}
}
private fun setWidgetSize(landscape: SizeFCompat, portrait: SizeFCompat? = null) {
shadowOf(appWidgetManager).addBoundWidget(appWidgetId, AppWidgetProviderInfo())
appWidgetManager.updateAppWidgetOptions(
appWidgetId,
Bundle().apply {
putInt(OPTION_APPWIDGET_MAX_WIDTH, landscape.width.toInt())
putInt(OPTION_APPWIDGET_MIN_HEIGHT, landscape.height.toInt())
putInt(
OPTION_APPWIDGET_MIN_WIDTH,
portrait?.width?.toInt() ?: landscape.width.toInt()
)
putInt(
OPTION_APPWIDGET_MAX_HEIGHT,
portrait?.height?.toInt() ?: landscape.height.toInt()
)
}
)
}
private infix fun Int.x(other: Int) = SizeFCompat(this.toFloat(), other.toFloat())
private fun remoteViews(modifier: RemoteViews.() -> Unit = {}): RemoteViews {
return RemoteViews(packageName, R.layout.remote_views).apply(modifier)
}
private val RemoteViews.landscape: RemoteViews?
get() = ReflectionHelpers.getField(this, "mLandscape")
private val RemoteViews.portrait: RemoteViews?
get() = ReflectionHelpers.getField(this, "mPortrait")
@RequiresApi(17)
private fun configurationContext(modifier: Configuration.() -> Unit): Context {
val configuration = Configuration()
configuration.apply(modifier)
return context.createConfigurationContext(configuration)
}
}