Merge "Dedicated Smartspace handling for Dreams." into tm-dev
This commit is contained in:
@@ -48,6 +48,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
|
||||
import com.android.systemui.recents.Recents;
|
||||
import com.android.systemui.screenshot.dagger.ScreenshotModule;
|
||||
import com.android.systemui.settings.dagger.SettingsModule;
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceModule;
|
||||
import com.android.systemui.statusbar.CommandQueue;
|
||||
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
|
||||
import com.android.systemui.statusbar.NotificationShadeWindowController;
|
||||
@@ -119,6 +120,7 @@ import dagger.Provides;
|
||||
SettingsModule.class,
|
||||
SettingsUtilModule.class,
|
||||
SmartRepliesInflationModule.class,
|
||||
SmartspaceModule.class,
|
||||
StatusBarPolicyModule.class,
|
||||
StatusBarWindowModule.class,
|
||||
SysUIConcurrencyModule.class,
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
@@ -25,7 +26,10 @@ import com.android.systemui.CoreStartable;
|
||||
import com.android.systemui.dreams.complication.Complication;
|
||||
import com.android.systemui.dreams.complication.ComplicationLayoutParams;
|
||||
import com.android.systemui.dreams.complication.ComplicationViewModel;
|
||||
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
|
||||
import com.android.systemui.dreams.smartspace.DreamsSmartspaceController;
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@@ -39,10 +43,22 @@ public class SmartSpaceComplication implements Complication {
|
||||
* SystemUI.
|
||||
*/
|
||||
public static class Registrant extends CoreStartable {
|
||||
private final LockscreenSmartspaceController mSmartSpaceController;
|
||||
private final DreamsSmartspaceController mSmartSpaceController;
|
||||
private final DreamOverlayStateController mDreamOverlayStateController;
|
||||
private final SmartSpaceComplication mComplication;
|
||||
|
||||
private final BcSmartspaceDataPlugin.SmartspaceTargetListener mSmartspaceListener =
|
||||
new BcSmartspaceDataPlugin.SmartspaceTargetListener() {
|
||||
@Override
|
||||
public void onSmartspaceTargetsUpdated(List<? extends Parcelable> targets) {
|
||||
if (!targets.isEmpty()) {
|
||||
mDreamOverlayStateController.addComplication(mComplication);
|
||||
} else {
|
||||
mDreamOverlayStateController.removeComplication(mComplication);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Default constructor for {@link SmartSpaceComplication}.
|
||||
*/
|
||||
@@ -50,7 +66,7 @@ public class SmartSpaceComplication implements Complication {
|
||||
public Registrant(Context context,
|
||||
DreamOverlayStateController dreamOverlayStateController,
|
||||
SmartSpaceComplication smartSpaceComplication,
|
||||
LockscreenSmartspaceController smartSpaceController) {
|
||||
DreamsSmartspaceController smartSpaceController) {
|
||||
super(context);
|
||||
mDreamOverlayStateController = dreamOverlayStateController;
|
||||
mComplication = smartSpaceComplication;
|
||||
@@ -59,32 +75,27 @@ public class SmartSpaceComplication implements Complication {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
addOrRemoveOverlay();
|
||||
mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
|
||||
@Override
|
||||
public void onStateChanged() {
|
||||
addOrRemoveOverlay();
|
||||
if (mDreamOverlayStateController.isOverlayActive()) {
|
||||
mSmartSpaceController.addListener(mSmartspaceListener);
|
||||
} else {
|
||||
mSmartSpaceController.removeListener(mSmartspaceListener);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void addOrRemoveOverlay() {
|
||||
if (mDreamOverlayStateController.isPreviewMode()) {
|
||||
mDreamOverlayStateController.removeComplication(mComplication);
|
||||
} else if (mSmartSpaceController.isEnabled()) {
|
||||
mDreamOverlayStateController.addComplication(mComplication);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class SmartSpaceComplicationViewHolder implements ViewHolder {
|
||||
private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
|
||||
private final LockscreenSmartspaceController mSmartSpaceController;
|
||||
private final DreamsSmartspaceController mSmartSpaceController;
|
||||
private final Context mContext;
|
||||
|
||||
protected SmartSpaceComplicationViewHolder(
|
||||
Context context,
|
||||
LockscreenSmartspaceController smartSpaceController) {
|
||||
DreamsSmartspaceController smartSpaceController) {
|
||||
mSmartSpaceController = smartSpaceController;
|
||||
mContext = context;
|
||||
}
|
||||
@@ -109,12 +120,12 @@ public class SmartSpaceComplication implements Complication {
|
||||
}
|
||||
}
|
||||
|
||||
private final LockscreenSmartspaceController mSmartSpaceController;
|
||||
private final DreamsSmartspaceController mSmartSpaceController;
|
||||
private final Context mContext;
|
||||
|
||||
@Inject
|
||||
public SmartSpaceComplication(Context context,
|
||||
LockscreenSmartspaceController smartSpaceController) {
|
||||
DreamsSmartspaceController smartSpaceController) {
|
||||
mContext = context;
|
||||
mSmartSpaceController = smartSpaceController;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.dreams.smartspace
|
||||
|
||||
import android.app.smartspace.SmartspaceConfig
|
||||
import android.app.smartspace.SmartspaceManager
|
||||
import android.app.smartspace.SmartspaceSession
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
|
||||
import com.android.systemui.smartspace.SmartspacePrecondition
|
||||
import com.android.systemui.smartspace.SmartspaceTargetFilter
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import java.lang.RuntimeException
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* Controller for managing the smartspace view on the dream
|
||||
*/
|
||||
@SysUISingleton
|
||||
class DreamsSmartspaceController @Inject constructor(
|
||||
private val context: Context,
|
||||
private val smartspaceManager: SmartspaceManager,
|
||||
private val execution: Execution,
|
||||
@Main private val uiExecutor: Executor,
|
||||
private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
|
||||
@Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
|
||||
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
|
||||
private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
|
||||
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "DreamsSmartspaceCtrlr"
|
||||
}
|
||||
|
||||
private var session: SmartspaceSession? = null
|
||||
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
|
||||
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
|
||||
|
||||
// A shadow copy of listeners is maintained to track whether the session should remain open.
|
||||
private var listeners = mutableSetOf<BcSmartspaceDataPlugin.SmartspaceTargetListener>()
|
||||
|
||||
// Smartspace can be used on multiple displays, such as when the user casts their screen
|
||||
private var smartspaceViews = mutableSetOf<SmartspaceView>()
|
||||
|
||||
var preconditionListener = object : SmartspacePrecondition.Listener {
|
||||
override fun onCriteriaChanged() {
|
||||
reloadSmartspace()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
precondition.addListener(preconditionListener)
|
||||
}
|
||||
|
||||
var filterListener = object : SmartspaceTargetFilter.Listener {
|
||||
override fun onCriteriaChanged() {
|
||||
reloadSmartspace()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
targetFilter?.addListener(filterListener)
|
||||
}
|
||||
|
||||
var stateChangeListener = object : View.OnAttachStateChangeListener {
|
||||
override fun onViewAttachedToWindow(v: View) {
|
||||
val view = v as SmartspaceView
|
||||
// Until there is dream color matching
|
||||
view.setPrimaryTextColor(Color.WHITE)
|
||||
smartspaceViews.add(view)
|
||||
connectSession()
|
||||
}
|
||||
|
||||
override fun onViewDetachedFromWindow(v: View) {
|
||||
smartspaceViews.remove(v as SmartspaceView)
|
||||
|
||||
if (smartspaceViews.isEmpty()) {
|
||||
disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
|
||||
execution.assertIsMainThread()
|
||||
|
||||
val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
|
||||
plugin?.onTargetsAvailable(filteredTargets)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the smartspace view and connects it to the smartspace service.
|
||||
*/
|
||||
fun buildAndConnectView(parent: ViewGroup): View? {
|
||||
execution.assertIsMainThread()
|
||||
|
||||
if (!precondition.conditionsMet()) {
|
||||
throw RuntimeException("Cannot build view when not enabled")
|
||||
}
|
||||
|
||||
val view = buildView(parent)
|
||||
connectSession()
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun buildView(parent: ViewGroup): View? {
|
||||
return if (plugin != null) {
|
||||
var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
|
||||
.getView()
|
||||
|
||||
if (view is View) {
|
||||
return view
|
||||
}
|
||||
|
||||
return null
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasActiveSessionListeners(): Boolean {
|
||||
return smartspaceViews.isNotEmpty() || listeners.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun connectSession() {
|
||||
if (plugin == null || session != null || !hasActiveSessionListeners()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!precondition.conditionsMet()) {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(b/217559844): Replace with "dream" session when available.
|
||||
val newSession = smartspaceManager.createSmartspaceSession(
|
||||
SmartspaceConfig.Builder(context, "lockscreen").build())
|
||||
Log.d(TAG, "Starting smartspace session for dream")
|
||||
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
|
||||
this.session = newSession
|
||||
|
||||
plugin.registerSmartspaceEventNotifier {
|
||||
e -> session?.notifySmartspaceEvent(e)
|
||||
}
|
||||
|
||||
reloadSmartspace()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the smartspace view from the smartspace service and cleans up any resources.
|
||||
*/
|
||||
private fun disconnect() {
|
||||
if (hasActiveSessionListeners()) return
|
||||
|
||||
execution.assertIsMainThread()
|
||||
|
||||
if (session == null) {
|
||||
return
|
||||
}
|
||||
|
||||
session?.let {
|
||||
it.removeOnTargetsAvailableListener(sessionListener)
|
||||
it.close()
|
||||
}
|
||||
|
||||
session = null
|
||||
|
||||
plugin?.registerSmartspaceEventNotifier(null)
|
||||
plugin?.onTargetsAvailable(emptyList())
|
||||
Log.d(TAG, "Ending smartspace session for dream")
|
||||
}
|
||||
|
||||
fun addListener(listener: SmartspaceTargetListener) {
|
||||
execution.assertIsMainThread()
|
||||
plugin?.registerListener(listener)
|
||||
listeners.add(listener)
|
||||
|
||||
connectSession()
|
||||
}
|
||||
|
||||
fun removeListener(listener: SmartspaceTargetListener) {
|
||||
execution.assertIsMainThread()
|
||||
plugin?.unregisterListener(listener)
|
||||
listeners.remove(listener)
|
||||
disconnect()
|
||||
}
|
||||
|
||||
private fun reloadSmartspace() {
|
||||
session?.requestSmartspaceUpdate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace
|
||||
|
||||
/**
|
||||
* A {@link SmartspacePrecondition} captures the conditions that must be met for Smartspace to be
|
||||
* used in a particular setting.
|
||||
*/
|
||||
interface SmartspacePrecondition {
|
||||
/**
|
||||
* A callback for receiving updates when conditions have changed.
|
||||
*/
|
||||
interface Listener {
|
||||
fun onCriteriaChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be called
|
||||
* immediately upon adding.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Removes a listener from receiving future updates.
|
||||
*/
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Returns whether all conditions have been met.
|
||||
*/
|
||||
fun conditionsMet(): Boolean
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace
|
||||
|
||||
import android.app.smartspace.SmartspaceTarget
|
||||
|
||||
/**
|
||||
* {@link SmartspaceTargetFilter} defines a way to locally filter targets from inclusion. This
|
||||
* should be used for filtering that isn't available further upstream.
|
||||
*/
|
||||
interface SmartspaceTargetFilter {
|
||||
/**
|
||||
* An interface implemented by clients to receive updates when the filtering criteria changes.
|
||||
* When this happens, the client should refresh their target set.
|
||||
*/
|
||||
interface Listener {
|
||||
fun onCriteriaChanged()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a listener to receive future updates. {@link Listener#onCriteriaChanged} will be
|
||||
* invoked immediately after.
|
||||
*/
|
||||
fun addListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Removes listener from receiving future updates.
|
||||
*/
|
||||
fun removeListener(listener: Listener)
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the {@link SmartspaceTarget} should be included in the current
|
||||
* target set, {@code false} otherwise.
|
||||
*/
|
||||
fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace.dagger
|
||||
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
||||
import com.android.systemui.smartspace.SmartspacePrecondition
|
||||
import com.android.systemui.smartspace.SmartspaceTargetFilter
|
||||
import com.android.systemui.smartspace.filters.LockscreenTargetFilter
|
||||
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
|
||||
import dagger.Binds
|
||||
import dagger.BindsOptionalOf
|
||||
import dagger.Module
|
||||
import javax.inject.Named
|
||||
|
||||
@Module(subcomponents = [SmartspaceViewComponent::class])
|
||||
abstract class SmartspaceModule {
|
||||
@Module
|
||||
companion object {
|
||||
/**
|
||||
* The BcSmartspaceDataProvider for dreams.
|
||||
*/
|
||||
const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
|
||||
|
||||
/**
|
||||
* The lockscreen smartspace target filter.
|
||||
*/
|
||||
const val LOCKSCREEN_SMARTSPACE_TARGET_FILTER = "lockscreen_smartspace_target_filter"
|
||||
|
||||
/**
|
||||
* The dream smartspace target filter.
|
||||
*/
|
||||
const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter"
|
||||
|
||||
/**
|
||||
* The precondition for dream smartspace
|
||||
*/
|
||||
const val DREAM_SMARTSPACE_PRECONDITION = "dream_smartspace_precondition"
|
||||
}
|
||||
|
||||
@BindsOptionalOf
|
||||
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
|
||||
abstract fun optionalDreamSmartspaceTargetFilter(): SmartspaceTargetFilter?
|
||||
|
||||
@BindsOptionalOf
|
||||
@Named(DREAM_SMARTSPACE_DATA_PLUGIN)
|
||||
abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
|
||||
|
||||
@Binds
|
||||
@Named(LOCKSCREEN_SMARTSPACE_TARGET_FILTER)
|
||||
abstract fun provideLockscreenSmartspaceTargetFilter(
|
||||
filter: LockscreenTargetFilter?
|
||||
): SmartspaceTargetFilter?
|
||||
|
||||
@Binds
|
||||
@Named(DREAM_SMARTSPACE_PRECONDITION)
|
||||
abstract fun bindSmartspacePrecondition(
|
||||
lockscreenPrecondition: LockscreenPrecondition?
|
||||
): SmartspacePrecondition?
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace.dagger
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent.SmartspaceViewModule.PLUGIN
|
||||
import dagger.BindsInstance
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Subcomponent
|
||||
import javax.inject.Named
|
||||
|
||||
@Subcomponent(modules = [SmartspaceViewComponent.SmartspaceViewModule::class])
|
||||
interface SmartspaceViewComponent {
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(
|
||||
@BindsInstance parent: ViewGroup,
|
||||
@BindsInstance @Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
|
||||
@BindsInstance onAttachListener: View.OnAttachStateChangeListener
|
||||
): SmartspaceViewComponent
|
||||
}
|
||||
|
||||
fun getView(): BcSmartspaceDataPlugin.SmartspaceView
|
||||
|
||||
@Module
|
||||
object SmartspaceViewModule {
|
||||
const val PLUGIN = "plugin"
|
||||
|
||||
@Provides
|
||||
fun providesSmartspaceView(
|
||||
activityStarter: ActivityStarter,
|
||||
falsingManager: FalsingManager,
|
||||
parent: ViewGroup,
|
||||
@Named(PLUGIN) plugin: BcSmartspaceDataPlugin,
|
||||
onAttachListener: View.OnAttachStateChangeListener
|
||||
):
|
||||
BcSmartspaceDataPlugin.SmartspaceView {
|
||||
val ssView = plugin.getView(parent)
|
||||
ssView.registerDataProvider(plugin)
|
||||
|
||||
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
|
||||
override fun startIntent(view: View, intent: Intent, showOnLockscreen: Boolean) {
|
||||
activityStarter.startActivity(
|
||||
intent,
|
||||
true, /* dismissShade */
|
||||
null, /* launch animator */
|
||||
showOnLockscreen
|
||||
)
|
||||
}
|
||||
|
||||
override fun startPendingIntent(pi: PendingIntent, showOnLockscreen: Boolean) {
|
||||
if (showOnLockscreen) {
|
||||
pi.send()
|
||||
} else {
|
||||
activityStarter.startPendingIntentDismissingKeyguard(pi)
|
||||
}
|
||||
}
|
||||
})
|
||||
(ssView as View).addOnAttachStateChangeListener(onAttachListener)
|
||||
ssView.setFalsingManager(falsingManager)
|
||||
return ssView
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace.filters
|
||||
|
||||
import android.app.smartspace.SmartspaceTarget
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.smartspace.SmartspaceTargetFilter
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import com.android.systemui.util.settings.SecureSettings
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* {@link SmartspaceTargetFilter} for smartspace targets that show above the lockscreen.
|
||||
*/
|
||||
class LockscreenTargetFilter @Inject constructor(
|
||||
private val secureSettings: SecureSettings,
|
||||
private val userTracker: UserTracker,
|
||||
private val execution: Execution,
|
||||
@Main private val handler: Handler,
|
||||
private val contentResolver: ContentResolver,
|
||||
@Main private val uiExecutor: Executor
|
||||
) : SmartspaceTargetFilter {
|
||||
private var listeners: MutableSet<SmartspaceTargetFilter.Listener> = mutableSetOf()
|
||||
private var showSensitiveContentForCurrentUser = false
|
||||
set(value) {
|
||||
val existing = field
|
||||
field = value
|
||||
if (existing != field) {
|
||||
listeners.forEach { it.onCriteriaChanged() }
|
||||
}
|
||||
}
|
||||
private var showSensitiveContentForManagedUser = false
|
||||
set(value) {
|
||||
val existing = field
|
||||
field = value
|
||||
if (existing != field) {
|
||||
listeners.forEach { it.onCriteriaChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
private val settingsObserver = object : ContentObserver(handler) {
|
||||
override fun onChange(selfChange: Boolean, uri: Uri?) {
|
||||
execution.assertIsMainThread()
|
||||
updateUserContentSettings()
|
||||
}
|
||||
}
|
||||
|
||||
private var managedUserHandle: UserHandle? = null
|
||||
|
||||
override fun addListener(listener: SmartspaceTargetFilter.Listener) {
|
||||
listeners.add(listener)
|
||||
|
||||
if (listeners.size != 1) {
|
||||
return
|
||||
}
|
||||
|
||||
userTracker.addCallback(userTrackerCallback, uiExecutor)
|
||||
|
||||
contentResolver.registerContentObserver(
|
||||
secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS),
|
||||
true,
|
||||
settingsObserver,
|
||||
UserHandle.USER_ALL
|
||||
)
|
||||
|
||||
updateUserContentSettings()
|
||||
}
|
||||
|
||||
override fun removeListener(listener: SmartspaceTargetFilter.Listener) {
|
||||
listeners.remove(listener)
|
||||
|
||||
if (listeners.isNotEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
userTracker.removeCallback(userTrackerCallback)
|
||||
contentResolver.unregisterContentObserver(settingsObserver)
|
||||
}
|
||||
|
||||
override 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 val userTrackerCallback = object : UserTracker.Callback {
|
||||
override fun onUserChanged(newUser: Int, userContext: Context) {
|
||||
execution.assertIsMainThread()
|
||||
updateUserContentSettings()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getWorkProfileUser(): UserHandle? {
|
||||
for (userInfo in userTracker.userProfiles) {
|
||||
if (userInfo.isManagedProfile) {
|
||||
return userInfo.userHandle
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun updateUserContentSettings() {
|
||||
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
|
||||
}
|
||||
|
||||
listeners.forEach { it.onCriteriaChanged() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace.preconditions
|
||||
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.smartspace.SmartspacePrecondition
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* {@link LockscreenPrecondition} covers the conditions that must be met before Smartspace can be
|
||||
* used over lockscreen. These conditions include the device being provisioned with a setup user
|
||||
* and the Smartspace feature flag enabled.
|
||||
*/
|
||||
class LockscreenPrecondition @Inject constructor(
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val deviceProvisionedController: DeviceProvisionedController,
|
||||
private val execution: Execution
|
||||
) : SmartspacePrecondition {
|
||||
private var listeners = mutableSetOf<SmartspacePrecondition.Listener>()
|
||||
|
||||
private val deviceProvisionedListener =
|
||||
object : DeviceProvisionedController.DeviceProvisionedListener {
|
||||
override fun onDeviceProvisionedChanged() {
|
||||
updateDeviceReadiness()
|
||||
}
|
||||
|
||||
override fun onUserSetupChanged() {
|
||||
updateDeviceReadiness()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
deviceProvisionedController.addCallback(deviceProvisionedListener)
|
||||
}
|
||||
|
||||
var deviceReady: Boolean = false
|
||||
private set
|
||||
|
||||
init {
|
||||
updateDeviceReadiness()
|
||||
}
|
||||
|
||||
private fun updateDeviceReadiness() {
|
||||
if (deviceReady) {
|
||||
return
|
||||
}
|
||||
|
||||
deviceReady = deviceProvisionedController.isDeviceProvisioned &&
|
||||
deviceProvisionedController.isCurrentUserSetup
|
||||
|
||||
if (!deviceReady) {
|
||||
return
|
||||
}
|
||||
|
||||
deviceProvisionedController.removeCallback(deviceProvisionedListener)
|
||||
synchronized(listeners) {
|
||||
listeners.forEach { it.onCriteriaChanged() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun addListener(listener: SmartspacePrecondition.Listener) {
|
||||
synchronized(listeners) {
|
||||
listeners += listener
|
||||
}
|
||||
// Always trigger a targeted callback upon addition of listener.
|
||||
listener.onCriteriaChanged()
|
||||
}
|
||||
|
||||
override fun removeListener(listener: SmartspacePrecondition.Listener) {
|
||||
synchronized(listeners) {
|
||||
listeners -= listener
|
||||
}
|
||||
}
|
||||
|
||||
override fun conditionsMet(): Boolean {
|
||||
execution.assertIsMainThread()
|
||||
return featureFlags.isEnabled(Flags.SMARTSPACE) && deviceReady
|
||||
}
|
||||
}
|
||||
@@ -15,26 +15,31 @@
|
||||
*/
|
||||
package com.android.systemui.dreams;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.app.smartspace.SmartspaceTarget;
|
||||
import android.content.Context;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
|
||||
import com.android.systemui.dreams.smartspace.DreamsSmartspaceController;
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
public class SmartSpaceComplicationTest extends SysuiTestCase {
|
||||
@@ -42,7 +47,7 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private LockscreenSmartspaceController mSmartspaceController;
|
||||
private DreamsSmartspaceController mSmartspaceController;
|
||||
|
||||
@Mock
|
||||
private DreamOverlayStateController mDreamOverlayStateController;
|
||||
@@ -60,7 +65,6 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
|
||||
*/
|
||||
@Test
|
||||
public void testAvailability() {
|
||||
when(mSmartspaceController.isEnabled()).thenReturn(false);
|
||||
|
||||
final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
|
||||
mContext,
|
||||
@@ -68,10 +72,22 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
|
||||
mComplication,
|
||||
mSmartspaceController);
|
||||
registrant.start();
|
||||
verify(mDreamOverlayStateController, never()).addComplication(any());
|
||||
verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
|
||||
|
||||
when(mSmartspaceController.isEnabled()).thenReturn(true);
|
||||
registrant.start();
|
||||
|
||||
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
|
||||
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
|
||||
verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
|
||||
|
||||
when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
|
||||
dreamCallbackCaptor.getValue().onStateChanged();
|
||||
|
||||
final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
|
||||
ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
|
||||
verify(mSmartspaceController).addListener(listenerCaptor.capture());
|
||||
|
||||
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
|
||||
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
|
||||
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace
|
||||
|
||||
import android.app.smartspace.SmartspaceManager
|
||||
import android.app.smartspace.SmartspaceSession
|
||||
import android.app.smartspace.SmartspaceTarget
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.dreams.smartspace.DreamsSmartspaceController
|
||||
import com.android.systemui.plugins.BcSmartspaceDataPlugin
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.Optional
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class DreamSmartspaceControllerTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var smartspaceManager: SmartspaceManager
|
||||
|
||||
@Mock
|
||||
private lateinit var execution: Execution
|
||||
|
||||
@Mock
|
||||
private lateinit var uiExecutor: Executor
|
||||
|
||||
@Mock
|
||||
private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
|
||||
|
||||
@Mock
|
||||
private lateinit var viewComponent: SmartspaceViewComponent
|
||||
|
||||
@Mock
|
||||
private lateinit var targetFilter: SmartspaceTargetFilter
|
||||
|
||||
@Mock
|
||||
private lateinit var plugin: BcSmartspaceDataPlugin
|
||||
|
||||
@Mock
|
||||
private lateinit var precondition: SmartspacePrecondition
|
||||
|
||||
@Mock
|
||||
private lateinit var smartspaceView: BcSmartspaceDataPlugin.SmartspaceView
|
||||
|
||||
@Mock
|
||||
private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
|
||||
|
||||
@Mock
|
||||
private lateinit var session: SmartspaceSession
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
`when`(viewComponentFactory.create(any(), eq(plugin), any()))
|
||||
.thenReturn(viewComponent)
|
||||
`when`(viewComponent.getView()).thenReturn(smartspaceView)
|
||||
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures smartspace session begins on a listener only flow.
|
||||
*/
|
||||
@Test
|
||||
fun testConnectOnListen() {
|
||||
val controller = DreamsSmartspaceController(context,
|
||||
smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition,
|
||||
Optional.of(targetFilter), Optional.of(plugin))
|
||||
|
||||
`when`(precondition.conditionsMet()).thenReturn(true)
|
||||
controller.addListener(listener)
|
||||
|
||||
verify(smartspaceManager).createSmartspaceSession(any())
|
||||
|
||||
var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
|
||||
verify(session).addOnTargetsAvailableListener(any(), capture())
|
||||
}
|
||||
|
||||
`when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
|
||||
|
||||
var target = Mockito.mock(SmartspaceTarget::class.java)
|
||||
targetListener.onTargetsAvailable(listOf(target))
|
||||
|
||||
var targets = withArgCaptor<List<SmartspaceTarget>> {
|
||||
verify(plugin).onTargetsAvailable(capture())
|
||||
}
|
||||
|
||||
assertThat(targets.contains(target)).isTrue()
|
||||
|
||||
controller.removeListener(listener)
|
||||
|
||||
verify(session).close()
|
||||
}
|
||||
|
||||
/**
|
||||
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
|
||||
* object inheritance and interface implementation used in DreamSmartspaceController
|
||||
*/
|
||||
private class TestView(context: Context?) : View(context),
|
||||
BcSmartspaceDataPlugin.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?) {}
|
||||
|
||||
override fun setMediaTarget(target: SmartspaceTarget?) {}
|
||||
|
||||
override fun getSelectedPage(): Int { return 0; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures session begins when a view is attached.
|
||||
*/
|
||||
@Test
|
||||
fun testConnectOnViewCreate() {
|
||||
val controller = DreamsSmartspaceController(context,
|
||||
smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition,
|
||||
Optional.of(targetFilter),
|
||||
Optional.of(plugin))
|
||||
|
||||
`when`(precondition.conditionsMet()).thenReturn(true)
|
||||
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
|
||||
|
||||
var stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
|
||||
verify(viewComponentFactory).create(any(), eq(plugin), capture())
|
||||
}
|
||||
|
||||
var mockView = Mockito.mock(TestView::class.java)
|
||||
`when`(precondition.conditionsMet()).thenReturn(true)
|
||||
stateChangeListener.onViewAttachedToWindow(mockView)
|
||||
|
||||
verify(smartspaceManager).createSmartspaceSession(any())
|
||||
|
||||
stateChangeListener.onViewDetachedFromWindow(mockView)
|
||||
|
||||
verify(session).close()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace
|
||||
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.smartspace.preconditions.LockscreenPrecondition
|
||||
import com.android.systemui.statusbar.policy.DeviceProvisionedController
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper
|
||||
class LockscreenPreconditionTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var featureFlags: FeatureFlags
|
||||
|
||||
@Mock
|
||||
private lateinit var deviceProvisionedController: DeviceProvisionedController
|
||||
|
||||
@Mock
|
||||
private lateinit var execution: Execution
|
||||
|
||||
@Mock
|
||||
private lateinit var listener: SmartspacePrecondition.Listener
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures fully enabled state is published.
|
||||
*/
|
||||
@Test
|
||||
fun testFullyEnabled() {
|
||||
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
|
||||
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
|
||||
`when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
|
||||
.thenReturn(true)
|
||||
val precondition = LockscreenPrecondition(featureFlags, deviceProvisionedController,
|
||||
execution)
|
||||
precondition.addListener(listener)
|
||||
|
||||
`verify`(listener).onCriteriaChanged()
|
||||
assertThat(precondition.conditionsMet()).isTrue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures fully enabled state is published.
|
||||
*/
|
||||
@Test
|
||||
fun testProvisioning() {
|
||||
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
|
||||
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(false)
|
||||
`when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
|
||||
.thenReturn(true)
|
||||
val precondition =
|
||||
LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
|
||||
precondition.addListener(listener)
|
||||
|
||||
verify(listener).onCriteriaChanged()
|
||||
assertThat(precondition.conditionsMet()).isFalse()
|
||||
|
||||
var argumentCaptor = ArgumentCaptor.forClass(DeviceProvisionedController
|
||||
.DeviceProvisionedListener::class.java)
|
||||
verify(deviceProvisionedController).addCallback(argumentCaptor.capture())
|
||||
|
||||
Mockito.clearInvocations(listener)
|
||||
|
||||
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
|
||||
argumentCaptor.value.onDeviceProvisionedChanged()
|
||||
verify(listener).onCriteriaChanged()
|
||||
assertThat(precondition.conditionsMet()).isTrue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure user setup changes are propagated.
|
||||
*/
|
||||
@Test
|
||||
fun testUserSetup() {
|
||||
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
|
||||
`when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
|
||||
`when`(featureFlags.isEnabled(Mockito.eq(Flags.SMARTSPACE) ?: Flags.SMARTSPACE))
|
||||
.thenReturn(true)
|
||||
val precondition =
|
||||
LockscreenPrecondition(featureFlags, deviceProvisionedController, execution)
|
||||
precondition.addListener(listener)
|
||||
|
||||
verify(listener).onCriteriaChanged()
|
||||
assertThat(precondition.conditionsMet()).isFalse()
|
||||
|
||||
var argumentCaptor = ArgumentCaptor.forClass(DeviceProvisionedController
|
||||
.DeviceProvisionedListener::class.java)
|
||||
verify(deviceProvisionedController).addCallback(argumentCaptor.capture())
|
||||
|
||||
Mockito.clearInvocations(listener)
|
||||
|
||||
`when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
|
||||
argumentCaptor.value.onUserSetupChanged()
|
||||
verify(listener).onCriteriaChanged()
|
||||
assertThat(precondition.conditionsMet()).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.smartspace
|
||||
|
||||
import android.app.smartspace.SmartspaceTarget
|
||||
import android.content.ContentResolver
|
||||
import android.database.ContentObserver
|
||||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.UserHandle
|
||||
import android.provider.Settings
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.smartspace.filters.LockscreenTargetFilter
|
||||
import com.android.systemui.util.concurrency.Execution
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.withArgCaptor
|
||||
import com.android.systemui.util.settings.SecureSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.anyBoolean
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.`when`
|
||||
import org.mockito.Mockito.atLeast
|
||||
import org.mockito.Mockito.clearInvocations
|
||||
import org.mockito.Mockito.mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
class LockscreenTargetFilterTest : SysuiTestCase() {
|
||||
@Mock
|
||||
private lateinit var secureSettings: SecureSettings
|
||||
|
||||
@Mock
|
||||
private lateinit var userTracker: UserTracker
|
||||
|
||||
@Mock
|
||||
private lateinit var execution: Execution
|
||||
|
||||
@Mock
|
||||
private lateinit var handler: Handler
|
||||
|
||||
@Mock
|
||||
private lateinit var contentResolver: ContentResolver
|
||||
|
||||
@Mock
|
||||
private lateinit var uiExecution: Executor
|
||||
|
||||
@Mock
|
||||
private lateinit var userHandle: UserHandle
|
||||
|
||||
@Mock
|
||||
private lateinit var listener: SmartspaceTargetFilter.Listener
|
||||
|
||||
@Mock
|
||||
private lateinit var lockScreenAllowPrivateNotificationsUri: Uri
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
`when`(userTracker.userHandle).thenReturn(userHandle)
|
||||
`when`(secureSettings
|
||||
.getUriFor(eq(Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS)))
|
||||
.thenReturn(lockScreenAllowPrivateNotificationsUri)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures {@link Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS} is
|
||||
* tracked.
|
||||
*/
|
||||
@Test
|
||||
fun testLockscreenAllowPrivateNotifications() {
|
||||
var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
|
||||
`when`(secureSettings
|
||||
.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
|
||||
.thenReturn(0)
|
||||
var filter = LockscreenTargetFilter(secureSettings, userTracker, execution, handler,
|
||||
contentResolver, uiExecution)
|
||||
|
||||
filter.addListener(listener)
|
||||
var smartspaceTarget = mock(SmartspaceTarget::class.java)
|
||||
`when`(smartspaceTarget.userHandle).thenReturn(userHandle)
|
||||
`when`(smartspaceTarget.isSensitive).thenReturn(true)
|
||||
assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isFalse()
|
||||
|
||||
var settingCaptor = ArgumentCaptor.forClass(ContentObserver::class.java)
|
||||
|
||||
verify(contentResolver).registerContentObserver(eq(lockScreenAllowPrivateNotificationsUri),
|
||||
anyBoolean(), settingCaptor.capture(), anyInt())
|
||||
|
||||
`when`(secureSettings
|
||||
.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
|
||||
.thenReturn(1)
|
||||
|
||||
clearInvocations(listener)
|
||||
settingCaptor.value.onChange(false, mock(Uri::class.java))
|
||||
verify(listener, atLeast(1)).onCriteriaChanged()
|
||||
assertThat(filter.filterSmartspaceTarget(smartspaceTarget)).isTrue()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures user switches are tracked.
|
||||
*/
|
||||
@Test
|
||||
fun testUserSwitchCallback() {
|
||||
var setting = Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS
|
||||
`when`(secureSettings
|
||||
.getIntForUser(eq(setting) ?: setting, anyInt(), anyInt()))
|
||||
.thenReturn(0)
|
||||
var filter = LockscreenTargetFilter(secureSettings, userTracker, execution, handler,
|
||||
contentResolver, uiExecution)
|
||||
|
||||
filter.addListener(listener)
|
||||
|
||||
var userTrackerCallback = withArgCaptor<UserTracker.Callback> {
|
||||
verify(userTracker).addCallback(capture(), any())
|
||||
}
|
||||
|
||||
clearInvocations(listener)
|
||||
userTrackerCallback.onUserChanged(0, context)
|
||||
|
||||
verify(listener).onCriteriaChanged()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user