Lock screen preview.
Adds code on the system UI side that renders a preview of the lock screen. This is done through the content provider, which receives a handle to a SurfaceView from wallpaper picker, where the UI is actually shown to the user. There are several changes here, following the path of the code, they are: 1. KeyguardQuickAffordanceProvider can handle the "call" from wallpaper picker and delegates the rendering to the preview system 2. The preview system starts with KeyguardRemotePreviewManager which is responsible for driving the lifecycle of the rendering as the connection to the remote SurfaceView is established or torn down 3. The preview system continues with the KeyguardPreviewRenderer which builds the view hierarchy and hooks up each view to its appropriate view-binder or view-controller 4. For quick affordances, we are making a couple of minor changes to the view-binder, view-model, and interactor to allow for them to run in "preview" mode where different rules apply: no clicks allowed, the affordances are ever-visible (as opposed to only when the lock screen is shown), etc. Bug: 261362750 Test: unit tests created/updated, manually verified in the shortcuts settings screen Change-Id: I5d1c51dacbedf0a7a1fb3ec742244141a2fe8a32
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.shared.quickaffordance.shared.model
|
||||
|
||||
object KeyguardQuickAffordancePreviewConstants {
|
||||
const val MESSAGE_ID_SLOT_SELECTED = 1337
|
||||
const val KEY_SLOT_ID = "slot_id"
|
||||
const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
|
||||
}
|
||||
@@ -16,13 +16,53 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
-->
|
||||
<shape
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?androidprv:attr/colorSurface"/>
|
||||
<size
|
||||
android:width="@dimen/keyguard_affordance_width"
|
||||
android:height="@dimen/keyguard_affordance_height"/>
|
||||
<corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
|
||||
</shape>
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
|
||||
|
||||
<item android:state_selected="true">
|
||||
<layer-list>
|
||||
<item
|
||||
android:left="3dp"
|
||||
android:top="3dp"
|
||||
android:right="3dp"
|
||||
android:bottom="3dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?androidprv:attr/colorSurface"/>
|
||||
<size
|
||||
android:width="@dimen/keyguard_affordance_width"
|
||||
android:height="@dimen/keyguard_affordance_height"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<stroke
|
||||
android:color="@color/control_primary_text"
|
||||
android:width="2dp"/>
|
||||
<size
|
||||
android:width="@dimen/keyguard_affordance_width"
|
||||
android:height="@dimen/keyguard_affordance_height"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
|
||||
<item>
|
||||
<layer-list>
|
||||
<item
|
||||
android:left="3dp"
|
||||
android:top="3dp"
|
||||
android:right="3dp"
|
||||
android:bottom="3dp">
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="?androidprv:attr/colorSurface"/>
|
||||
<size
|
||||
android:width="@dimen/keyguard_affordance_width"
|
||||
android:height="@dimen/keyguard_affordance_height"/>
|
||||
</shape>
|
||||
</item>
|
||||
</layer-list>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
@@ -43,6 +44,21 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
public static final int LARGE = 0;
|
||||
public static final int SMALL = 1;
|
||||
|
||||
/** Returns a region for the large clock to position itself, based on the given parent. */
|
||||
public static Rect getLargeClockRegion(ViewGroup parent) {
|
||||
int largeClockTopMargin = parent.getResources()
|
||||
.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
|
||||
int targetHeight = parent.getResources()
|
||||
.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
|
||||
int top = parent.getHeight() / 2 - targetHeight / 2
|
||||
+ largeClockTopMargin / 2;
|
||||
return new Rect(
|
||||
parent.getLeft(),
|
||||
top,
|
||||
parent.getRight(),
|
||||
top + targetHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Frame for small/large clocks
|
||||
*/
|
||||
@@ -129,17 +145,8 @@ public class KeyguardClockSwitch extends RelativeLayout {
|
||||
}
|
||||
|
||||
if (mLargeClockFrame.isLaidOut()) {
|
||||
int largeClockTopMargin = getResources()
|
||||
.getDimensionPixelSize(R.dimen.keyguard_large_clock_top_margin);
|
||||
int targetHeight = getResources()
|
||||
.getDimensionPixelSize(R.dimen.large_clock_text_size) * 2;
|
||||
int top = mLargeClockFrame.getHeight() / 2 - targetHeight / 2
|
||||
+ largeClockTopMargin / 2;
|
||||
mClock.getLargeClock().getEvents().onTargetRegionChanged(new Rect(
|
||||
mLargeClockFrame.getLeft(),
|
||||
top,
|
||||
mLargeClockFrame.getRight(),
|
||||
top + targetHeight));
|
||||
mClock.getLargeClock().getEvents().onTargetRegionChanged(
|
||||
getLargeClockRegion(mLargeClockFrame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,14 +21,18 @@ import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.UriMatcher
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import com.android.systemui.SystemUIAppComponentFactoryBase
|
||||
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
||||
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
|
||||
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@@ -37,6 +41,7 @@ class KeyguardQuickAffordanceProvider :
|
||||
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
|
||||
|
||||
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
|
||||
@Inject lateinit var previewManager: KeyguardRemotePreviewManager
|
||||
|
||||
private lateinit var contextAvailableCallback: ContextAvailableCallback
|
||||
|
||||
@@ -149,6 +154,21 @@ class KeyguardQuickAffordanceProvider :
|
||||
return deleteSelection(uri, selectionArgs)
|
||||
}
|
||||
|
||||
override fun call(method: String, arg: String?, extras: Bundle?): Bundle? {
|
||||
return if (
|
||||
requireContext()
|
||||
.checkPermission(
|
||||
android.Manifest.permission.BIND_WALLPAPER,
|
||||
Binder.getCallingPid(),
|
||||
Binder.getCallingUid(),
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
previewManager.preview(extras)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertSelection(values: ContentValues?): Uri? {
|
||||
if (values == null) {
|
||||
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import dagger.Lazy
|
||||
@@ -67,7 +66,7 @@ constructor(
|
||||
position: KeyguardQuickAffordancePosition
|
||||
): Flow<KeyguardQuickAffordanceModel> {
|
||||
return combine(
|
||||
quickAffordanceInternal(position),
|
||||
quickAffordanceAlwaysVisible(position),
|
||||
keyguardInteractor.isDozing,
|
||||
keyguardInteractor.isKeyguardShowing,
|
||||
) { affordance, isDozing, isKeyguardShowing ->
|
||||
@@ -79,6 +78,19 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable for the quick affordance at the given position but always visible,
|
||||
* regardless of lock screen state.
|
||||
*
|
||||
* This is useful for experiences like the lock screen preview mode, where the affordances must
|
||||
* always be visible.
|
||||
*/
|
||||
fun quickAffordanceAlwaysVisible(
|
||||
position: KeyguardQuickAffordancePosition,
|
||||
): Flow<KeyguardQuickAffordanceModel> {
|
||||
return quickAffordanceInternal(position)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that a quick affordance has been "triggered" (clicked) by the user.
|
||||
*
|
||||
@@ -290,15 +302,6 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun KeyguardQuickAffordancePosition.toSlotId(): String {
|
||||
return when (this) {
|
||||
KeyguardQuickAffordancePosition.BOTTOM_START ->
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
KeyguardQuickAffordancePosition.BOTTOM_END ->
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.encode(slotId: String): String {
|
||||
return "$slotId$DELIMITER$this"
|
||||
}
|
||||
|
||||
@@ -16,8 +16,17 @@
|
||||
|
||||
package com.android.systemui.keyguard.shared.quickaffordance
|
||||
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
|
||||
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
|
||||
enum class KeyguardQuickAffordancePosition {
|
||||
BOTTOM_START,
|
||||
BOTTOM_END,
|
||||
BOTTOM_END;
|
||||
|
||||
fun toSlotId(): String {
|
||||
return when (this) {
|
||||
BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewMod
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.plugins.FalsingManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -84,7 +83,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
fun bind(
|
||||
view: ViewGroup,
|
||||
viewModel: KeyguardBottomAreaViewModel,
|
||||
falsingManager: FalsingManager,
|
||||
falsingManager: FalsingManager?,
|
||||
): Binding {
|
||||
val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
|
||||
val ambientIndicationArea: View? = view.findViewById(R.id.ambient_indication_container)
|
||||
@@ -225,7 +224,7 @@ object KeyguardBottomAreaViewBinder {
|
||||
private fun updateButton(
|
||||
view: ImageView,
|
||||
viewModel: KeyguardQuickAffordanceViewModel,
|
||||
falsingManager: FalsingManager,
|
||||
falsingManager: FalsingManager?,
|
||||
) {
|
||||
if (!viewModel.isVisible) {
|
||||
view.isVisible = false
|
||||
@@ -281,22 +280,29 @@ object KeyguardBottomAreaViewBinder {
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
view.backgroundTintList =
|
||||
Utils.getColorAttr(
|
||||
view.context,
|
||||
if (viewModel.isActivated) {
|
||||
com.android.internal.R.attr.colorAccentPrimary
|
||||
} else {
|
||||
com.android.internal.R.attr.colorSurface
|
||||
}
|
||||
)
|
||||
if (!viewModel.isSelected) {
|
||||
Utils.getColorAttr(
|
||||
view.context,
|
||||
if (viewModel.isActivated) {
|
||||
com.android.internal.R.attr.colorAccentPrimary
|
||||
} else {
|
||||
com.android.internal.R.attr.colorSurface
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
view.isClickable = viewModel.isClickable
|
||||
if (viewModel.isClickable) {
|
||||
view.setOnClickListener(OnClickListener(viewModel, falsingManager))
|
||||
view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
|
||||
} else {
|
||||
view.setOnClickListener(null)
|
||||
}
|
||||
|
||||
view.isSelected = viewModel.isSelected
|
||||
}
|
||||
|
||||
private class OnClickListener(
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* 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.keyguard.ui.preview
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.hardware.display.DisplayManager
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.SurfaceControlViewHost
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.widget.FrameLayout
|
||||
import com.android.keyguard.ClockEventController
|
||||
import com.android.keyguard.KeyguardClockSwitch
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
|
||||
import com.android.systemui.shared.clocks.ClockRegistry
|
||||
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
|
||||
import com.android.systemui.statusbar.phone.KeyguardBottomAreaView
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
/** Renders the preview of the lock screen. */
|
||||
class KeyguardPreviewRenderer
|
||||
@AssistedInject
|
||||
constructor(
|
||||
@Application private val context: Context,
|
||||
@Main private val mainDispatcher: CoroutineDispatcher,
|
||||
private val bottomAreaViewModel: KeyguardBottomAreaViewModel,
|
||||
displayManager: DisplayManager,
|
||||
private val windowManager: WindowManager,
|
||||
private val clockController: ClockEventController,
|
||||
private val clockRegistry: ClockRegistry,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
@Assisted bundle: Bundle,
|
||||
) {
|
||||
|
||||
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
|
||||
private val width: Int = bundle.getInt(KEY_VIEW_WIDTH)
|
||||
private val height: Int = bundle.getInt(KEY_VIEW_HEIGHT)
|
||||
|
||||
private var host: SurfaceControlViewHost
|
||||
|
||||
val surfacePackage: SurfaceControlViewHost.SurfacePackage
|
||||
get() = host.surfacePackage
|
||||
|
||||
private var clockView: View? = null
|
||||
|
||||
private val disposables = mutableSetOf<DisposableHandle>()
|
||||
private var isDestroyed = false
|
||||
|
||||
init {
|
||||
bottomAreaViewModel.enablePreviewMode(
|
||||
initiallySelectedSlotId =
|
||||
bundle.getString(
|
||||
KeyguardQuickAffordancePreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
|
||||
),
|
||||
)
|
||||
runBlocking(mainDispatcher) {
|
||||
host =
|
||||
SurfaceControlViewHost(
|
||||
context,
|
||||
displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)),
|
||||
hostToken,
|
||||
)
|
||||
disposables.add(DisposableHandle { host.release() })
|
||||
}
|
||||
}
|
||||
|
||||
fun render() {
|
||||
runBlocking(mainDispatcher) {
|
||||
val rootView = FrameLayout(context)
|
||||
|
||||
setUpBottomArea(rootView)
|
||||
setUpClock(rootView)
|
||||
|
||||
rootView.measure(
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
windowManager.currentWindowMetrics.bounds.width(),
|
||||
View.MeasureSpec.EXACTLY
|
||||
),
|
||||
View.MeasureSpec.makeMeasureSpec(
|
||||
windowManager.currentWindowMetrics.bounds.height(),
|
||||
View.MeasureSpec.EXACTLY
|
||||
),
|
||||
)
|
||||
rootView.layout(0, 0, rootView.measuredWidth, rootView.measuredHeight)
|
||||
|
||||
// This aspect scales the view to fit in the surface and centers it
|
||||
val scale: Float =
|
||||
(width / rootView.measuredWidth.toFloat()).coerceAtMost(
|
||||
height / rootView.measuredHeight.toFloat()
|
||||
)
|
||||
|
||||
rootView.scaleX = scale
|
||||
rootView.scaleY = scale
|
||||
rootView.pivotX = 0f
|
||||
rootView.pivotY = 0f
|
||||
rootView.translationX = (width - scale * rootView.width) / 2
|
||||
rootView.translationY = (height - scale * rootView.height) / 2
|
||||
|
||||
host.setView(rootView, rootView.measuredWidth, rootView.measuredHeight)
|
||||
}
|
||||
}
|
||||
|
||||
fun onSlotSelected(slotId: String) {
|
||||
bottomAreaViewModel.onPreviewSlotSelected(slotId = slotId)
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
isDestroyed = true
|
||||
disposables.forEach { it.dispose() }
|
||||
}
|
||||
|
||||
private fun setUpBottomArea(parentView: ViewGroup) {
|
||||
val bottomAreaView =
|
||||
LayoutInflater.from(context)
|
||||
.inflate(
|
||||
R.layout.keyguard_bottom_area,
|
||||
parentView,
|
||||
false,
|
||||
) as KeyguardBottomAreaView
|
||||
bottomAreaView.init(
|
||||
viewModel = bottomAreaViewModel,
|
||||
)
|
||||
parentView.addView(
|
||||
bottomAreaView,
|
||||
FrameLayout.LayoutParams(
|
||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||
FrameLayout.LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUpClock(parentView: ViewGroup) {
|
||||
val clockChangeListener = ClockRegistry.ClockChangeListener { onClockChanged(parentView) }
|
||||
clockRegistry.registerClockChangeListener(clockChangeListener)
|
||||
disposables.add(
|
||||
DisposableHandle { clockRegistry.unregisterClockChangeListener(clockChangeListener) }
|
||||
)
|
||||
|
||||
clockController.registerListeners(parentView)
|
||||
disposables.add(DisposableHandle { clockController.unregisterListeners() })
|
||||
|
||||
val receiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
clockController.clock?.events?.onTimeTick()
|
||||
}
|
||||
}
|
||||
broadcastDispatcher.registerReceiver(
|
||||
receiver,
|
||||
IntentFilter().apply {
|
||||
addAction(Intent.ACTION_TIME_TICK)
|
||||
addAction(Intent.ACTION_TIME_CHANGED)
|
||||
},
|
||||
)
|
||||
disposables.add(DisposableHandle { broadcastDispatcher.unregisterReceiver(receiver) })
|
||||
|
||||
onClockChanged(parentView)
|
||||
}
|
||||
|
||||
private fun onClockChanged(parentView: ViewGroup) {
|
||||
clockController.clock = clockRegistry.createCurrentClock()
|
||||
clockController.clock
|
||||
?.largeClock
|
||||
?.events
|
||||
?.onTargetRegionChanged(KeyguardClockSwitch.getLargeClockRegion(parentView))
|
||||
clockView?.let { parentView.removeView(it) }
|
||||
clockView = clockController.clock?.largeClock?.view?.apply { parentView.addView(this) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_HOST_TOKEN = "host_token"
|
||||
private const val KEY_VIEW_WIDTH = "width"
|
||||
private const val KEY_VIEW_HEIGHT = "height"
|
||||
private const val KEY_DISPLAY_ID = "display_id"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.keyguard.ui.preview
|
||||
|
||||
import android.os.Bundle
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
||||
@AssistedFactory
|
||||
interface KeyguardPreviewRendererFactory {
|
||||
fun create(bundle: Bundle): KeyguardPreviewRenderer
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.keyguard.ui.preview
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.util.ArrayMap
|
||||
import android.util.Log
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.shared.quickaffordance.shared.model.KeyguardQuickAffordancePreviewConstants
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@SysUISingleton
|
||||
class KeyguardRemotePreviewManager
|
||||
@Inject
|
||||
constructor(
|
||||
private val previewRendererFactory: KeyguardPreviewRendererFactory,
|
||||
@Main private val mainDispatcher: CoroutineDispatcher,
|
||||
@Background private val backgroundHandler: Handler,
|
||||
) {
|
||||
private val activePreviews: ArrayMap<IBinder, PreviewLifecycleObserver> =
|
||||
ArrayMap<IBinder, PreviewLifecycleObserver>()
|
||||
|
||||
fun preview(request: Bundle?): Bundle? {
|
||||
if (request == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
var observer: PreviewLifecycleObserver? = null
|
||||
return try {
|
||||
val renderer = previewRendererFactory.create(request)
|
||||
|
||||
// Destroy any previous renderer associated with this token.
|
||||
activePreviews[renderer.hostToken]?.let { destroyObserver(it) }
|
||||
observer = PreviewLifecycleObserver(renderer, mainDispatcher, ::destroyObserver)
|
||||
activePreviews[renderer.hostToken] = observer
|
||||
renderer.render()
|
||||
renderer.hostToken?.linkToDeath(observer, 0)
|
||||
val result = Bundle()
|
||||
result.putParcelable(
|
||||
KEY_PREVIEW_SURFACE_PACKAGE,
|
||||
renderer.surfacePackage,
|
||||
)
|
||||
val messenger =
|
||||
Messenger(
|
||||
Handler(
|
||||
backgroundHandler.looper,
|
||||
observer,
|
||||
)
|
||||
)
|
||||
val msg = Message.obtain()
|
||||
msg.replyTo = messenger
|
||||
result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to generate preview", e)
|
||||
observer?.let { destroyObserver(it) }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun destroyObserver(observer: PreviewLifecycleObserver) {
|
||||
observer.onDestroy()?.let { hostToken ->
|
||||
if (activePreviews[hostToken] === observer) {
|
||||
activePreviews.remove(hostToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PreviewLifecycleObserver(
|
||||
private val renderer: KeyguardPreviewRenderer,
|
||||
private val mainDispatcher: CoroutineDispatcher,
|
||||
private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
|
||||
) : Handler.Callback, IBinder.DeathRecipient {
|
||||
|
||||
private var isDestroyed = false
|
||||
|
||||
override fun handleMessage(message: Message): Boolean {
|
||||
when (message.what) {
|
||||
KeyguardQuickAffordancePreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
|
||||
message.data
|
||||
.getString(
|
||||
KeyguardQuickAffordancePreviewConstants.KEY_SLOT_ID,
|
||||
)
|
||||
?.let { slotId -> renderer.onSlotSelected(slotId = slotId) }
|
||||
}
|
||||
else -> requestDestruction(this)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun binderDied() {
|
||||
requestDestruction(this)
|
||||
}
|
||||
|
||||
fun onDestroy(): IBinder? {
|
||||
if (isDestroyed) {
|
||||
return null
|
||||
}
|
||||
|
||||
isDestroyed = true
|
||||
val hostToken = renderer.hostToken
|
||||
hostToken?.unlinkToDeath(this, 0)
|
||||
runBlocking(mainDispatcher) { renderer.destroy() }
|
||||
return hostToken
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyguardRemotePreviewManager"
|
||||
@VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
|
||||
@VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,19 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceIn
|
||||
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
/** View-model for the keyguard bottom area view */
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class KeyguardBottomAreaViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@@ -39,6 +45,20 @@ constructor(
|
||||
private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
|
||||
private val burnInHelperWrapper: BurnInHelperWrapper,
|
||||
) {
|
||||
/**
|
||||
* Whether this view-model instance is powering the preview experience that renders exclusively
|
||||
* in the wallpaper picker application. This should _always_ be `false` for the real lock screen
|
||||
* experience.
|
||||
*/
|
||||
private val isInPreviewMode = MutableStateFlow(false)
|
||||
|
||||
/**
|
||||
* ID of the slot that's currently selected in the preview that renders exclusively in the
|
||||
* wallpaper picker application. This is ignored for the actual, real lock screen experience.
|
||||
*/
|
||||
private val selectedPreviewSlotId =
|
||||
MutableStateFlow(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
|
||||
|
||||
/**
|
||||
* Whether quick affordances are "opaque enough" to be considered visible to and interactive by
|
||||
* the user. If they are not interactive, user input should not be allowed on them.
|
||||
@@ -66,7 +86,14 @@ constructor(
|
||||
val isOverlayContainerVisible: Flow<Boolean> =
|
||||
keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
|
||||
/** An observable for the alpha level for the entire bottom area. */
|
||||
val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
|
||||
val alpha: Flow<Float> =
|
||||
isInPreviewMode.flatMapLatest { isInPreviewMode ->
|
||||
if (isInPreviewMode) {
|
||||
flowOf(1f)
|
||||
} else {
|
||||
bottomAreaInteractor.alpha.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
/** An observable for whether the indication area should be padded. */
|
||||
val isIndicationAreaPadded: Flow<Boolean> =
|
||||
combine(startButton, endButton) { startButtonModel, endButtonModel ->
|
||||
@@ -94,27 +121,61 @@ constructor(
|
||||
* Returns whether the keyguard bottom area should be constrained to the top of the lock icon
|
||||
*/
|
||||
fun shouldConstrainToTopOfLockIcon(): Boolean =
|
||||
bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
|
||||
bottomAreaInteractor.shouldConstrainToTopOfLockIcon()
|
||||
|
||||
/**
|
||||
* Puts this view-model in "preview mode", which means it's being used for UI that is rendering
|
||||
* the lock screen preview in wallpaper picker / settings and not the real experience on the
|
||||
* lock screen.
|
||||
*
|
||||
* @param initiallySelectedSlotId The ID of the initial slot to render as the selected one.
|
||||
*/
|
||||
fun enablePreviewMode(initiallySelectedSlotId: String?) {
|
||||
isInPreviewMode.value = true
|
||||
onPreviewSlotSelected(
|
||||
initiallySelectedSlotId ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies that a slot with the given ID has been selected in the preview experience that is
|
||||
* rendering in the wallpaper picker. This is ignored for the real lock screen experience.
|
||||
*
|
||||
* @see enablePreviewMode
|
||||
*/
|
||||
fun onPreviewSlotSelected(slotId: String) {
|
||||
selectedPreviewSlotId.value = slotId
|
||||
}
|
||||
|
||||
private fun button(
|
||||
position: KeyguardQuickAffordancePosition
|
||||
): Flow<KeyguardQuickAffordanceViewModel> {
|
||||
return combine(
|
||||
quickAffordanceInteractor.quickAffordance(position),
|
||||
bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
|
||||
areQuickAffordancesFullyOpaque,
|
||||
) { model, animateReveal, isFullyOpaque ->
|
||||
model.toViewModel(
|
||||
animateReveal = animateReveal,
|
||||
isClickable = isFullyOpaque,
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
return isInPreviewMode.flatMapLatest { isInPreviewMode ->
|
||||
combine(
|
||||
if (isInPreviewMode) {
|
||||
quickAffordanceInteractor.quickAffordanceAlwaysVisible(position = position)
|
||||
} else {
|
||||
quickAffordanceInteractor.quickAffordance(position = position)
|
||||
},
|
||||
bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged(),
|
||||
areQuickAffordancesFullyOpaque,
|
||||
selectedPreviewSlotId,
|
||||
) { model, animateReveal, isFullyOpaque, selectedPreviewSlotId ->
|
||||
model.toViewModel(
|
||||
animateReveal = !isInPreviewMode && animateReveal,
|
||||
isClickable = isFullyOpaque && !isInPreviewMode,
|
||||
isSelected =
|
||||
(isInPreviewMode && selectedPreviewSlotId == position.toSlotId()),
|
||||
)
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private fun KeyguardQuickAffordanceModel.toViewModel(
|
||||
animateReveal: Boolean,
|
||||
isClickable: Boolean,
|
||||
isSelected: Boolean,
|
||||
): KeyguardQuickAffordanceViewModel {
|
||||
return when (this) {
|
||||
is KeyguardQuickAffordanceModel.Visible ->
|
||||
@@ -131,6 +192,7 @@ constructor(
|
||||
},
|
||||
isClickable = isClickable,
|
||||
isActivated = activationState is ActivationState.Active,
|
||||
isSelected = isSelected,
|
||||
)
|
||||
is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ data class KeyguardQuickAffordanceViewModel(
|
||||
val onClicked: (OnClickedParameters) -> Unit = {},
|
||||
val isClickable: Boolean = false,
|
||||
val isActivated: Boolean = false,
|
||||
val isSelected: Boolean = false,
|
||||
) {
|
||||
data class OnClickedParameters(
|
||||
val configKey: String,
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.view.ViewGroup
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.WindowInsets
|
||||
import android.widget.FrameLayout
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.LockIconViewController
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder
|
||||
@@ -53,19 +52,20 @@ constructor(
|
||||
|
||||
private var ambientIndicationArea: View? = null
|
||||
private lateinit var binding: KeyguardBottomAreaViewBinder.Binding
|
||||
private lateinit var lockIconViewController: LockIconViewController
|
||||
private var lockIconViewController: LockIconViewController? = null
|
||||
|
||||
/** Initializes the view. */
|
||||
fun init(
|
||||
viewModel: KeyguardBottomAreaViewModel,
|
||||
falsingManager: FalsingManager,
|
||||
lockIconViewController: LockIconViewController,
|
||||
falsingManager: FalsingManager? = null,
|
||||
lockIconViewController: LockIconViewController? = null,
|
||||
) {
|
||||
binding = bind(
|
||||
binding =
|
||||
bind(
|
||||
this,
|
||||
viewModel,
|
||||
falsingManager,
|
||||
)
|
||||
)
|
||||
this.lockIconViewController = lockIconViewController
|
||||
}
|
||||
|
||||
@@ -129,21 +129,21 @@ constructor(
|
||||
findViewById<View>(R.id.ambient_indication_container)?.let {
|
||||
val (ambientLeft, ambientTop) = it.locationOnScreen
|
||||
if (binding.shouldConstrainToTopOfLockIcon()) {
|
||||
//make top of ambient indication view the bottom of the lock icon
|
||||
// make top of ambient indication view the bottom of the lock icon
|
||||
it.layout(
|
||||
ambientLeft,
|
||||
lockIconViewController.bottom.toInt(),
|
||||
right - ambientLeft,
|
||||
ambientTop + it.measuredHeight
|
||||
ambientLeft,
|
||||
lockIconViewController?.bottom?.toInt() ?: 0,
|
||||
right - ambientLeft,
|
||||
ambientTop + it.measuredHeight
|
||||
)
|
||||
} else {
|
||||
//make bottom of ambient indication view the top of the lock icon
|
||||
val lockLocationTop = lockIconViewController.top
|
||||
// make bottom of ambient indication view the top of the lock icon
|
||||
val lockLocationTop = lockIconViewController?.top ?: 0
|
||||
it.layout(
|
||||
ambientLeft,
|
||||
lockLocationTop.toInt() - it.measuredHeight,
|
||||
right - ambientLeft,
|
||||
lockLocationTop.toInt()
|
||||
ambientLeft,
|
||||
lockLocationTop.toInt() - it.measuredHeight,
|
||||
right - ambientLeft,
|
||||
lockLocationTop.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,13 @@ package com.android.systemui.keyguard
|
||||
import android.content.ContentValues
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ProviderInfo
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.os.UserHandle
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.SurfaceControlViewHost
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.systemui.SystemUIAppComponentFactoryBase
|
||||
@@ -36,6 +42,9 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
||||
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer
|
||||
import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory
|
||||
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.settings.UserFileManager
|
||||
import com.android.systemui.settings.UserTracker
|
||||
@@ -43,40 +52,53 @@ import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordance
|
||||
import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.util.FakeSharedPreferences
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Mock private lateinit var lockPatternUtils: LockPatternUtils
|
||||
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||
@Mock private lateinit var userTracker: UserTracker
|
||||
@Mock private lateinit var activityStarter: ActivityStarter
|
||||
@Mock private lateinit var previewRendererFactory: KeyguardPreviewRendererFactory
|
||||
@Mock private lateinit var previewRenderer: KeyguardPreviewRenderer
|
||||
@Mock private lateinit var backgroundHandler: Handler
|
||||
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
|
||||
|
||||
private lateinit var underTest: KeyguardQuickAffordanceProvider
|
||||
|
||||
private lateinit var testScope: TestScope
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(previewRenderer.surfacePackage).thenReturn(previewSurfacePackage)
|
||||
whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer)
|
||||
whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
|
||||
|
||||
underTest = KeyguardQuickAffordanceProvider()
|
||||
val scope = CoroutineScope(IMMEDIATE)
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(testDispatcher)
|
||||
val localUserSelectionManager =
|
||||
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||
context = context,
|
||||
@@ -96,7 +118,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
)
|
||||
val remoteUserSelectionManager =
|
||||
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
userTracker = userTracker,
|
||||
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||
userHandle = UserHandle.SYSTEM,
|
||||
@@ -104,7 +126,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
val quickAffordanceRepository =
|
||||
KeyguardQuickAffordanceRepository(
|
||||
appContext = context,
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
localUserSelectionManager = localUserSelectionManager,
|
||||
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||
userTracker = userTracker,
|
||||
@@ -123,8 +145,8 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
),
|
||||
legacySettingSyncer =
|
||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||
scope = scope,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
scope = testScope.backgroundScope,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
secureSettings = FakeSettings(),
|
||||
selectionsManager = localUserSelectionManager,
|
||||
),
|
||||
@@ -148,6 +170,12 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
},
|
||||
repository = { quickAffordanceRepository },
|
||||
)
|
||||
underTest.previewManager =
|
||||
KeyguardRemotePreviewManager(
|
||||
previewRendererFactory = previewRendererFactory,
|
||||
mainDispatcher = testDispatcher,
|
||||
backgroundHandler = backgroundHandler,
|
||||
)
|
||||
|
||||
underTest.attachInfoForTesting(
|
||||
context,
|
||||
@@ -190,7 +218,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun `insert and query selection`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
|
||||
val affordanceId = AFFORDANCE_2
|
||||
val affordanceName = AFFORDANCE_2_NAME
|
||||
@@ -214,7 +242,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun `query slots`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
assertThat(querySlots())
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
@@ -232,7 +260,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun `query affordances`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
assertThat(queryAffordances())
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
@@ -252,7 +280,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun `delete and query selection`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
insertSelection(
|
||||
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
|
||||
affordanceId = AFFORDANCE_1,
|
||||
@@ -286,7 +314,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun `delete all selections in a slot`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
insertSelection(
|
||||
slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
|
||||
affordanceId = AFFORDANCE_1,
|
||||
@@ -316,6 +344,23 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun preview() =
|
||||
testScope.runTest {
|
||||
val hostToken: IBinder = mock()
|
||||
whenever(previewRenderer.hostToken).thenReturn(hostToken)
|
||||
val extras = Bundle()
|
||||
|
||||
val result = underTest.call("whatever", "anything", extras)
|
||||
|
||||
verify(previewRenderer).render()
|
||||
verify(hostToken).linkToDeath(any(), anyInt())
|
||||
assertThat(result!!).isNotNull()
|
||||
assertThat(result.get(KeyguardRemotePreviewManager.KEY_PREVIEW_SURFACE_PACKAGE))
|
||||
.isEqualTo(previewSurfacePackage)
|
||||
assertThat(result.containsKey(KeyguardRemotePreviewManager.KEY_PREVIEW_CALLBACK))
|
||||
}
|
||||
|
||||
private fun insertSelection(
|
||||
slotId: String,
|
||||
affordanceId: String,
|
||||
@@ -451,7 +496,6 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
private const val AFFORDANCE_1 = "affordance_1"
|
||||
private const val AFFORDANCE_2 = "affordance_2"
|
||||
private const val AFFORDANCE_1_NAME = "affordance_1_name"
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.common.shared.model.ContentDescription
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
|
||||
@@ -49,14 +50,10 @@ import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import kotlinx.coroutines.yield
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -78,6 +75,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: KeyguardQuickAffordanceInteractor
|
||||
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
|
||||
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
|
||||
@@ -99,7 +97,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
qrCodeScanner =
|
||||
FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
|
||||
val scope = CoroutineScope(IMMEDIATE)
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(testDispatcher)
|
||||
|
||||
val localUserSelectionManager =
|
||||
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||
@@ -120,7 +119,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
val remoteUserSelectionManager =
|
||||
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
userTracker = userTracker,
|
||||
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||
userHandle = UserHandle.SYSTEM,
|
||||
@@ -128,14 +127,14 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
val quickAffordanceRepository =
|
||||
KeyguardQuickAffordanceRepository(
|
||||
appContext = context,
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
localUserSelectionManager = localUserSelectionManager,
|
||||
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||
userTracker = userTracker,
|
||||
legacySettingSyncer =
|
||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||
scope = scope,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
scope = testScope.backgroundScope,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
secureSettings = FakeSettings(),
|
||||
selectionsManager = localUserSelectionManager,
|
||||
),
|
||||
@@ -175,88 +174,76 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
|
||||
val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
activationState = ActivationState.Active,
|
||||
fun `quickAffordance - bottom start affordance is visible`() =
|
||||
testScope.runTest {
|
||||
val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
activationState = ActivationState.Active,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
var latest: KeyguardQuickAffordanceModel? = null
|
||||
val job =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
// The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
|
||||
// produce an initial value. We yield to give the coroutine time to emit the first real
|
||||
// value from our config.
|
||||
yield()
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
)
|
||||
|
||||
assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
|
||||
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
|
||||
assertThat(visibleModel.configKey).isEqualTo(configKey)
|
||||
assertThat(visibleModel.icon).isEqualTo(ICON)
|
||||
assertThat(visibleModel.icon.contentDescription)
|
||||
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
|
||||
assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
|
||||
job.cancel()
|
||||
}
|
||||
assertThat(collectedValue())
|
||||
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
|
||||
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
|
||||
assertThat(visibleModel.configKey).isEqualTo(configKey)
|
||||
assertThat(visibleModel.icon).isEqualTo(ICON)
|
||||
assertThat(visibleModel.icon.contentDescription)
|
||||
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
|
||||
assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
|
||||
val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
||||
quickAccessWallet.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
fun `quickAffordance - bottom end affordance is visible`() =
|
||||
testScope.runTest {
|
||||
val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
||||
quickAccessWallet.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
var latest: KeyguardQuickAffordanceModel? = null
|
||||
val job =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
// The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
|
||||
// produce an initial value. We yield to give the coroutine time to emit the first real
|
||||
// value from our config.
|
||||
yield()
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
)
|
||||
|
||||
assertThat(latest).isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
|
||||
val visibleModel = latest as KeyguardQuickAffordanceModel.Visible
|
||||
assertThat(visibleModel.configKey).isEqualTo(configKey)
|
||||
assertThat(visibleModel.icon).isEqualTo(ICON)
|
||||
assertThat(visibleModel.icon.contentDescription)
|
||||
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
|
||||
assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
|
||||
job.cancel()
|
||||
}
|
||||
assertThat(collectedValue())
|
||||
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
|
||||
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
|
||||
assertThat(visibleModel.configKey).isEqualTo(configKey)
|
||||
assertThat(visibleModel.icon).isEqualTo(ICON)
|
||||
assertThat(visibleModel.icon.contentDescription)
|
||||
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
|
||||
assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
|
||||
repository.setDozing(true)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
fun `quickAffordance - bottom start affordance hidden while dozing`() =
|
||||
testScope.runTest {
|
||||
repository.setDozing(true)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
var latest: KeyguardQuickAffordanceModel? = null
|
||||
val job =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||
job.cancel()
|
||||
}
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
)
|
||||
assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
|
||||
runBlockingTest {
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(false)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
@@ -264,19 +251,45 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
)
|
||||
|
||||
var latest: KeyguardQuickAffordanceModel? = null
|
||||
val job =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
.onEach { latest = it }
|
||||
.launchIn(this)
|
||||
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||
job.cancel()
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
)
|
||||
assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `quickAffordanceAlwaysVisible - even when lock screen not showing and dozing`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(false)
|
||||
repository.setDozing(true)
|
||||
val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(
|
||||
icon = ICON,
|
||||
activationState = ActivationState.Active,
|
||||
)
|
||||
)
|
||||
|
||||
val collectedValue =
|
||||
collectLastValue(
|
||||
underTest.quickAffordanceAlwaysVisible(
|
||||
KeyguardQuickAffordancePosition.BOTTOM_START
|
||||
)
|
||||
)
|
||||
assertThat(collectedValue())
|
||||
.isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
|
||||
val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
|
||||
assertThat(visibleModel.configKey).isEqualTo(configKey)
|
||||
assertThat(visibleModel.icon).isEqualTo(ICON)
|
||||
assertThat(visibleModel.icon.contentDescription)
|
||||
.isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
|
||||
assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun select() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
|
||||
@@ -296,23 +309,18 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
)
|
||||
|
||||
var startConfig: KeyguardQuickAffordanceModel? = null
|
||||
val job1 =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
.onEach { startConfig = it }
|
||||
.launchIn(this)
|
||||
var endConfig: KeyguardQuickAffordanceModel? = null
|
||||
val job2 =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
.onEach { endConfig = it }
|
||||
.launchIn(this)
|
||||
val startConfig =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
)
|
||||
val endConfig =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
)
|
||||
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
|
||||
yield()
|
||||
yield()
|
||||
assertThat(startConfig)
|
||||
|
||||
assertThat(startConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Visible(
|
||||
configKey =
|
||||
@@ -322,7 +330,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
activationState = ActivationState.NotSupported,
|
||||
)
|
||||
)
|
||||
assertThat(endConfig)
|
||||
assertThat(endConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Hidden,
|
||||
)
|
||||
@@ -345,9 +353,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
|
||||
quickAccessWallet.key
|
||||
)
|
||||
yield()
|
||||
yield()
|
||||
assertThat(startConfig)
|
||||
|
||||
assertThat(startConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Visible(
|
||||
configKey =
|
||||
@@ -357,7 +364,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
activationState = ActivationState.NotSupported,
|
||||
)
|
||||
)
|
||||
assertThat(endConfig)
|
||||
assertThat(endConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Hidden,
|
||||
)
|
||||
@@ -377,9 +384,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
|
||||
yield()
|
||||
yield()
|
||||
assertThat(startConfig)
|
||||
|
||||
assertThat(startConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Visible(
|
||||
configKey =
|
||||
@@ -389,7 +395,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
activationState = ActivationState.NotSupported,
|
||||
)
|
||||
)
|
||||
assertThat(endConfig)
|
||||
assertThat(endConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Visible(
|
||||
configKey =
|
||||
@@ -420,14 +426,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
job1.cancel()
|
||||
job2.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unselect - one`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
|
||||
@@ -439,34 +442,23 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
|
||||
)
|
||||
|
||||
var startConfig: KeyguardQuickAffordanceModel? = null
|
||||
val job1 =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
.onEach { startConfig = it }
|
||||
.launchIn(this)
|
||||
var endConfig: KeyguardQuickAffordanceModel? = null
|
||||
val job2 =
|
||||
underTest
|
||||
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
.onEach { endConfig = it }
|
||||
.launchIn(this)
|
||||
val startConfig =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||
)
|
||||
val endConfig =
|
||||
collectLastValue(
|
||||
underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||
)
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
|
||||
yield()
|
||||
yield()
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
assertThat(startConfig)
|
||||
assertThat(startConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Hidden,
|
||||
)
|
||||
assertThat(endConfig)
|
||||
assertThat(endConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Visible(
|
||||
configKey =
|
||||
@@ -495,14 +487,12 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
|
||||
quickAccessWallet.key
|
||||
)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
assertThat(startConfig)
|
||||
assertThat(startConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Hidden,
|
||||
)
|
||||
assertThat(endConfig)
|
||||
assertThat(endConfig())
|
||||
.isEqualTo(
|
||||
KeyguardQuickAffordanceModel.Hidden,
|
||||
)
|
||||
@@ -513,14 +503,11 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
|
||||
)
|
||||
)
|
||||
|
||||
job1.cancel()
|
||||
job2.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unselect - all`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
testScope.runTest {
|
||||
featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
|
||||
homeControls.setState(
|
||||
KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
|
||||
@@ -533,15 +520,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
|
||||
yield()
|
||||
yield()
|
||||
underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
assertThat(underTest.getSelections())
|
||||
.isEqualTo(
|
||||
@@ -562,8 +542,6 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
|
||||
null,
|
||||
)
|
||||
yield()
|
||||
yield()
|
||||
|
||||
assertThat(underTest.getSelections())
|
||||
.isEqualTo(
|
||||
@@ -584,6 +562,5 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
|
||||
)
|
||||
}
|
||||
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.internal.widget.LockPatternUtils
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.Expandable
|
||||
import com.android.systemui.common.shared.model.Icon
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.doze.util.BurnInHelperWrapper
|
||||
import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags
|
||||
@@ -44,20 +45,21 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda
|
||||
import com.android.systemui.plugins.ActivityStarter
|
||||
import com.android.systemui.settings.UserFileManager
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.util.FakeSharedPreferences
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.android.systemui.util.settings.FakeSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.runBlockingTest
|
||||
import kotlinx.coroutines.yield
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -67,9 +69,9 @@ import org.mockito.ArgumentMatchers.anyString
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.Mockito.verifyZeroInteractions
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
@@ -83,6 +85,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: KeyguardBottomAreaViewModel
|
||||
|
||||
private lateinit var testScope: TestScope
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
|
||||
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
|
||||
@@ -123,7 +126,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
whenever(userTracker.userHandle).thenReturn(mock())
|
||||
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
|
||||
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
|
||||
val scope = CoroutineScope(IMMEDIATE)
|
||||
val testDispatcher = StandardTestDispatcher()
|
||||
testScope = TestScope(testDispatcher)
|
||||
val localUserSelectionManager =
|
||||
KeyguardQuickAffordanceLocalUserSelectionManager(
|
||||
context = context,
|
||||
@@ -143,7 +147,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
)
|
||||
val remoteUserSelectionManager =
|
||||
KeyguardQuickAffordanceRemoteUserSelectionManager(
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
userTracker = userTracker,
|
||||
clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker),
|
||||
userHandle = UserHandle.SYSTEM,
|
||||
@@ -151,14 +155,14 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
val quickAffordanceRepository =
|
||||
KeyguardQuickAffordanceRepository(
|
||||
appContext = context,
|
||||
scope = scope,
|
||||
scope = testScope.backgroundScope,
|
||||
localUserSelectionManager = localUserSelectionManager,
|
||||
remoteUserSelectionManager = remoteUserSelectionManager,
|
||||
userTracker = userTracker,
|
||||
legacySettingSyncer =
|
||||
KeyguardQuickAffordanceLegacySettingSyncer(
|
||||
scope = scope,
|
||||
backgroundDispatcher = IMMEDIATE,
|
||||
scope = testScope.backgroundScope,
|
||||
backgroundDispatcher = testDispatcher,
|
||||
secureSettings = FakeSettings(),
|
||||
selectionsManager = localUserSelectionManager,
|
||||
),
|
||||
@@ -194,366 +198,394 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
fun `startButton - present - visible model - starts activity on click`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.endButton.onEach { latest = it }.launchIn(this)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = null, // This will cause it to tell the system that the click was handled.
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startButton - not present - model is hidden`() = runBlockingTest {
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateButtonReveal() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
val values = mutableListOf<Boolean>()
|
||||
val job = underTest.startButton.onEach { values.add(it.animateReveal) }.launchIn(this)
|
||||
|
||||
repository.setAnimateDozingTransitions(true)
|
||||
yield()
|
||||
repository.setAnimateDozingTransitions(false)
|
||||
yield()
|
||||
|
||||
// Note the extra false value in the beginning. This is to cover for the initial value
|
||||
// inserted by the quick affordance interactor which it does to cover for config
|
||||
// implementations that don't emit an initial value.
|
||||
assertThat(values).isEqualTo(listOf(false, false, true, false))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOverlayContainerVisible() = runBlockingTest {
|
||||
val values = mutableListOf<Boolean>()
|
||||
val job = underTest.isOverlayContainerVisible.onEach(values::add).launchIn(this)
|
||||
|
||||
repository.setDozing(true)
|
||||
repository.setDozing(false)
|
||||
|
||||
assertThat(values).isEqualTo(listOf(true, false, true))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alpha() = runBlockingTest {
|
||||
val values = mutableListOf<Float>()
|
||||
val job = underTest.alpha.onEach(values::add).launchIn(this)
|
||||
|
||||
repository.setBottomAreaAlpha(0.1f)
|
||||
repository.setBottomAreaAlpha(0.5f)
|
||||
repository.setBottomAreaAlpha(0.2f)
|
||||
repository.setBottomAreaAlpha(0f)
|
||||
|
||||
assertThat(values).isEqualTo(listOf(1f, 0.1f, 0.5f, 0.2f, 0f))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isIndicationAreaPadded() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val values = mutableListOf<Boolean>()
|
||||
val job = underTest.isIndicationAreaPadded.onEach(values::add).launchIn(this)
|
||||
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = true,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
)
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig =
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startButton - in preview mode - visible even when keyguard not showing`() =
|
||||
testScope.runTest {
|
||||
underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
|
||||
repository.setKeyguardShowing(false)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val icon: Icon = mock()
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
isActivated = true,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
),
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
isActivated = true,
|
||||
icon = icon,
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
),
|
||||
configKey = configKey,
|
||||
)
|
||||
assertThat(latest()?.isSelected).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `endButton - present - visible model - do nothing on click`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.endButton)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent =
|
||||
null, // This will cause it to tell the system that the click was handled.
|
||||
)
|
||||
)
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startButton - not present - model is hidden`() =
|
||||
testScope.runTest {
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
val config =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
)
|
||||
)
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig =
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = config,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = config,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun animateButtonReveal() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
)
|
||||
|
||||
assertThat(values)
|
||||
.isEqualTo(
|
||||
listOf(
|
||||
// Initially, no button is visible so the indication area is not padded.
|
||||
false,
|
||||
// Once we add the first visible button, the indication area becomes padded.
|
||||
// This
|
||||
// continues to be true after we add the second visible button and even after we
|
||||
// make the first button not visible anymore.
|
||||
true,
|
||||
// Once both buttons are not visible, the indication area is, again, not padded.
|
||||
false,
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
val value = collectLastValue(underTest.startButton.map { it.animateReveal })
|
||||
|
||||
assertThat(value()).isFalse()
|
||||
repository.setAnimateDozingTransitions(true)
|
||||
assertThat(value()).isTrue()
|
||||
repository.setAnimateDozingTransitions(false)
|
||||
assertThat(value()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isOverlayContainerVisible() =
|
||||
testScope.runTest {
|
||||
val value = collectLastValue(underTest.isOverlayContainerVisible)
|
||||
|
||||
assertThat(value()).isTrue()
|
||||
repository.setDozing(true)
|
||||
assertThat(value()).isFalse()
|
||||
repository.setDozing(false)
|
||||
assertThat(value()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun alpha() =
|
||||
testScope.runTest {
|
||||
val value = collectLastValue(underTest.alpha)
|
||||
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
repository.setBottomAreaAlpha(0.1f)
|
||||
assertThat(value()).isEqualTo(0.1f)
|
||||
repository.setBottomAreaAlpha(0.5f)
|
||||
assertThat(value()).isEqualTo(0.5f)
|
||||
repository.setBottomAreaAlpha(0.2f)
|
||||
assertThat(value()).isEqualTo(0.2f)
|
||||
repository.setBottomAreaAlpha(0f)
|
||||
assertThat(value()).isEqualTo(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `alpha - in preview mode - does not change`() =
|
||||
testScope.runTest {
|
||||
underTest.enablePreviewMode(null)
|
||||
val value = collectLastValue(underTest.alpha)
|
||||
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
repository.setBottomAreaAlpha(0.1f)
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
repository.setBottomAreaAlpha(0.5f)
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
repository.setBottomAreaAlpha(0.2f)
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
repository.setBottomAreaAlpha(0f)
|
||||
assertThat(value()).isEqualTo(1f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isIndicationAreaPadded() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val value = collectLastValue(underTest.isIndicationAreaPadded)
|
||||
|
||||
assertThat(value()).isFalse()
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = true,
|
||||
)
|
||||
)
|
||||
assertThat(value()).isTrue()
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
)
|
||||
)
|
||||
assertThat(value()).isTrue()
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
)
|
||||
)
|
||||
assertThat(value()).isTrue()
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_END,
|
||||
testConfig =
|
||||
TestConfig(
|
||||
isVisible = false,
|
||||
)
|
||||
)
|
||||
assertThat(value()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indicationAreaTranslationX() =
|
||||
testScope.runTest {
|
||||
val value = collectLastValue(underTest.indicationAreaTranslationX)
|
||||
|
||||
assertThat(value()).isEqualTo(0f)
|
||||
repository.setClockPosition(100, 100)
|
||||
assertThat(value()).isEqualTo(100f)
|
||||
repository.setClockPosition(200, 100)
|
||||
assertThat(value()).isEqualTo(200f)
|
||||
repository.setClockPosition(200, 200)
|
||||
assertThat(value()).isEqualTo(200f)
|
||||
repository.setClockPosition(300, 100)
|
||||
assertThat(value()).isEqualTo(300f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indicationAreaTranslationY() =
|
||||
testScope.runTest {
|
||||
val value =
|
||||
collectLastValue(underTest.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET))
|
||||
|
||||
// Negative 0 - apparently there's a difference in floating point arithmetic - FML
|
||||
assertThat(value()).isEqualTo(-0f)
|
||||
val expected1 = setDozeAmountAndCalculateExpectedTranslationY(0.1f)
|
||||
assertThat(value()).isEqualTo(expected1)
|
||||
val expected2 = setDozeAmountAndCalculateExpectedTranslationY(0.2f)
|
||||
assertThat(value()).isEqualTo(expected2)
|
||||
val expected3 = setDozeAmountAndCalculateExpectedTranslationY(0.5f)
|
||||
assertThat(value()).isEqualTo(expected3)
|
||||
val expected4 = setDozeAmountAndCalculateExpectedTranslationY(1f)
|
||||
assertThat(value()).isEqualTo(expected4)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isClickable - true when alpha at threshold`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
repository.setBottomAreaAlpha(
|
||||
KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indicationAreaTranslationX() = runBlockingTest {
|
||||
val values = mutableListOf<Float>()
|
||||
val job = underTest.indicationAreaTranslationX.onEach(values::add).launchIn(this)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
|
||||
repository.setClockPosition(100, 100)
|
||||
repository.setClockPosition(200, 100)
|
||||
repository.setClockPosition(200, 200)
|
||||
repository.setClockPosition(300, 100)
|
||||
|
||||
assertThat(values).isEqualTo(listOf(0f, 100f, 200f, 300f))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun indicationAreaTranslationY() = runBlockingTest {
|
||||
val values = mutableListOf<Float>()
|
||||
val job =
|
||||
underTest
|
||||
.indicationAreaTranslationY(DEFAULT_BURN_IN_OFFSET)
|
||||
.onEach(values::add)
|
||||
.launchIn(this)
|
||||
|
||||
val expectedTranslationValues =
|
||||
listOf(
|
||||
-0f, // Negative 0 - apparently there's a difference in floating point arithmetic -
|
||||
// FML
|
||||
setDozeAmountAndCalculateExpectedTranslationY(0.1f),
|
||||
setDozeAmountAndCalculateExpectedTranslationY(0.2f),
|
||||
setDozeAmountAndCalculateExpectedTranslationY(0.5f),
|
||||
setDozeAmountAndCalculateExpectedTranslationY(1f),
|
||||
)
|
||||
|
||||
assertThat(values).isEqualTo(expectedTranslationValues)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isClickable - true when alpha at threshold`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
repository.setBottomAreaAlpha(
|
||||
KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
// The interactor has an onStart { emit(Hidden) } to cover for upstream configs that don't
|
||||
// produce an initial value. We yield to give the coroutine time to emit the first real
|
||||
// value from our config.
|
||||
yield()
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isClickable - true when alpha above threshold`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
repository.setBottomAreaAlpha(
|
||||
min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
fun `isClickable - true when alpha above threshold`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setBottomAreaAlpha(
|
||||
min(1f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD + 0.1f),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = true,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isClickable - false when alpha below threshold`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
repository.setBottomAreaAlpha(
|
||||
max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
|
||||
)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
fun `isClickable - false when alpha below threshold`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setBottomAreaAlpha(
|
||||
max(0f, KeyguardBottomAreaViewModel.AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD - 0.1f),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isClickable - false when alpha at zero`() = runBlockingTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
var latest: KeyguardQuickAffordanceViewModel? = null
|
||||
val job = underTest.startButton.onEach { latest = it }.launchIn(this)
|
||||
repository.setBottomAreaAlpha(0f)
|
||||
fun `isClickable - false when alpha at zero`() =
|
||||
testScope.runTest {
|
||||
repository.setKeyguardShowing(true)
|
||||
val latest = collectLastValue(underTest.startButton)
|
||||
repository.setBottomAreaAlpha(0f)
|
||||
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
val testConfig =
|
||||
TestConfig(
|
||||
isVisible = true,
|
||||
isClickable = false,
|
||||
icon = mock(),
|
||||
canShowWhileLocked = false,
|
||||
intent = Intent("action"),
|
||||
)
|
||||
val configKey =
|
||||
setUpQuickAffordanceModel(
|
||||
position = KeyguardQuickAffordancePosition.BOTTOM_START,
|
||||
testConfig = testConfig,
|
||||
)
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest(),
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
}
|
||||
|
||||
assertQuickAffordanceViewModel(
|
||||
viewModel = latest,
|
||||
testConfig = testConfig,
|
||||
configKey = configKey,
|
||||
)
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
private suspend fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
|
||||
private fun setDozeAmountAndCalculateExpectedTranslationY(dozeAmount: Float): Float {
|
||||
repository.setDozeAmount(dozeAmount)
|
||||
return dozeAmount * (RETURNED_BURN_IN_OFFSET - DEFAULT_BURN_IN_OFFSET)
|
||||
}
|
||||
@@ -583,7 +615,6 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
when (testConfig.isActivated) {
|
||||
true -> ActivationState.Active
|
||||
false -> ActivationState.Inactive
|
||||
null -> ActivationState.NotSupported
|
||||
}
|
||||
)
|
||||
} else {
|
||||
@@ -636,6 +667,5 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
|
||||
companion object {
|
||||
private const val DEFAULT_BURN_IN_OFFSET = 5
|
||||
private const val RETURNED_BURN_IN_OFFSET = 3
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user