Merge "Spin off smartspace behavior into SmartspaceController" into sc-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user