Merge "Add standalone weather view to lockscreen if decoupling isenabled." into tm-qpr-dev

This commit is contained in:
Xiaowen Lei
2023-01-27 16:53:55 +00:00
committed by Android (Google) Code Review
5 changed files with 250 additions and 48 deletions

View File

@@ -88,7 +88,9 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final ClockRegistry.ClockChangeListener mClockChangedListener; private final ClockRegistry.ClockChangeListener mClockChangedListener;
private ViewGroup mStatusArea; private ViewGroup mStatusArea;
// If set will replace keyguard_slice_view
// If the SMARTSPACE flag is set, keyguard_slice_view is replaced by the following views.
private View mWeatherView;
private View mSmartspaceView; private View mSmartspaceView;
private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -192,10 +194,17 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
if (mSmartspaceController.isEnabled()) { if (mSmartspaceController.isEnabled()) {
View ksv = mView.findViewById(R.id.keyguard_slice_view); View ksv = mView.findViewById(R.id.keyguard_slice_view);
int ksvIndex = mStatusArea.indexOfChild(ksv); int viewIndex = mStatusArea.indexOfChild(ksv);
ksv.setVisibility(View.GONE); ksv.setVisibility(View.GONE);
addSmartspaceView(ksvIndex); // TODO(b/261757708): add content observer for the Settings toggle and add/remove
// weather according to the Settings.
if (mSmartspaceController.isDateWeatherDecoupled()) {
addWeatherView(viewIndex);
viewIndex += 1;
}
addSmartspaceView(viewIndex);
} }
mSecureSettings.registerContentObserverForUser( mSecureSettings.registerContentObserverForUser(
@@ -237,6 +246,18 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
} }
} }
private void addWeatherView(int index) {
mWeatherView = mSmartspaceController.buildAndConnectWeatherView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
mStatusArea.addView(mWeatherView, index, lp);
int startPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_start);
int endPadding = getContext().getResources().getDimensionPixelSize(
R.dimen.below_clock_padding_end);
mWeatherView.setPaddingRelative(startPadding, 0, endPadding, 0);
}
private void addSmartspaceView(int index) { private void addSmartspaceView(int index) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView); mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(

View File

@@ -107,6 +107,8 @@ import com.android.wm.shell.bubbles.Bubbles;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.inject.Named;
import dagger.Binds; import dagger.Binds;
import dagger.BindsOptionalOf; import dagger.BindsOptionalOf;
import dagger.Module; import dagger.Module;
@@ -214,6 +216,10 @@ public abstract class SystemUIModule {
@BindsOptionalOf @BindsOptionalOf
abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin(); abstract BcSmartspaceConfigPlugin optionalBcSmartspaceConfigPlugin();
@BindsOptionalOf
@Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
abstract BcSmartspaceDataPlugin optionalWeatherSmartspaceConfigPlugin();
@BindsOptionalOf @BindsOptionalOf
abstract Recents optionalRecents(); abstract Recents optionalRecents();

View File

@@ -18,7 +18,6 @@ package com.android.systemui.smartspace.dagger
import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.smartspace.SmartspacePrecondition import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.filters.LockscreenAndDreamTargetFilter
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
import dagger.Binds import dagger.Binds
import dagger.BindsOptionalOf import dagger.BindsOptionalOf
@@ -34,11 +33,6 @@ abstract class SmartspaceModule {
*/ */
const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin" const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
/**
* The lockscreen smartspace target filter.
*/
const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
/** /**
* The dream smartspace target filter. * The dream smartspace target filter.
*/ */
@@ -48,6 +42,11 @@ abstract class SmartspaceModule {
* The precondition for dream smartspace * The precondition for dream smartspace
*/ */
const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition" const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
/**
* The BcSmartspaceDataPlugin for the standalone weather.
*/
const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
} }
@BindsOptionalOf @BindsOptionalOf
@@ -58,12 +57,6 @@ abstract class SmartspaceModule {
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) @Named(DREAM_SMARTSPACE_DATA_PLUGIN)
abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin? abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
@Binds
@Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
abstract fun provideLockscreenSmartspaceTargetFilter(
filter: LockscreenAndDreamTargetFilter?
): SmartspaceTargetFilter?
@Binds @Binds
@Named(DREAM_SMARTSPACE_PRECONDITION) @Named(DREAM_SMARTSPACE_PRECONDITION)
abstract fun bindSmartspacePrecondition( abstract fun bindSmartspacePrecondition(

View File

@@ -52,6 +52,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker import com.android.systemui.settings.UserTracker
import com.android.systemui.shared.regionsampling.RegionSampler import com.android.systemui.shared.regionsampling.RegionSampler
import com.android.systemui.shared.regionsampling.UpdateColorCallback import com.android.systemui.shared.regionsampling.UpdateColorCallback
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -60,6 +61,7 @@ import com.android.systemui.util.settings.SecureSettings
import java.util.Optional import java.util.Optional
import java.util.concurrent.Executor import java.util.concurrent.Executor
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
/** Controller for managing the smartspace view on the lockscreen */ /** Controller for managing the smartspace view on the lockscreen */
@SysUISingleton @SysUISingleton
@@ -82,6 +84,8 @@ constructor(
@Main private val uiExecutor: Executor, @Main private val uiExecutor: Executor,
@Background private val bgExecutor: Executor, @Background private val bgExecutor: Executor,
@Main private val handler: Handler, @Main private val handler: Handler,
@Named(WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
optionalPlugin: Optional<BcSmartspaceDataPlugin>, optionalPlugin: Optional<BcSmartspaceDataPlugin>,
optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>, optionalConfigPlugin: Optional<BcSmartspaceConfigPlugin>,
) { ) {
@@ -90,6 +94,7 @@ constructor(
} }
private var session: SmartspaceSession? = null private var session: SmartspaceSession? = null
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null) private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null) private val configPlugin: BcSmartspaceConfigPlugin? = optionalConfigPlugin.orElse(null)
@@ -131,6 +136,10 @@ constructor(
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets -> private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread() execution.assertIsMainThread()
// The weather data plugin takes unfiltered targets and performs the filtering internally.
weatherPlugin?.onTargetsAvailable(targets)
val filteredTargets = targets.filter(::filterSmartspaceTarget) val filteredTargets = targets.filter(::filterSmartspaceTarget)
plugin?.onTargetsAvailable(filteredTargets) plugin?.onTargetsAvailable(filteredTargets)
if (!isContentUpdatedOnce) { if (!isContentUpdatedOnce) {
@@ -209,11 +218,37 @@ constructor(
return plugin != null return plugin != null
} }
fun isDateWeatherDecoupled(): Boolean {
execution.assertIsMainThread()
return featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED) &&
weatherPlugin != null
}
private fun updateBypassEnabled() { private fun updateBypassEnabled() {
val bypassEnabled = bypassController.bypassEnabled val bypassEnabled = bypassController.bypassEnabled
smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) } smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
} }
/**
* Constructs the weather view and connects it to the smartspace service.
*/
fun buildAndConnectWeatherView(parent: ViewGroup): View? {
execution.assertIsMainThread()
if (!isEnabled()) {
throw RuntimeException("Cannot build view when not enabled")
}
if (!isDateWeatherDecoupled()) {
throw RuntimeException("Cannot build weather view when not decoupled")
}
val view = buildView(parent, weatherPlugin)
connectSession()
return view
}
/** /**
* Constructs the smartspace view and connects it to the smartspace service. * Constructs the smartspace view and connects it to the smartspace service.
*/ */
@@ -224,17 +259,17 @@ constructor(
throw RuntimeException("Cannot build view when not enabled") throw RuntimeException("Cannot build view when not enabled")
} }
val view = buildView(parent) val view = buildView(parent, plugin, configPlugin)
connectSession() connectSession()
return view return view
} }
fun requestSmartspaceUpdate() { private fun buildView(
session?.requestSmartspaceUpdate() parent: ViewGroup,
} plugin: BcSmartspaceDataPlugin?,
configPlugin: BcSmartspaceConfigPlugin? = null
private fun buildView(parent: ViewGroup): View? { ): View? {
if (plugin == null) { if (plugin == null) {
return null return null
} }
@@ -242,7 +277,7 @@ constructor(
val ssView = plugin.getView(parent) val ssView = plugin.getView(parent)
ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) ssView.setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
ssView.registerDataProvider(plugin) ssView.registerDataProvider(plugin)
ssView.registerConfigProvider(configPlugin) configPlugin?.let { ssView.registerConfigProvider(it) }
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter { ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) { override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
@@ -273,7 +308,8 @@ constructor(
} }
private fun connectSession() { private fun connectSession() {
if (plugin == null || session != null || smartspaceViews.isEmpty()) { if (weatherPlugin == null && plugin == null) return
if (session != null || smartspaceViews.isEmpty()) {
return return
} }
@@ -310,14 +346,20 @@ constructor(
statusBarStateController.addCallback(statusBarStateListener) statusBarStateController.addCallback(statusBarStateListener)
bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener) bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
plugin.registerSmartspaceEventNotifier { weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
e -> session?.notifySmartspaceEvent(e) plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
}
updateBypassEnabled() updateBypassEnabled()
reloadSmartspace() reloadSmartspace()
} }
/**
* Requests the smartspace session for an update.
*/
fun requestSmartspaceUpdate() {
session?.requestSmartspaceUpdate()
}
/** /**
* Disconnects the smartspace view from the smartspace service and cleans up any resources. * Disconnects the smartspace view from the smartspace service and cleans up any resources.
*/ */
@@ -341,9 +383,13 @@ constructor(
bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener) bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
session = null session = null
weatherPlugin?.registerSmartspaceEventNotifier(null)
weatherPlugin?.onTargetsAvailable(emptyList())
plugin?.registerSmartspaceEventNotifier(null) plugin?.registerSmartspaceEventNotifier(null)
plugin?.onTargetsAvailable(emptyList()) plugin?.onTargetsAvailable(emptyList())
Log.d(TAG, "Ending smartspace session for lockscreen")
Log.d(TAG, "Ended smartspace session for lockscreen")
} }
fun addListener(listener: SmartspaceTargetListener) { fun addListener(listener: SmartspaceTargetListener) {
@@ -357,8 +403,11 @@ constructor(
} }
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean { private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
if (isDateWeatherDecoupled()) {
return t.featureType != SmartspaceTarget.FEATURE_WEATHER
}
if (!showNotifications) { if (!showNotifications) {
return t.getFeatureType() == SmartspaceTarget.FEATURE_WEATHER return t.featureType == SmartspaceTarget.FEATURE_WEATHER
} }
return when (t.userHandle) { return when (t.userHandle) {
userTracker.userHandle -> { userTracker.userHandle -> {

View File

@@ -34,6 +34,7 @@ import android.widget.FrameLayout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceConfigPlugin import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin import com.android.systemui.plugins.BcSmartspaceDataPlugin
@@ -111,6 +112,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
@Mock @Mock
private lateinit var handler: Handler private lateinit var handler: Handler
@Mock
private lateinit var weatherPlugin: BcSmartspaceDataPlugin
@Mock @Mock
private lateinit var plugin: BcSmartspaceDataPlugin private lateinit var plugin: BcSmartspaceDataPlugin
@@ -151,6 +155,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
KeyguardBypassController.OnBypassStateChangedListener KeyguardBypassController.OnBypassStateChangedListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private lateinit var weatherSmartspaceView: SmartspaceView
private lateinit var smartspaceView: SmartspaceView private lateinit var smartspaceView: SmartspaceView
private val clock = FakeSystemClock() private val clock = FakeSystemClock()
@@ -176,16 +181,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
// Todo(b/261760571): flip the flag value here when feature is launched, and update relevant
// tests.
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
`when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING)) `when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
.thenReturn(fakePrivateLockscreenSettingUri) .thenReturn(fakePrivateLockscreenSettingUri)
`when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING)) `when`(secureSettings.getUriFor(NOTIF_ON_LOCKSCREEN_SETTING))
.thenReturn(fakeNotifOnLockscreenSettingUri) .thenReturn(fakeNotifOnLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession) `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
`when`(weatherPlugin.getView(any())).thenReturn(
createWeatherSmartspaceView(), createWeatherSmartspaceView())
`when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView()) `when`(plugin.getView(any())).thenReturn(createSmartspaceView(), createSmartspaceView())
`when`(userTracker.userProfiles).thenReturn(userList) `when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f) `when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
`when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
setActiveUser(userHandlePrimary) setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true) setAllowPrivateNotifications(userHandlePrimary, true)
@@ -210,6 +221,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
executor, executor,
bgExecutor, bgExecutor,
handler, handler,
Optional.of(weatherPlugin),
Optional.of(plugin), Optional.of(plugin),
Optional.of(configPlugin), Optional.of(configPlugin),
) )
@@ -218,11 +230,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
deviceProvisionedListener = deviceProvisionedCaptor.value deviceProvisionedListener = deviceProvisionedCaptor.value
} }
@Test(expected = RuntimeException::class)
fun testBuildAndConnectWeatherView_throwsIfDecouplingDisabled() {
// GIVEN the feature flag is disabled
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(false)
// WHEN we try to build the view
controller.buildAndConnectWeatherView(fakeParent)
// THEN an exception is thrown
}
@Test @Test
fun connectOnlyAfterDeviceIsProvisioned() { fun testBuildAndConnectView_connectsOnlyAfterDeviceIsProvisioned() {
// GIVEN an unprovisioned device and an attempt to connect // GIVEN an unprovisioned device and an attempt to connect
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(false) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
`when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(false) `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
// WHEN a connection attempt is made and view is attached // WHEN a connection attempt is made and view is attached
val view = controller.buildAndConnectView(fakeParent) val view = controller.buildAndConnectView(fakeParent)
@@ -232,8 +255,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(smartspaceManager, never()).createSmartspaceSession(any()) verify(smartspaceManager, never()).createSmartspaceSession(any())
// WHEN it does become provisioned // WHEN it does become provisioned
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
`when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
deviceProvisionedListener.onUserSetupChanged() deviceProvisionedListener.onUserSetupChanged()
// THEN the session is created // THEN the session is created
@@ -243,7 +266,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testListenersAreRegistered() { fun testAddListener_registersListenersForPlugin() {
// GIVEN a listener is added after a session is created // GIVEN a listener is added after a session is created
connectSession() connectSession()
@@ -252,10 +275,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
// THEN the listener is registered to the underlying plugin // THEN the listener is registered to the underlying plugin
verify(plugin).registerListener(controllerListener) verify(plugin).registerListener(controllerListener)
// The listener is registered only for the plugin, not the weather plugin.
verify(weatherPlugin, never()).registerListener(any())
} }
@Test @Test
fun testEarlyRegisteredListenersAreAttachedAfterConnected() { fun testAddListener_earlyRegisteredListenersAreAttachedAfterConnected() {
// GIVEN a listener that is registered before the session is created // GIVEN a listener that is registered before the session is created
controller.addListener(controllerListener) controller.addListener(controllerListener)
@@ -264,10 +289,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
// THEN the listener is subsequently registered // THEN the listener is subsequently registered
verify(plugin).registerListener(controllerListener) verify(plugin).registerListener(controllerListener)
// The listener is registered only for the plugin, not the weather plugin.
verify(weatherPlugin, never()).registerListener(any())
} }
@Test @Test
fun testEmptyListIsEmittedAndNotifierRemovedAfterDisconnect() { fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
// GIVEN a registered listener on an active session // GIVEN a registered listener on an active session
connectSession() connectSession()
clearInvocations(plugin) clearInvocations(plugin)
@@ -279,10 +306,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
// THEN the listener receives an empty list of targets and unregisters the notifier // THEN the listener receives an empty list of targets and unregisters the notifier
verify(plugin).onTargetsAvailable(emptyList()) verify(plugin).onTargetsAvailable(emptyList())
verify(plugin).registerSmartspaceEventNotifier(null) verify(plugin).registerSmartspaceEventNotifier(null)
verify(weatherPlugin).onTargetsAvailable(emptyList())
verify(weatherPlugin).registerSmartspaceEventNotifier(null)
} }
@Test @Test
fun testUserChangeReloadsSmartspace() { fun testUserChange_reloadsSmartspace() {
// GIVEN a connected smartspace session // GIVEN a connected smartspace session
connectSession() connectSession()
@@ -294,7 +323,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testSettingsChangeReloadsSmartspace() { fun testSettingsChange_reloadsSmartspace() {
// GIVEN a connected smartspace session // GIVEN a connected smartspace session
connectSession() connectSession()
@@ -306,7 +335,7 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testThemeChangeUpdatesTextColor() { fun testThemeChange_updatesTextColor() {
// GIVEN a connected smartspace session // GIVEN a connected smartspace session
connectSession() connectSession()
@@ -318,7 +347,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testDozeAmountChangeUpdatesView() { fun testThemeChange_ifDecouplingEnabled_updatesTextColor() {
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
// GIVEN a connected smartspace session
connectSession()
// WHEN the theme changes
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
verify(smartspaceView).setPrimaryTextColor(anyInt())
}
@Test
fun testDozeAmountChange_updatesView() {
// GIVEN a connected smartspace session // GIVEN a connected smartspace session
connectSession() connectSession()
@@ -330,7 +374,22 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testKeyguardBypassEnabledUpdatesView() { fun testDozeAmountChange_ifDecouplingEnabled_updatesViews() {
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
// GIVEN a connected smartspace session
connectSession()
// WHEN the doze amount changes
statusBarStateListener.onDozeAmountChanged(0.1f, 0.7f)
// We pass that along to the view
verify(weatherSmartspaceView).setDozeAmount(0.7f)
verify(smartspaceView).setDozeAmount(0.7f)
}
@Test
fun testKeyguardBypassEnabled_updatesView() {
// GIVEN a connected smartspace session // GIVEN a connected smartspace session
connectSession() connectSession()
`when`(keyguardBypassController.bypassEnabled).thenReturn(true) `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
@@ -424,6 +483,27 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
))) )))
} }
@Test
fun testSessionListener_ifDecouplingEnabled_weatherTargetIsFilteredOut() {
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
connectSession()
// WHEN we receive a list of targets
val targets = listOf(
makeTarget(1, userHandlePrimary, isSensitive = true),
makeTarget(2, userHandlePrimary),
makeTarget(3, userHandleManaged),
makeTarget(4, userHandlePrimary, featureType = SmartspaceTarget.FEATURE_WEATHER)
)
sessionListener.onTargetsAvailable(targets)
// THEN all non-sensitive content is still shown
verify(plugin).onTargetsAvailable(eq(listOf(targets[0], targets[1], targets[2])))
// No filtering is applied for the weather plugin
verify(weatherPlugin).onTargetsAvailable(eq(targets))
}
@Test @Test
fun testSettingsAreReloaded() { fun testSettingsAreReloaded() {
// GIVEN a connected session where the privacy settings later flip to false // GIVEN a connected session where the privacy settings later flip to false
@@ -514,6 +594,16 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(smartspaceView2).registerConfigProvider(configPlugin) verify(smartspaceView2).registerConfigProvider(configPlugin)
} }
@Test
fun testWeatherViewUsesSameSession() {
`when`(featureFlags.isEnabled(Flags.SMARTSPACE_DATE_WEATHER_DECOUPLED)).thenReturn(true)
// GIVEN a connected session
connectSession()
// No checks is needed here, since connectSession() already checks internally that
// createSmartspaceSession is invoked only once.
}
@Test @Test
fun testViewGetInitializedWithBypassEnabledState() { fun testViewGetInitializedWithBypassEnabledState() {
// GIVEN keyguard bypass is enabled. // GIVEN keyguard bypass is enabled.
@@ -531,8 +621,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
fun testConnectAttemptBeforeInitializationShouldNotCreateSession() { fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
// GIVEN an uninitalized smartspaceView // GIVEN an uninitalized smartspaceView
// WHEN the device is provisioned // WHEN the device is provisioned
`when`(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true) `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
`when`(deviceProvisionedController.isCurrentUserSetup()).thenReturn(true) `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
deviceProvisionedListener.onDeviceProvisionedChanged() deviceProvisionedListener.onDeviceProvisionedChanged()
// THEN no calls to createSmartspaceSession should occur // THEN no calls to createSmartspaceSession should occur
@@ -542,9 +632,23 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
} }
private fun connectSession() { private fun connectSession() {
if (controller.isDateWeatherDecoupled()) {
val weatherView = controller.buildAndConnectWeatherView(fakeParent)
weatherSmartspaceView = weatherView as SmartspaceView
fakeParent.addView(weatherView)
controller.stateChangeListener.onViewAttachedToWindow(weatherView)
verify(weatherSmartspaceView).setUiSurface(
BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
verify(weatherSmartspaceView).registerDataProvider(weatherPlugin)
verify(weatherSmartspaceView).setPrimaryTextColor(anyInt())
verify(weatherSmartspaceView).setDozeAmount(0.5f)
}
val view = controller.buildAndConnectView(fakeParent) val view = controller.buildAndConnectView(fakeParent)
smartspaceView = view as SmartspaceView smartspaceView = view as SmartspaceView
fakeParent.addView(view)
controller.stateChangeListener.onViewAttachedToWindow(view) controller.stateChangeListener.onViewAttachedToWindow(view)
verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD) verify(smartspaceView).setUiSurface(BcSmartspaceDataPlugin.UI_SURFACE_LOCK_SCREEN_AOD)
@@ -554,6 +658,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
.addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor)) .addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
sessionListener = sessionListenerCaptor.value sessionListener = sessionListenerCaptor.value
verify(smartspaceManager).createSmartspaceSession(any())
verify(userTracker).addCallback(capture(userTrackerCaptor), any()) verify(userTracker).addCallback(capture(userTrackerCaptor), any())
userListener = userTrackerCaptor.value userListener = userTrackerCaptor.value
@@ -578,9 +684,11 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(smartspaceView).setPrimaryTextColor(anyInt()) verify(smartspaceView).setPrimaryTextColor(anyInt())
verify(smartspaceView).setDozeAmount(0.5f) verify(smartspaceView).setDozeAmount(0.5f)
clearInvocations(view)
fakeParent.addView(view) if (controller.isDateWeatherDecoupled()) {
clearInvocations(weatherSmartspaceView)
}
clearInvocations(smartspaceView)
} }
private fun setActiveUser(userHandle: UserHandle) { private fun setActiveUser(userHandle: UserHandle) {
@@ -626,6 +734,31 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
).thenReturn(if (value) 1 else 0) ).thenReturn(if (value) 1 else 0)
} }
// Separate function for the weather view, which doesn't implement all functions in interface.
private fun createWeatherSmartspaceView(): SmartspaceView {
return spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
}
override fun setPrimaryTextColor(color: Int) {
}
override fun setIsDreaming(isDreaming: Boolean) {
}
override fun setUiSurface(uiSurface: String) {
}
override fun setDozeAmount(amount: Float) {
}
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
}
override fun setFalsingManager(falsingManager: FalsingManager?) {
}
})
}
private fun createSmartspaceView(): SmartspaceView { private fun createSmartspaceView(): SmartspaceView {
return spy(object : View(context), SmartspaceView { return spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) { override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {