Spin off smartspace behavior into SmartspaceController

Spins off the logic for manging the smartspace view into
LockscreenSmartspaceController. In addition to just cleaning things up,
this allows us to expose the current contents of the smartspace to any
listeners that might be interested (there's an interested listener in
the following CL).

Bug: 186481467
Test: atest
Change-Id: I117afdc984215128ec47dfd3190d1685757c0f69
This commit is contained in:
Ned Burns
2021-04-27 00:08:48 -04:00
parent ee5ca902e1
commit 7db37a067e
5 changed files with 866 additions and 393 deletions

View File

@@ -19,42 +19,22 @@ package com.android.keyguard;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import android.app.PendingIntent;
import android.app.WallpaperManager;
import android.app.smartspace.SmartspaceConfig;
import android.app.smartspace.SmartspaceManager;
import android.app.smartspace.SmartspaceSession;
import android.app.smartspace.SmartspaceTarget;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.plugins.BcSmartspaceDataPlugin.IntentStarter;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -62,14 +42,10 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.SecureSettings;
import java.util.Locale;
import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -85,9 +61,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private final KeyguardSliceViewController mKeyguardSliceViewController;
private final NotificationIconAreaController mNotificationIconAreaController;
private final BroadcastDispatcher mBroadcastDispatcher;
private final Executor mUiExecutor;
private final BatteryController mBatteryController;
private final FeatureFlags mFeatureFlags;
private final LockscreenSmartspaceController mSmartspaceController;
/**
* Clock for both small and large sizes
@@ -97,20 +72,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private AnimatableClockController mLargeClockViewController;
private FrameLayout mLargeClockFrame;
private SmartspaceSession mSmartspaceSession;
private SmartspaceSession.OnTargetsAvailableListener mSmartspaceCallback;
private ConfigurationController mConfigurationController;
private ActivityStarter mActivityStarter;
private FalsingManager mFalsingManager;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final KeyguardBypassController mBypassController;
private Handler mHandler;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
private ContentObserver mSettingsObserver;
private boolean mShowSensitiveContentForCurrentUser;
private boolean mShowSensitiveContentForManagedUser;
private UserHandle mManagedUserHandle;
/**
* Listener for changes to the color palette.
@@ -118,59 +81,30 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
* The color palette changes when the wallpaper is changed.
*/
private final ColorExtractor.OnColorsChangedListener mColorsListener =
new ColorExtractor.OnColorsChangedListener() {
@Override
public void onColorsChanged(ColorExtractor extractor, int which) {
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
mView.updateColors(getGradientColors());
}
}
};
private final ConfigurationController.ConfigurationListener mConfigurationListener =
new ConfigurationController.ConfigurationListener() {
@Override
public void onThemeChanged() {
updateWallpaperColor();
}
};
private ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
private final StatusBarStateController.StateListener mStatusBarStateListener =
new StatusBarStateController.StateListener() {
@Override
public void onDozeAmountChanged(float linear, float eased) {
if (mSmartspaceView != null) {
mSmartspaceView.setDozeAmount(eased);
}
(extractor, which) -> {
if ((which & WallpaperManager.FLAG_LOCK) != 0) {
mView.updateColors(getGradientColors());
}
};
private final ClockManager.ClockChangedListener mClockChangedListener = this::setClockPlugin;
// If set, will replace keyguard_status_area
private BcSmartspaceDataPlugin.SmartspaceView mSmartspaceView;
private Optional<BcSmartspaceDataPlugin> mSmartspacePlugin;
private View mSmartspaceView;
@Inject
public KeyguardClockSwitchController(
KeyguardClockSwitch keyguardClockSwitch,
StatusBarStateController statusBarStateController,
SysuiColorExtractor colorExtractor, ClockManager clockManager,
SysuiColorExtractor colorExtractor,
ClockManager clockManager,
KeyguardSliceViewController keyguardSliceViewController,
NotificationIconAreaController notificationIconAreaController,
BroadcastDispatcher broadcastDispatcher,
FeatureFlags featureFlags,
@Main Executor uiExecutor,
BatteryController batteryController,
ConfigurationController configurationController,
ActivityStarter activityStarter,
FalsingManager falsingManager,
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardBypassController bypassController,
@Main Handler handler,
UserTracker userTracker,
SecureSettings secureSettings,
Optional<BcSmartspaceDataPlugin> smartspacePlugin) {
LockscreenSmartspaceController smartspaceController) {
super(keyguardClockSwitch);
mStatusBarStateController = statusBarStateController;
mColorExtractor = colorExtractor;
@@ -178,18 +112,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mKeyguardSliceViewController = keyguardSliceViewController;
mNotificationIconAreaController = notificationIconAreaController;
mBroadcastDispatcher = broadcastDispatcher;
mFeatureFlags = featureFlags;
mUiExecutor = uiExecutor;
mBatteryController = batteryController;
mConfigurationController = configurationController;
mActivityStarter = activityStarter;
mFalsingManager = falsingManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBypassController = bypassController;
mHandler = handler;
mUserTracker = userTracker;
mSecureSettings = secureSettings;
mSmartspacePlugin = smartspacePlugin;
mSmartspaceController = smartspaceController;
}
/**
@@ -232,119 +158,33 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mBypassController);
mLargeClockViewController.init();
mStatusBarStateController.addCallback(mStatusBarStateListener);
mConfigurationController.addCallback(mConfigurationListener);
if (mSmartspaceController.isEnabled()) {
mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
if (mFeatureFlags.isSmartspaceEnabled() && mSmartspacePlugin.isPresent()) {
BcSmartspaceDataPlugin smartspaceDataPlugin = mSmartspacePlugin.get();
View ksa = mView.findViewById(R.id.keyguard_status_area);
int ksaIndex = mView.indexOfChild(ksa);
ksa.setVisibility(View.GONE);
mSmartspaceView = smartspaceDataPlugin.getView(mView);
mSmartspaceView.registerDataProvider(smartspaceDataPlugin);
mSmartspaceView.setIntentStarter(new IntentStarter() {
public void startIntent(View v, Intent i) {
mActivityStarter.startActivity(i, true /* dismissShade */);
}
public void startPendingIntent(PendingIntent pi) {
mActivityStarter.startPendingIntentDismissingKeyguard(pi);
}
});
mSmartspaceView.setFalsingManager(mFalsingManager);
updateWallpaperColor();
View asView = (View) mSmartspaceView;
// Place smartspace view below normal clock...
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
MATCH_PARENT, WRAP_CONTENT);
lp.addRule(RelativeLayout.BELOW, R.id.lockscreen_clock_view);
mView.addView(asView, ksaIndex, lp);
mView.addView(mSmartspaceView, ksaIndex, lp);
int padding = getContext().getResources()
.getDimensionPixelSize(R.dimen.below_clock_padding_start);
asView.setPadding(padding, 0, padding, 0);
mSmartspaceView.setPadding(padding, 0, padding, 0);
// ... but above the large clock
lp = new RelativeLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT);
lp.addRule(RelativeLayout.BELOW, asView.getId());
lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
mLargeClockFrame.setLayoutParams(lp);
View nic = mView.findViewById(
R.id.left_aligned_notification_icon_container);
lp = (RelativeLayout.LayoutParams) nic.getLayoutParams();
lp.addRule(RelativeLayout.BELOW, asView.getId());
lp.addRule(RelativeLayout.BELOW, mSmartspaceView.getId());
nic.setLayoutParams(lp);
mSmartspaceSession = getContext().getSystemService(SmartspaceManager.class)
.createSmartspaceSession(
new SmartspaceConfig.Builder(getContext(), "lockscreen").build());
mSmartspaceCallback = targets -> {
targets.removeIf(this::filterSmartspaceTarget);
smartspaceDataPlugin.onTargetsAvailable(targets);
};
mSmartspaceSession.addOnTargetsAvailableListener(mUiExecutor, mSmartspaceCallback);
mSettingsObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
reloadSmartspace();
}
};
getContext().getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
true, mSettingsObserver, UserHandle.USER_ALL);
reloadSmartspace();
}
float dozeAmount = mStatusBarStateController.getDozeAmount();
mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
}
@VisibleForTesting
boolean filterSmartspaceTarget(SmartspaceTarget t) {
if (!t.isSensitive()) return false;
if (t.getUserHandle().equals(mUserTracker.getUserHandle())) {
return !mShowSensitiveContentForCurrentUser;
}
if (t.getUserHandle().equals(mManagedUserHandle)) {
return !mShowSensitiveContentForManagedUser;
}
return false;
}
private void reloadSmartspace() {
mManagedUserHandle = getWorkProfileUser();
final String setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS;
mShowSensitiveContentForCurrentUser =
mSecureSettings.getIntForUser(setting, 0, mUserTracker.getUserId()) == 1;
if (mManagedUserHandle != null) {
int id = mManagedUserHandle.getIdentifier();
mShowSensitiveContentForManagedUser =
mSecureSettings.getIntForUser(setting, 0, id) == 1;
}
mSmartspaceSession.requestSmartspaceUpdate();
}
private UserHandle getWorkProfileUser() {
for (UserInfo userInfo : mUserTracker.getUserProfiles()) {
if (userInfo.isManagedProfile()) {
return userInfo.getUserHandle();
}
}
return null;
}
private void updateWallpaperColor() {
if (mSmartspaceView != null) {
int color = Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColor);
mSmartspaceView.setPrimaryTextColor(color);
}
}
@@ -356,16 +196,16 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
mColorExtractor.removeOnColorsChangedListener(mColorsListener);
mView.setClockPlugin(null, mStatusBarStateController.getState());
if (mSmartspaceSession != null) {
mSmartspaceSession.removeOnTargetsAvailableListener(mSmartspaceCallback);
mSmartspaceSession.close();
mSmartspaceSession = null;
}
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mSmartspaceController.disconnect();
if (mSettingsObserver != null) {
getContext().getContentResolver().unregisterContentObserver(mSettingsObserver);
// TODO: This is an unfortunate necessity since smartspace plugin retains a single instance
// of the smartspace view -- if we don't remove the view, it can't be reused by a later
// instance of this class. In order to fix this, we need to modify the plugin so that
// (a) we get a new view each time and (b) we can properly clean up an old view by making
// it unregister itself as a plugin listener.
if (mSmartspaceView != null) {
mView.removeView(mSmartspaceView);
mSmartspaceView = null;
}
}
@@ -436,7 +276,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
scale, props, animate);
if (mSmartspaceView != null) {
PropertyAnimator.setProperty((View) mSmartspaceView, AnimatableProperty.TRANSLATION_X,
PropertyAnimator.setProperty(mSmartspaceView, AnimatableProperty.TRANSLATION_X,
x, props, animate);
}
@@ -510,14 +350,4 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
private int getCurrentLayoutDirection() {
return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
}
@VisibleForTesting
ConfigurationController.ConfigurationListener getConfigurationListener() {
return mConfigurationListener;
}
@VisibleForTesting
ContentObserver getSettingsObserver() {
return mSettingsObserver;
}
}

View File

@@ -29,6 +29,7 @@ import android.app.StatsManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
import android.app.role.RoleManager;
import android.app.smartspace.SmartspaceManager;
import android.app.trust.TrustManager;
import android.content.ContentResolver;
import android.content.Context;
@@ -400,4 +401,10 @@ public class FrameworkServicesModule {
static PermissionManager providePermissionManager(Context context) {
return context.getSystemService(PermissionManager.class);
}
@Provides
@Singleton
static SmartspaceManager provideSmartspaceManager(Context context) {
return context.getSystemService(SmartspaceManager.class);
}
}

View File

@@ -0,0 +1,277 @@
/*
* Copyright (C) 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 com.android.systemui.statusbar.lockscreen
import android.app.PendingIntent
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
import android.view.View
import android.view.ViewGroup
import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.ActivityStarter
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.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
/**
* Controller for managing the smartspace view on the lockscreen
*/
@SysUISingleton
class LockscreenSmartspaceController @Inject constructor(
private val context: Context,
private val featureFlags: FeatureFlags,
private val smartspaceManager: SmartspaceManager,
private val activityStarter: ActivityStarter,
private val falsingManager: FalsingManager,
private val secureSettings: SecureSettings,
private val userTracker: UserTracker,
private val contentResolver: ContentResolver,
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private lateinit var smartspaceView: SmartspaceView
lateinit var view: View
private set
private var showSensitiveContentForCurrentUser = false
private var showSensitiveContentForManagedUser = false
private var managedUserHandle: UserHandle? = null
fun isEnabled(): Boolean {
execution.assertIsMainThread()
return featureFlags.isSmartspaceEnabled && plugin != null
}
/**
* Constructs the smartspace view and connects it to the smartspace service. Subsequent calls
* are idempotent until [disconnect] is called.
*/
fun buildAndConnectView(parent: ViewGroup): View {
execution.assertIsMainThread()
if (!isEnabled()) {
throw RuntimeException("Cannot build view when not enabled")
}
buildView(parent)
connectSession()
return view
}
private fun buildView(parent: ViewGroup) {
if (plugin == null || this::view.isInitialized) {
return
}
val ssView = plugin.getView(parent)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
override fun startIntent(v: View?, i: Intent?) {
activityStarter.startActivity(i, true /* dismissShade */)
}
override fun startPendingIntent(pi: PendingIntent?) {
activityStarter.startPendingIntentDismissingKeyguard(pi)
}
})
ssView.setFalsingManager(falsingManager)
this.smartspaceView = ssView
this.view = ssView as View
updateTextColorFromWallpaper()
statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
}
private fun connectSession() {
if (plugin == null || session != null) {
return
}
val session = smartspaceManager.createSmartspaceSession(
SmartspaceConfig.Builder(context, "lockscreen").build())
session.addOnTargetsAvailableListener(uiExecutor, sessionListener)
userTracker.addCallback(userTrackerCallback, uiExecutor)
contentResolver.registerContentObserver(
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
true,
settingsObserver,
UserHandle.USER_ALL
)
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
this.session = session
reloadSmartspace()
}
/**
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
* Calling [buildAndConnectView] again will cause the same view to be reconnected to the
* service.
*/
fun disconnect() {
execution.assertIsMainThread()
if (session == null) {
return
}
session?.let {
it.removeOnTargetsAvailableListener(sessionListener)
it.close()
}
userTracker.removeCallback(userTrackerCallback)
contentResolver.unregisterContentObserver(settingsObserver)
configurationController.removeCallback(configChangeListener)
statusBarStateController.removeCallback(statusBarStateListener)
session = null
plugin?.onTargetsAvailable(emptyList())
}
fun addListener(listener: SmartspaceTargetListener) {
execution.assertIsMainThread()
plugin?.registerListener(listener)
}
fun removeListener(listener: SmartspaceTargetListener) {
execution.assertIsMainThread()
plugin?.unregisterListener(listener)
}
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
val filteredTargets = targets.filter(::filterSmartspaceTarget)
plugin?.onTargetsAvailable(filteredTargets)
}
private val userTrackerCallback = object : UserTracker.Callback {
override fun onUserChanged(newUser: Int, userContext: Context) {
execution.assertIsMainThread()
reloadSmartspace()
}
override fun onProfilesChanged(profiles: List<UserInfo>) {
}
}
private val settingsObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean, uri: Uri?) {
execution.assertIsMainThread()
reloadSmartspace()
}
}
private val configChangeListener = object : ConfigurationController.ConfigurationListener {
override fun onThemeChanged() {
execution.assertIsMainThread()
updateTextColorFromWallpaper()
}
}
private val statusBarStateListener = object : StatusBarStateController.StateListener {
override fun onDozeAmountChanged(linear: Float, eased: Float) {
execution.assertIsMainThread()
smartspaceView.setDozeAmount(eased)
}
}
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
return when (t.userHandle) {
userTracker.userHandle -> {
!t.isSensitive || showSensitiveContentForCurrentUser
}
managedUserHandle -> {
// Really, this should be "if this managed profile is associated with the current
// active user", but we don't have a good way to check that, so instead we cheat:
// Only the primary user can have an associated managed profile, so only show
// content for the managed profile if the primary user is active
userTracker.userHandle.identifier == UserHandle.USER_SYSTEM &&
(!t.isSensitive || showSensitiveContentForManagedUser)
}
else -> {
false
}
}
}
private fun updateTextColorFromWallpaper() {
val wallpaperTextColor = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColor)
smartspaceView.setPrimaryTextColor(wallpaperTextColor)
}
private fun reloadSmartspace() {
val setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
showSensitiveContentForCurrentUser =
secureSettings.getIntForUser(setting, 0, userTracker.userId) == 1
managedUserHandle = getWorkProfileUser()
val managedId = managedUserHandle?.identifier
if (managedId != null) {
showSensitiveContentForManagedUser =
secureSettings.getIntForUser(setting, 0, managedId) == 1
}
session?.requestSmartspaceUpdate()
}
private fun getWorkProfileUser(): UserHandle? {
for (userInfo in userTracker.userProfiles) {
if (userInfo.isManagedProfile) {
return userInfo.userHandle
}
}
return null
}
}

View File

@@ -19,30 +19,20 @@ package com.android.keyguard;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.UserHandle;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.keyguard.clock.ClockManager;
@@ -50,21 +40,14 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.plugins.BcSmartspaceDataPlugin.IntentStarter;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
import com.android.systemui.statusbar.phone.NotificationIconContainer;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -74,15 +57,12 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.verification.VerificationMode;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
private KeyguardClockSwitch mView;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
@@ -90,62 +70,41 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Mock
private ClockManager mClockManager;
@Mock
private KeyguardClockSwitch mView;
@Mock
private NotificationIconContainer mNotificationIcons;
@Mock
private ClockPlugin mClockPlugin;
@Mock
ColorExtractor.GradientColors mGradientColors;
@Mock
KeyguardSliceViewController mKeyguardSliceViewController;
@Mock
Resources mResources;
@Mock
NotificationIconAreaController mNotificationIconAreaController;
@Mock
BroadcastDispatcher mBroadcastDispatcher;
@Mock
private FeatureFlags mFeatureFlags;
BatteryController mBatteryController;
@Mock
private Executor mExecutor;
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
KeyguardBypassController mBypassController;
@Mock
LockscreenSmartspaceController mSmartspaceController;
@Mock
Resources mResources;
@Mock
private ClockPlugin mClockPlugin;
@Mock
ColorExtractor.GradientColors mGradientColors;
@Mock
private NotificationIconContainer mNotificationIcons;
@Mock
private AnimatableClockView mClockView;
@Mock
private AnimatableClockView mLargeClockView;
@Mock
private FrameLayout mLargeClockFrame;
@Mock
BatteryController mBatteryController;
@Mock
ConfigurationController mConfigurationController;
@Mock
Optional<BcSmartspaceDataPlugin> mOptionalSmartspaceDataProvider;
@Mock
BcSmartspaceDataPlugin mSmartspaceDataProvider;
@Mock
SmartspaceView mSmartspaceView;
@Mock
ActivityStarter mActivityStarter;
@Mock
FalsingManager mFalsingManager;
@Mock
KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
KeyguardBypassController mBypassController;
@Mock
Handler mHandler;
@Mock
UserTracker mUserTracker;
@Mock
SecureSettings mSecureSettings;
private final View mFakeSmartspaceView = new View(mContext);
private KeyguardClockSwitchController mController;
private View mStatusArea;
private static final int USER_ID = 5;
private static final int MANAGED_USER_ID = 15;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -162,9 +121,9 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
when(mClockView.getContext()).thenReturn(getContext());
when(mLargeClockView.getContext()).thenReturn(getContext());
when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
when(mView.isAttachedToWindow()).thenReturn(true);
when(mResources.getString(anyInt())).thenReturn("h:mm");
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController = new KeyguardClockSwitchController(
mView,
mStatusBarStateController,
@@ -173,28 +132,16 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
mKeyguardSliceViewController,
mNotificationIconAreaController,
mBroadcastDispatcher,
mFeatureFlags,
mExecutor,
mBatteryController,
mConfigurationController,
mActivityStarter,
mFalsingManager,
mKeyguardUpdateMonitor,
mBypassController,
mHandler,
mUserTracker,
mSecureSettings,
mOptionalSmartspaceDataProvider
);
mSmartspaceController);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
when(mColorExtractor.getColors(anyInt())).thenReturn(mGradientColors);
mStatusArea = new View(getContext());
when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
when(mOptionalSmartspaceDataProvider.isPresent()).thenReturn(true);
when(mOptionalSmartspaceDataProvider.get()).thenReturn(mSmartspaceDataProvider);
when(mSmartspaceDataProvider.getView(any())).thenReturn(mSmartspaceView);
}
@Test
@@ -255,119 +202,34 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
@Test
public void testSmartspaceEnabledRemovesKeyguardStatusArea() {
when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
when(mSmartspaceController.isEnabled()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
assertEquals(View.GONE, mStatusArea.getVisibility());
}
@Test
public void testSmartspaceEnabledNoDataProviderShowsKeyguardStatusArea() {
when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(true);
when(mOptionalSmartspaceDataProvider.isPresent()).thenReturn(false);
mController.init();
assertEquals(View.VISIBLE, mStatusArea.getVisibility());
}
@Test
public void testSmartspaceDisabledShowsKeyguardStatusArea() {
when(mFeatureFlags.isSmartspaceEnabled()).thenReturn(false);
when(mSmartspaceController.isEnabled()).thenReturn(false);
mController.init();
assertEquals(View.VISIBLE, mStatusArea.getVisibility());
}
@Test
public void testThemeChangeNotifiesSmartspace() {
public void testDetachRemovesSmartspaceView() {
when(mSmartspaceController.isEnabled()).thenReturn(true);
when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
mController.init();
verify(mSmartspaceView).setPrimaryTextColor(anyInt());
verify(mView).addView(eq(mFakeSmartspaceView), anyInt(), any());
mController.getConfigurationListener().onThemeChanged();
verify(mSmartspaceView, times(2)).setPrimaryTextColor(anyInt());
}
ArgumentCaptor<View.OnAttachStateChangeListener> listenerArgumentCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
verify(mView).addOnAttachStateChangeListener(listenerArgumentCaptor.capture());
@Test
public void doNotFilterRegularTarget() {
setupPrimaryAndManagedUser();
mController.init();
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(0);
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
.thenReturn(0);
mController.getSettingsObserver().onChange(true, null);
SmartspaceTarget t = mock(SmartspaceTarget.class);
when(t.isSensitive()).thenReturn(false);
when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
assertEquals(false, mController.filterSmartspaceTarget(t));
reset(t);
when(t.isSensitive()).thenReturn(false);
when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
assertEquals(false, mController.filterSmartspaceTarget(t));
}
@Test
public void filterAllSensitiveTargetsAllUsers() {
setupPrimaryAndManagedUser();
mController.init();
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(0);
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
.thenReturn(0);
mController.getSettingsObserver().onChange(true, null);
SmartspaceTarget t = mock(SmartspaceTarget.class);
when(t.isSensitive()).thenReturn(true);
when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
assertEquals(true, mController.filterSmartspaceTarget(t));
reset(t);
when(t.isSensitive()).thenReturn(true);
when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
assertEquals(true, mController.filterSmartspaceTarget(t));
}
@Test
public void filterSensitiveManagedUserTargets() {
setupPrimaryAndManagedUser();
mController.init();
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(USER_ID))).thenReturn(1);
when(mSecureSettings.getIntForUser(anyString(), anyInt(), eq(MANAGED_USER_ID)))
.thenReturn(0);
mController.getSettingsObserver().onChange(true, null);
SmartspaceTarget t = mock(SmartspaceTarget.class);
when(t.isSensitive()).thenReturn(true);
when(t.getUserHandle()).thenReturn(new UserHandle(USER_ID));
assertEquals(false, mController.filterSmartspaceTarget(t));
reset(t);
when(t.isSensitive()).thenReturn(true);
when(t.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
assertEquals(true, mController.filterSmartspaceTarget(t));
}
private void setupPrimaryAndManagedUser() {
UserInfo userInfo = mock(UserInfo.class);
when(userInfo.isManagedProfile()).thenReturn(true);
when(userInfo.getUserHandle()).thenReturn(new UserHandle(MANAGED_USER_ID));
when(mUserTracker.getUserProfiles()).thenReturn(List.of(userInfo));
when(mUserTracker.getUserId()).thenReturn(USER_ID);
when(mUserTracker.getUserHandle()).thenReturn(new UserHandle(USER_ID));
}
private void setupPrimaryAndNoManagedUser() {
when(mUserTracker.getUserProfiles()).thenReturn(Collections.emptyList());
when(mUserTracker.getUserId()).thenReturn(USER_ID);
when(mUserTracker.getUserHandle()).thenReturn(new UserHandle(USER_ID));
listenerArgumentCaptor.getValue().onViewDetachedFromWindow(mView);
verify(mView).removeView(mFakeSmartspaceView);
}
private void verifyAttachment(VerificationMode times) {
@@ -377,25 +239,4 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
any(ColorExtractor.OnColorsChangedListener.class));
verify(mView, times).updateColors(mGradientColors);
}
private static class SmartspaceView extends View
implements BcSmartspaceDataPlugin.SmartspaceView {
SmartspaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void registerDataProvider(BcSmartspaceDataPlugin plugin) { }
public void setPrimaryTextColor(int color) { }
public void setDozeAmount(float amount) { }
public void setIntentStarter(IntentStarter intentStarter) { }
public void setFalsingManager(FalsingManager falsingManager) { }
public void setDnd(@Nullable Drawable dndIcon, @Nullable String description) { }
public void setNextAlarm(@Nullable Drawable dndIcon, @Nullable String description) { }
}
}

View File

@@ -0,0 +1,518 @@
/*
* Copyright (C) 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 com.android.systemui.statusbar.lockscreen
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceSession.OnTargetsAvailableListener
import android.app.smartspace.SmartspaceTarget
import android.content.ComponentName
import android.content.ContentResolver
import android.content.pm.UserInfo
import android.database.ContentObserver
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Handler
import android.os.UserHandle
import android.provider.Settings
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.plugins.ActivityStarter
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.statusbar.StatusBarStateController
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
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.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.Optional
@SmallTest
class LockscreenSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var featureFlags: FeatureFlags
@Mock
private lateinit var smartspaceManager: SmartspaceManager
@Mock
private lateinit var smartspaceSession: SmartspaceSession
@Mock
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var falsingManager: FalsingManager
@Mock
private lateinit var secureSettings: SecureSettings
@Mock
private lateinit var userTracker: UserTracker
@Mock
private lateinit var contentResolver: ContentResolver
@Mock
private lateinit var configurationController: ConfigurationController
@Mock
private lateinit var statusBarStateController: StatusBarStateController
@Mock
private lateinit var handler: Handler
@Mock
private lateinit var plugin: BcSmartspaceDataPlugin
@Mock
private lateinit var controllerListener: SmartspaceTargetListener
@Captor
private lateinit var sessionListenerCaptor: ArgumentCaptor<OnTargetsAvailableListener>
@Captor
private lateinit var userTrackerCaptor: ArgumentCaptor<UserTracker.Callback>
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
@Captor
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
private lateinit var sessionListener: OnTargetsAvailableListener
private lateinit var userListener: UserTracker.Callback
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
private val clock = FakeSystemClock()
private val executor = FakeExecutor(clock)
private val execution = FakeExecution()
private val fakeParent = FrameLayout(context)
private val fakePrivateLockscreenSettingUri = Uri.Builder().appendPath("test").build()
private val userHandlePrimary: UserHandle = UserHandle(0)
private val userHandleManaged: UserHandle = UserHandle(2)
private val userHandleSecondary: UserHandle = UserHandle(3)
private val userList = listOf(
mockUserInfo(userHandlePrimary, isManagedProfile = false),
mockUserInfo(userHandleManaged, isManagedProfile = true),
mockUserInfo(userHandleSecondary, isManagedProfile = false)
)
private lateinit var controller: LockscreenSmartspaceController
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
`when`(featureFlags.isSmartspaceEnabled).thenReturn(true)
`when`(secureSettings.getUriFor(PRIVATE_LOCKSCREEN_SETTING))
.thenReturn(fakePrivateLockscreenSettingUri)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(smartspaceSession)
`when`(plugin.getView(any())).thenReturn(fakeSmartspaceView)
`when`(userTracker.userProfiles).thenReturn(userList)
`when`(statusBarStateController.dozeAmount).thenReturn(0.5f)
setActiveUser(userHandlePrimary)
setAllowPrivateNotifications(userHandlePrimary, true)
setAllowPrivateNotifications(userHandleManaged, true)
setAllowPrivateNotifications(userHandleSecondary, true)
controller = LockscreenSmartspaceController(
context,
featureFlags,
smartspaceManager,
activityStarter,
falsingManager,
secureSettings,
userTracker,
contentResolver,
configurationController,
statusBarStateController,
execution,
executor,
handler,
Optional.of(plugin)
)
}
@Test(expected = RuntimeException::class)
fun testThrowsIfFlagIsDisabled() {
// GIVEN the feature flag is disabled
`when`(featureFlags.isSmartspaceEnabled).thenReturn(false)
// WHEN we try to build the view
controller.buildAndConnectView(fakeParent)
// THEN an exception is thrown
}
@Test
fun testListenersAreRegistered() {
// GIVEN a listener is added after a session is created
connectSession()
// WHEN a listener is registered
controller.addListener(controllerListener)
// THEN the listener is registered to the underlying plugin
verify(plugin).registerListener(controllerListener)
}
@Test
fun testEarlyRegisteredListenersAreAttachedAfterConnected() {
// GIVEN a listener that is registered before the session is created
controller.addListener(controllerListener)
// WHEN the session is created
connectSession()
// THEN the listener is subsequently registered
verify(plugin).registerListener(controllerListener)
}
@Test
fun testEmptyListIsEmittedAfterDisconnect() {
// GIVEN a registered listener on an active session
connectSession()
clearInvocations(plugin)
// WHEN the session is closed
controller.disconnect()
// THEN the listener receives an empty list of targets
verify(plugin).onTargetsAvailable(emptyList())
}
@Test
fun testUserChangeReloadsSmartspace() {
// GIVEN a connected smartspace session
connectSession()
// WHEN the active user changes
userListener.onUserChanged(-1, context)
// THEN we request a new smartspace update
verify(smartspaceSession).requestSmartspaceUpdate()
}
@Test
fun testSettingsChangeReloadsSmartspace() {
// GIVEN a connected smartspace session
connectSession()
// WHEN the lockscreen privacy setting changes
settingsObserver.onChange(true, null)
// THEN we request a new smartspace update
verify(smartspaceSession).requestSmartspaceUpdate()
}
@Test
fun testThemeChangeUpdatesTextColor() {
// GIVEN a connected smartspace session
connectSession()
// WHEN the theme changes
configChangeListener.onThemeChanged()
// We update the new text color to match the wallpaper color
verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
}
@Test
fun testDozeAmountChangeUpdatesView() {
// 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(fakeSmartspaceView).setDozeAmount(0.7f)
}
@Test
fun testSensitiveTargetsAreNotFilteredIfAllowed() {
// GIVEN the active and managed users allow sensitive content
connectSession()
// WHEN we receive a list of targets
val targets = listOf(
makeTarget(1, userHandlePrimary, isSensitive = true),
makeTarget(2, userHandleManaged, isSensitive = true),
makeTarget(3, userHandlePrimary, isSensitive = true)
)
sessionListener.onTargetsAvailable(targets)
// THEN all sensitive content is still shown
verify(plugin).onTargetsAvailable(eq(targets))
}
@Test
fun testNonSensitiveTargetsAreNeverFiltered() {
// GIVEN the active user doesn't allow sensitive lockscreen content
setAllowPrivateNotifications(userHandlePrimary, false)
connectSession()
// WHEN we receive a list of targets
val targets = listOf(
makeTarget(1, userHandlePrimary),
makeTarget(2, userHandlePrimary),
makeTarget(3, userHandlePrimary)
)
sessionListener.onTargetsAvailable(targets)
// THEN all non-sensitive content is still shown
verify(plugin).onTargetsAvailable(eq(targets))
}
@Test
fun testSensitiveTargetsAreFilteredOutForAppropriateUsers() {
// GIVEN the active and managed users don't allow sensitive lockscreen content
setAllowPrivateNotifications(userHandlePrimary, false)
setAllowPrivateNotifications(userHandleManaged, false)
connectSession()
// WHEN we receive a list of targets
val targets = listOf(
makeTarget(0, userHandlePrimary),
makeTarget(1, userHandlePrimary, isSensitive = true),
makeTarget(2, userHandleManaged, isSensitive = true),
makeTarget(3, userHandleManaged),
makeTarget(4, userHandlePrimary, isSensitive = true),
makeTarget(5, userHandlePrimary),
makeTarget(6, userHandleSecondary, isSensitive = true)
)
sessionListener.onTargetsAvailable(targets)
// THEN only non-sensitive content from those accounts is shown
verify(plugin).onTargetsAvailable(eq(listOf(
targets[0],
targets[3],
targets[5]
)))
}
@Test
fun testSettingsAreReloaded() {
// GIVEN a connected session where the privacy settings later flip to false
connectSession()
setAllowPrivateNotifications(userHandlePrimary, false)
setAllowPrivateNotifications(userHandleManaged, false)
settingsObserver.onChange(true, fakePrivateLockscreenSettingUri)
// WHEN we receive a new list of targets
val targets = listOf(
makeTarget(1, userHandlePrimary, isSensitive = true),
makeTarget(2, userHandleManaged, isSensitive = true),
makeTarget(4, userHandlePrimary, isSensitive = true)
)
sessionListener.onTargetsAvailable(targets)
// THEN we filter based on the new settings values
verify(plugin).onTargetsAvailable(emptyList())
}
@Test
fun testRecognizeSwitchToSecondaryUser() {
// GIVEN an inactive secondary user that doesn't allow sensitive content
setAllowPrivateNotifications(userHandleSecondary, false)
connectSession()
// WHEN the secondary user becomes the active user
setActiveUser(userHandleSecondary)
userListener.onUserChanged(userHandleSecondary.identifier, context)
// WHEN we receive a new list of targets
val targets = listOf(
makeTarget(0, userHandlePrimary),
makeTarget(1, userHandleSecondary),
makeTarget(2, userHandleSecondary, isSensitive = true),
makeTarget(3, userHandleManaged),
makeTarget(4, userHandleSecondary),
makeTarget(5, userHandleManaged),
makeTarget(6, userHandlePrimary)
)
sessionListener.onTargetsAvailable(targets)
// THEN only non-sensitive content from the secondary user is shown
verify(plugin).onTargetsAvailable(listOf(
targets[1],
targets[4]
))
}
@Test
fun testUnregisterListenersOnCleanup() {
// GIVEN a connected session
connectSession()
// WHEN we are told to cleanup
controller.disconnect()
// THEN we disconnect from the session and unregister any listeners
verify(smartspaceSession).removeOnTargetsAvailableListener(sessionListener)
verify(smartspaceSession).close()
verify(userTracker).removeCallback(userListener)
verify(contentResolver).unregisterContentObserver(settingsObserver)
verify(configurationController).removeCallback(configChangeListener)
verify(statusBarStateController).removeCallback(statusBarStateListener)
}
@Test
fun testBuildViewIsIdempotent() {
// GIVEN a connected session
connectSession()
clearInvocations(plugin)
// WHEN we disconnect and then reconnect
controller.disconnect()
controller.buildAndConnectView(fakeParent)
// THEN the view is not rebuilt
verify(plugin, never()).getView(any())
assertEquals(fakeSmartspaceView, controller.view)
}
@Test
fun testDoubleConnectIsIgnored() {
// GIVEN a connected session
connectSession()
clearInvocations(smartspaceManager)
clearInvocations(plugin)
// WHEN we're asked to connect a second time
controller.buildAndConnectView(fakeParent)
// THEN the existing view and session are reused
verify(smartspaceManager, never()).createSmartspaceSession(any())
verify(plugin, never()).getView(any())
assertEquals(fakeSmartspaceView, controller.view)
}
private fun connectSession(): View {
val view = controller.buildAndConnectView(fakeParent)
verify(smartspaceSession)
.addOnTargetsAvailableListener(any(), capture(sessionListenerCaptor))
sessionListener = sessionListenerCaptor.value
verify(userTracker).addCallback(capture(userTrackerCaptor), any())
userListener = userTrackerCaptor.value
verify(contentResolver).registerContentObserver(
eq(fakePrivateLockscreenSettingUri),
eq(true),
capture(settingsObserverCaptor),
eq(UserHandle.USER_ALL))
settingsObserver = settingsObserverCaptor.value
verify(configurationController).addCallback(configChangeListenerCaptor.capture())
configChangeListener = configChangeListenerCaptor.value
verify(statusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
statusBarStateListener = statusBarStateListenerCaptor.value
verify(smartspaceSession).requestSmartspaceUpdate()
clearInvocations(smartspaceSession)
verify(fakeSmartspaceView).setPrimaryTextColor(anyInt())
verify(fakeSmartspaceView).setDozeAmount(0.5f)
clearInvocations(fakeSmartspaceView)
return view
}
private fun setActiveUser(userHandle: UserHandle) {
`when`(userTracker.userId).thenReturn(userHandle.identifier)
`when`(userTracker.userHandle).thenReturn(userHandle)
}
private fun mockUserInfo(userHandle: UserHandle, isManagedProfile: Boolean): UserInfo {
val userInfo = mock(UserInfo::class.java)
`when`(userInfo.userHandle).thenReturn(userHandle)
`when`(userInfo.isManagedProfile).thenReturn(isManagedProfile)
return userInfo
}
fun makeTarget(
id: Int,
userHandle: UserHandle,
isSensitive: Boolean = false
): SmartspaceTarget {
return SmartspaceTarget.Builder(
"target$id",
ComponentName("testpackage", "testclass$id"),
userHandle)
.setSensitive(isSensitive)
.build()
}
private fun setAllowPrivateNotifications(user: UserHandle, value: Boolean) {
`when`(secureSettings.getIntForUser(
eq(PRIVATE_LOCKSCREEN_SETTING),
anyInt(),
eq(user.identifier))
).thenReturn(if (value) 1 else 0)
}
private val fakeSmartspaceView = spy(object : View(context), SmartspaceView {
override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {
}
override fun setPrimaryTextColor(color: Int) {
}
override fun setDozeAmount(amount: Float) {
}
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
}
override fun setFalsingManager(falsingManager: FalsingManager?) {
}
override fun setDnd(image: Drawable?, description: String?) {
}
override fun setNextAlarm(image: Drawable?, description: String?) {
}
})
}
private const val PRIVATE_LOCKSCREEN_SETTING =
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS