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:
@@ -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