diff options
8 files changed, 361 insertions, 109 deletions
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 1c2f38beb867..ab4aca569bd4 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -113,7 +113,7 @@ interface ClockEvents { fun onColorPaletteChanged(resources: Resources) {} /** Call whenever the weather data should update */ - fun onWeatherDataChanged(data: Weather) {} + fun onWeatherDataChanged(data: WeatherData) {} } /** Methods which trigger various clock animations */ diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt deleted file mode 100644 index 302f17556bc7..000000000000 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/Weather.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.android.systemui.plugins - -import android.os.Bundle - -class Weather(val conditions: WeatherStateIcon, val temperature: Int, val isCelsius: Boolean) { - companion object { - private const val TAG = "Weather" - private const val WEATHER_STATE_ICON_KEY = "weather_state_icon_extra_key" - private const val TEMPERATURE_VALUE_KEY = "temperature_value_extra_key" - private const val TEMPERATURE_UNIT_KEY = "temperature_unit_extra_key" - private const val INVALID_TEMPERATURE = Int.MIN_VALUE - - fun fromBundle(extras: Bundle): Weather? { - val icon = - WeatherStateIcon.fromInt( - extras.getInt(WEATHER_STATE_ICON_KEY, WeatherStateIcon.UNKNOWN_ICON.id) - ) - if (icon == null || icon == WeatherStateIcon.UNKNOWN_ICON) { - return null - } - val temperature = extras.getInt(TEMPERATURE_VALUE_KEY, INVALID_TEMPERATURE) - if (temperature == INVALID_TEMPERATURE) { - return null - } - return Weather(icon, temperature, extras.getBoolean(TEMPERATURE_UNIT_KEY)) - } - } - - enum class WeatherStateIcon(val id: Int) { - UNKNOWN_ICON(0), - - // Clear, day & night. - SUNNY(1), - CLEAR_NIGHT(2), - - // Mostly clear, day & night. - MOSTLY_SUNNY(3), - MOSTLY_CLEAR_NIGHT(4), - - // Partly cloudy, day & night. - PARTLY_CLOUDY(5), - PARTLY_CLOUDY_NIGHT(6), - - // Mostly cloudy, day & night. - MOSTLY_CLOUDY_DAY(7), - MOSTLY_CLOUDY_NIGHT(8), - CLOUDY(9), - HAZE_FOG_DUST_SMOKE(10), - DRIZZLE(11), - HEAVY_RAIN(12), - SHOWERS_RAIN(13), - - // Scattered showers, day & night. - SCATTERED_SHOWERS_DAY(14), - SCATTERED_SHOWERS_NIGHT(15), - - // Isolated scattered thunderstorms, day & night. - ISOLATED_SCATTERED_TSTORMS_DAY(16), - ISOLATED_SCATTERED_TSTORMS_NIGHT(17), - STRONG_TSTORMS(18), - BLIZZARD(19), - BLOWING_SNOW(20), - FLURRIES(21), - HEAVY_SNOW(22), - - // Scattered snow showers, day & night. - SCATTERED_SNOW_SHOWERS_DAY(23), - SCATTERED_SNOW_SHOWERS_NIGHT(24), - SNOW_SHOWERS_SNOW(25), - MIXED_RAIN_HAIL_RAIN_SLEET(26), - SLEET_HAIL(27), - TORNADO(28), - TROPICAL_STORM_HURRICANE(29), - WINDY_BREEZY(30), - WINTRY_MIX_RAIN_SNOW(31); - - companion object { - fun fromInt(value: Int) = values().firstOrNull { it.id == value } - } - } - - override fun toString(): String { - return "$conditions $temperature${if (isCelsius) "C" else "F"}" - } -} diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt new file mode 100644 index 000000000000..52dfc55c105d --- /dev/null +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -0,0 +1,107 @@ +package com.android.systemui.plugins + +import android.os.Bundle +import androidx.annotation.VisibleForTesting + +class WeatherData +private constructor( + val description: String, + val state: WeatherStateIcon, + val useCelsius: Boolean, + val temperature: Int, +) { + companion object { + private const val TAG = "WeatherData" + @VisibleForTesting const val DESCRIPTION_KEY = "description" + @VisibleForTesting const val STATE_KEY = "state" + @VisibleForTesting const val USE_CELSIUS_KEY = "use_celsius" + @VisibleForTesting const val TEMPERATURE_KEY = "temperature" + private const val INVALID_WEATHER_ICON_STATE = -1 + + fun fromBundle(extras: Bundle): WeatherData? { + val description = extras.getString(DESCRIPTION_KEY) + val state = + WeatherStateIcon.fromInt(extras.getInt(STATE_KEY, INVALID_WEATHER_ICON_STATE)) + val temperature = readIntFromBundle(extras, TEMPERATURE_KEY) + return if ( + description == null || + state == null || + !extras.containsKey(USE_CELSIUS_KEY) || + temperature == null + ) + null + else + WeatherData( + description = description, + state = state, + useCelsius = extras.getBoolean(USE_CELSIUS_KEY), + temperature = temperature + ) + } + + private fun readIntFromBundle(extras: Bundle, key: String): Int? = + try { + extras.getString(key).toInt() + } catch (e: Exception) { + null + } + } + + enum class WeatherStateIcon(val id: Int) { + UNKNOWN_ICON(0), + + // Clear, day & night. + SUNNY(1), + CLEAR_NIGHT(2), + + // Mostly clear, day & night. + MOSTLY_SUNNY(3), + MOSTLY_CLEAR_NIGHT(4), + + // Partly cloudy, day & night. + PARTLY_CLOUDY(5), + PARTLY_CLOUDY_NIGHT(6), + + // Mostly cloudy, day & night. + MOSTLY_CLOUDY_DAY(7), + MOSTLY_CLOUDY_NIGHT(8), + CLOUDY(9), + HAZE_FOG_DUST_SMOKE(10), + DRIZZLE(11), + HEAVY_RAIN(12), + SHOWERS_RAIN(13), + + // Scattered showers, day & night. + SCATTERED_SHOWERS_DAY(14), + SCATTERED_SHOWERS_NIGHT(15), + + // Isolated scattered thunderstorms, day & night. + ISOLATED_SCATTERED_TSTORMS_DAY(16), + ISOLATED_SCATTERED_TSTORMS_NIGHT(17), + STRONG_TSTORMS(18), + BLIZZARD(19), + BLOWING_SNOW(20), + FLURRIES(21), + HEAVY_SNOW(22), + + // Scattered snow showers, day & night. + SCATTERED_SNOW_SHOWERS_DAY(23), + SCATTERED_SNOW_SHOWERS_NIGHT(24), + SNOW_SHOWERS_SNOW(25), + MIXED_RAIN_HAIL_RAIN_SLEET(26), + SLEET_HAIL(27), + TORNADO(28), + TROPICAL_STORM_HURRICANE(29), + WINDY_BREEZY(30), + WINTRY_MIX_RAIN_SNOW(31); + + companion object { + fun fromInt(value: Int) = values().firstOrNull { it.id == value } + } + } + + override fun toString(): String { + val unit = if (useCelsius) "C" else "F" + return "$state (\"$description\") $temperature°$unit" + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 1254e1ee3311..92ee37310130 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -48,7 +48,7 @@ import com.android.systemui.plugins.ClockTickRate import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel.DEBUG import com.android.systemui.shared.regionsampling.RegionSampler -import com.android.systemui.plugins.Weather +import com.android.systemui.plugins.WeatherData import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback import com.android.systemui.statusbar.policy.ConfigurationController @@ -291,10 +291,8 @@ constructor( clock?.events?.onTimeFormatChanged(DateFormat.is24HourFormat(context)) } - override fun onWeatherDataChanged(data: Weather?) { - if (data != null) { - clock?.events?.onWeatherDataChanged(data) - } + override fun onWeatherDataChanged(data: WeatherData) { + clock?.events?.onWeatherDataChanged(data) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index be9264dbfcf3..d1bd486a4ede 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -148,7 +148,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; import com.android.systemui.log.SessionTracker; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.system.TaskStackChangeListener; @@ -3285,12 +3285,12 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @param data the weather data (temp, conditions, unit) for weather clock to use */ - public void sendWeatherData(Weather data) { + public void sendWeatherData(WeatherData data) { mHandler.post(()-> { handleWeatherDataUpdate(data); }); } - private void handleWeatherDataUpdate(Weather data) { + private void handleWeatherDataUpdate(WeatherData data) { Assert.isMainThread(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 0da799e0964d..38f3e5065eec 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -23,7 +23,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.settingslib.fuelgauge.BatteryStatus; -import com.android.systemui.plugins.Weather; +import com.android.systemui.plugins.WeatherData; import com.android.systemui.statusbar.KeyguardIndicationController; import java.util.TimeZone; @@ -61,7 +61,7 @@ public class KeyguardUpdateMonitorCallback { /** * Called when receive new weather data. */ - public void onWeatherDataChanged(Weather data) { } + public void onWeatherDataChanged(WeatherData data) { } /** * Called when the carrier PLMN or SPN changes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt index 6ef6165bcbb3..29510d00a8d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt @@ -52,18 +52,19 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.UpdateColorCallback import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DATE_SMARTSPACE_DATA_PLUGIN import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN -import com.android.systemui.plugins.Weather import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.util.concurrency.Execution import com.android.systemui.util.settings.SecureSettings +import com.android.systemui.util.time.SystemClock import java.io.PrintWriter import java.time.Instant import java.util.Optional @@ -81,6 +82,7 @@ constructor( private val smartspaceManager: SmartspaceManager, private val activityStarter: ActivityStarter, private val falsingManager: FalsingManager, + private val systemClock: SystemClock, private val secureSettings: SecureSettings, private val userTracker: UserTracker, private val contentResolver: ContentResolver, @@ -152,6 +154,18 @@ constructor( // The weather data plugin takes unfiltered targets and performs the filtering internally. weatherPlugin?.onTargetsAvailable(targets) + val now = Instant.ofEpochMilli(systemClock.currentTimeMillis()) + val weatherTarget = targets.find { t -> + t.featureType == SmartspaceTarget.FEATURE_WEATHER && + now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && + now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) + } + if (weatherTarget != null) { + val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras) + if (weatherData != null) { + keyguardUpdateMonitor.sendWeatherData(weatherData) + } + } val filteredTargets = targets.filter(::filterSmartspaceTarget) plugin?.onTargetsAvailable(filteredTargets) @@ -173,17 +187,6 @@ constructor( } isContentUpdatedOnce = true } - - val now = Instant.now() - val weatherTarget = targets.find { t -> - t.featureType == SmartspaceTarget.FEATURE_WEATHER && - now.isAfter(Instant.ofEpochMilli(t.creationTimeMillis)) && - now.isBefore(Instant.ofEpochMilli(t.expiryTimeMillis)) - } - if (weatherTarget != null) { - val weatherData = Weather.fromBundle(weatherTarget.baseAction.extras) - keyguardUpdateMonitor.sendWeatherData(weatherData) - } } private val userTrackerCallback = object : UserTracker.Callback { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt index d6225c6748f3..8fa0d652705a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.lockscreen +import android.app.smartspace.SmartspaceAction import android.app.smartspace.SmartspaceManager import android.app.smartspace.SmartspaceSession import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener @@ -26,6 +27,7 @@ import android.content.pm.UserInfo import android.database.ContentObserver import android.graphics.drawable.Drawable import android.net.Uri +import android.os.Bundle import android.os.Handler import android.os.UserHandle import android.provider.Settings @@ -43,6 +45,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView import com.android.systemui.plugins.FalsingManager +import com.android.systemui.plugins.WeatherData import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener import com.android.systemui.settings.UserTracker @@ -54,6 +57,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceP import com.android.systemui.util.concurrency.FakeExecution import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argThat import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.SecureSettings @@ -69,6 +73,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.spy +import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import java.util.Optional @@ -76,6 +81,13 @@ import java.util.concurrent.Executor @SmallTest class LockscreenSmartspaceControllerTest : SysuiTestCase() { + companion object { + const val SMARTSPACE_TIME_TOO_EARLY = 1000L + const val SMARTSPACE_TIME_JUST_RIGHT = 4000L + const val SMARTSPACE_TIME_TOO_LATE = 9000L + const val SMARTSPACE_CREATION_TIME = 1234L + const val SMARTSPACE_EXPIRY_TIME = 5678L + } @Mock private lateinit var featureFlags: FeatureFlags @Mock @@ -224,6 +236,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { smartspaceManager, activityStarter, falsingManager, + clock, secureSettings, userTracker, contentResolver, @@ -529,6 +542,190 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @Test + fun testSessionListener_ifWeatherExtraMissing_thenWeatherDataNotSent() { + connectSession() + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER) + + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifWeatherExtraIsMissingValues_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandlePrimary, + description = null, + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = null) + + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifTooEarly_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_EARLY) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_ifOnTime_thenWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Snow Showers", + state = WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW.id, + temperature = "-1", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Snow Showers" && + w.state == WeatherData.WeatherStateIcon.SNOW_SHOWERS_SNOW && + w.temperature == -1 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifTooLate_thenWeatherDataNotSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_TOO_LATE) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor, times(0)).sendWeatherData(any()) + } + + @Test + fun testSessionListener_onlyFirstWeatherDataSent() { + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandleManaged, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "72", + useCelsius = false), + makeWeatherTargetWithExtras( + id = 2, + userHandle = userHandleManaged, + description = "Showers", + state = WeatherData.WeatherStateIcon.SHOWERS_RAIN.id, + temperature = "62", + useCelsius = true) + ) + sessionListener.onTargetsAvailable(targets) + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 72 && !w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingEnabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeTarget(1, userHandlePrimary, isSensitive = true), + makeTarget(2, userHandlePrimary), + makeTarget(3, userHandleManaged), + makeWeatherTargetWithExtras( + id = 4, + userHandle = userHandlePrimary, + description = "Flurries", + state = WeatherData.WeatherStateIcon.FLURRIES.id, + temperature = "0", + useCelsius = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Flurries" && + w.state == WeatherData.WeatherStateIcon.FLURRIES && + w.temperature == 0 && w.useCelsius + }) + } + + @Test + fun testSessionListener_ifDecouplingDisabled_weatherDataUpdates() { + `when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false) + connectSession() + + clock.setCurrentTimeMillis(SMARTSPACE_TIME_JUST_RIGHT) + // WHEN we receive a list of targets + val targets = listOf( + makeWeatherTargetWithExtras( + id = 1, + userHandle = userHandlePrimary, + description = "Sunny", + state = WeatherData.WeatherStateIcon.SUNNY.id, + temperature = "32", + useCelsius = false), + makeTarget(2, userHandlePrimary, isSensitive = true) + ) + + sessionListener.onTargetsAvailable(targets) + + verify(keyguardUpdateMonitor).sendWeatherData(argThat { w -> + w.description == "Sunny" && + w.state == WeatherData.WeatherStateIcon.SUNNY && + w.temperature == 32 && !w.useCelsius + }) + } + + @Test fun testSettingsAreReloaded() { // GIVEN a connected session where the privacy settings later flip to false connectSession() @@ -740,7 +937,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { return userInfo } - fun makeTarget( + private fun makeTarget( id: Int, userHandle: UserHandle, isSensitive: Boolean = false, @@ -755,6 +952,38 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { .build() } + private fun makeWeatherTargetWithExtras( + id: Int, + userHandle: UserHandle, + description: String?, + state: Int?, + temperature: String?, + useCelsius: Boolean? + ): SmartspaceTarget { + val mockWeatherBundle = mock(Bundle::class.java).apply { + `when`(getString(WeatherData.DESCRIPTION_KEY)).thenReturn(description) + if (state != null) + `when`(getInt(eq(WeatherData.STATE_KEY), any())).thenReturn(state) + `when`(getString(WeatherData.TEMPERATURE_KEY)).thenReturn(temperature) + `when`(containsKey(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius != null) + if (useCelsius != null) + `when`(getBoolean(WeatherData.USE_CELSIUS_KEY)).thenReturn(useCelsius) + } + + val mockBaseAction = mock(SmartspaceAction::class.java) + `when`(mockBaseAction.extras).thenReturn(mockWeatherBundle) + return SmartspaceTarget.Builder( + "targetWithWeatherExtras$id", + ComponentName("testpackage", "testclass$id"), + userHandle) + .setSensitive(false) + .setFeatureType(SmartspaceTarget.FEATURE_WEATHER) + .setBaseAction(mockBaseAction) + .setExpiryTimeMillis(SMARTSPACE_EXPIRY_TIME) + .setCreationTimeMillis(SMARTSPACE_CREATION_TIME) + .build() + } + private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) { `when`(secureSettings.getIntForUser( eq(PRIVATE_LOCKSCREEN_SETTING), |