Rename layout -> blueprint and add "sections"

Rename from layout to blueprint because layout is an overloaded word.
Items will not be components that we can use to determine our layout
constraints.
Also refactor blueprints to use multiple data layers for scalability.
Flag: MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA

Bug: 288464307
Test: Adjust tests and see that views in the rootview are properly
constrained.

Change-Id: I8369360614e100a37964cb869faa1a3acd694acb
This commit is contained in:
Aaron Liu
2023-07-19 13:01:41 -07:00
parent da8da31917
commit a30c678eb4
32 changed files with 1199 additions and 879 deletions

View File

@@ -25,13 +25,13 @@
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/keyguard_settings_popup_menu_background"
android:padding="12dp">
android:padding="@dimen/keyguard_settings_popup_menu_padding">
<ImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="8dp"
android:layout_width="@dimen/keyguard_settings_popup_menu_icon_height"
android:layout_height="@dimen/keyguard_settings_popup_menu_icon_width"
android:layout_marginEnd="@dimen/keyguard_settings_popup_menu_icon_end_margin"
android:tint="?androidprv:attr/materialColorOnSecondaryFixed"
android:importantForAccessibility="no"
tools:ignore="UseAppTint" />

View File

@@ -181,6 +181,10 @@ public class LockIconView extends FrameLayout implements Dumpable {
mLockIcon.setImageState(getLockIconState(mIconType, mAod), true);
}
public ImageView getLockIcon() {
return mLockIcon;
}
private void addLockIconImageView(Context context, AttributeSet attrs) {
mLockIcon = new ImageView(context, attrs);
mLockIcon.setId(R.id.lock_icon);

View File

@@ -417,7 +417,10 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
private void updateLockIconLocation() {
final float scaleFactor = mAuthController.getScaleFactor();
final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor);
if (!mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
if (mFeatureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding,
scaledPadding);
} else {
if (mUdfpsSupported) {
mView.setCenterLocation(mAuthController.getUdfpsLocation(),
mAuthController.getUdfpsRadius(), scaledPadding);

View File

@@ -54,7 +54,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.keyboard.KeyboardModule;
import com.android.systemui.keyguard.ui.view.layout.LockscreenLayoutModule;
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.log.dagger.MonitorLog;
import com.android.systemui.log.table.TableLogBuffer;
@@ -179,7 +179,7 @@ import javax.inject.Named;
GarbageMonitorModule.class,
KeyboardModule.class,
LetterboxModule.class,
LockscreenLayoutModule.class,
KeyguardBlueprintModule.class,
LogModule.class,
MediaProjectionModule.class,
MediaProjectionTaskSwitcherModule.class,

View File

@@ -26,27 +26,28 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.KeyguardAmbientIndicationAreaViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
import com.android.systemui.keyguard.ui.viewmodel.KeyguardAmbientIndicationViewModel
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManagerCommandListener
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.statusbar.KeyguardIndicationController
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.SharedNotificationContainerBinder
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNotificationContainerViewModel
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
import javax.inject.Inject
@@ -68,9 +69,8 @@ constructor(
private val notificationShadeWindowView: NotificationShadeWindowView,
private val featureFlags: FeatureFlags,
private val indicationController: KeyguardIndicationController,
private val keyguardLayoutManager: KeyguardLayoutManager,
private val keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener,
private val keyguardQuickAffordancesCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
private val keyguardQuickAffordancesCombinedViewModel:
KeyguardQuickAffordancesCombinedViewModel,
private val falsingManager: FalsingManager,
private val vibratorHelper: VibratorHelper,
private val keyguardStateController: KeyguardStateController,
@@ -78,6 +78,8 @@ constructor(
private val activityStarter: ActivityStarter,
private val occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
private val chipbarCoordinator: ChipbarCoordinator,
private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener,
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
) : CoreStartable {
private var rootViewHandle: DisposableHandle? = null
@@ -100,8 +102,8 @@ constructor(
bindAmbientIndicationArea()
bindSettingsPopupMenu()
keyguardLayoutManager.layoutViews()
keyguardLayoutManagerCommandListener.start()
KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
keyguardBlueprintCommandListener.start()
}
fun setupNotificationStackScrollLayout(legacyParent: ViewGroup) {
@@ -125,8 +127,6 @@ constructor(
leftShortcutHandle?.onConfigurationChanged()
rightShortcutHandle?.onConfigurationChanged()
ambientIndicationAreaHandle?.onConfigurationChanged()
keyguardLayoutManager.layoutViews()
}
fun bindIndicationArea() {
@@ -152,14 +152,15 @@ constructor(
private fun bindKeyguardRootView() {
rootViewHandle?.dispose()
rootViewHandle = KeyguardRootViewBinder.bind(
keyguardRootView,
keyguardRootViewModel,
featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
keyguardStateController,
)
rootViewHandle =
KeyguardRootViewBinder.bind(
keyguardRootView,
keyguardRootViewModel,
featureFlags,
occludingAppDeviceEntryMessageViewModel,
chipbarCoordinator,
keyguardStateController,
)
}
private fun bindLockIconView(legacyParent: ViewGroup) {

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2023 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.data.repository
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
/**
* Manages blueprint changes for the lockscreen.
*
* To add a blueprint, create a class that implements LockscreenBlueprint and bind it to the map in
* the dagger module:
*
* A Blueprint determines how the layout should be constrained on a high level.
*
* A Section is a modular piece of code that implements the constraints. The blueprint uses the
* sections to define the constraints.
*
* @see KeyguardBlueprintModule
*/
@SysUISingleton
class KeyguardBlueprintRepository
@Inject
constructor(
configurationRepository: ConfigurationRepository,
blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
@Application private val applicationScope: CoroutineScope,
) {
private val blueprintIdMap: Map<String, KeyguardBlueprint> = blueprints.associateBy { it.id }
private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
init {
applyBlueprint(blueprintIdMap[DEFAULT]!!)
applicationScope.launch {
configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
}
}
/**
* Emits the blueprint value to the collectors.
*
* @param blueprintId
* @return whether the transition has succeeded.
*/
fun applyBlueprint(blueprintId: String?): Boolean {
val blueprint = blueprintIdMap[blueprintId] ?: return false
applyBlueprint(blueprint)
return true
}
/** Emits the blueprint value to the collectors. */
fun applyBlueprint(blueprint: KeyguardBlueprint?) {
blueprint?.let { _blueprint.tryEmit(it) }
}
/** Re-emits the last emitted blueprint value if possible. */
fun refreshBlueprint() {
if (_blueprint.replayCache.isNotEmpty()) {
_blueprint.tryEmit(_blueprint.replayCache.last())
}
}
/** Prints all available blueprints to the PrintWriter. */
fun printBlueprints(pw: PrintWriter) {
blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
}
}
/** Determines the constraints for the ConstraintSet in the lockscreen root view. */
interface KeyguardBlueprint {
val id: String
fun apply(constraintSet: ConstraintSet)
}
/**
* Lower level modules that determine constraints for a particular section in the lockscreen root
* view.
*/
interface KeyguardSection {
fun apply(constraintSet: ConstraintSet)
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (C) 2023 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.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import javax.inject.Inject
@SysUISingleton
class KeyguardBlueprintInteractor
@Inject
constructor(private val keyguardBlueprintRepository: KeyguardBlueprintRepository) {
val blueprint = keyguardBlueprintRepository.blueprint
/**
* Transitions to a blueprint.
*
* @param blueprintId
* @return whether the transition has succeeded.
*/
fun transitionToBlueprint(blueprintId: String): Boolean {
return keyguardBlueprintRepository.applyBlueprint(blueprintId)
}
/** Re-emits the blueprint value to the collectors. */
fun refreshBlueprint() {
keyguardBlueprintRepository.refreshBlueprint()
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 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.binder
import android.os.Trace
import android.util.Log
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
class KeyguardBlueprintViewBinder {
companion object {
private const val TAG = "KeyguardBlueprintViewBinder"
fun bind(keyguardRootView: KeyguardRootView, viewModel: KeyguardBlueprintViewModel) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.blueprint.collect { blueprint ->
Trace.beginSection("KeyguardBlueprintController#applyBlueprint")
Log.d(TAG, "applying blueprint: $blueprint")
ConstraintSet().apply {
clone(keyguardRootView)
val emptyLayout = ConstraintSet.Layout()
knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
blueprint?.apply(this)
applyTo(keyguardRootView)
}
Trace.endSection()
}
}
}
}
}
}
}

View File

@@ -44,11 +44,13 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardPreviewSmartspaceViewBinder
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.KeyguardLayoutManager
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel
@@ -90,12 +92,13 @@ constructor(
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
private val featureFlags: FeatureFlags,
private val keyguardLayoutManager: KeyguardLayoutManager,
private val falsingManager: FalsingManager,
private val vibratorHelper: VibratorHelper,
private val indicationController: KeyguardIndicationController,
private val keyguardRootViewModel: KeyguardRootViewModel,
@Assisted bundle: Bundle,
private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
) {
val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -129,17 +132,17 @@ constructor(
if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
keyguardRootViewModel.enablePreviewMode(
initiallySelectedSlotId =
bundle.getString(
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
bundle.getString(
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
} else {
bottomAreaViewModel.enablePreviewMode(
initiallySelectedSlotId =
bundle.getString(
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
bundle.getString(
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
),
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
}
@@ -169,7 +172,8 @@ constructor(
),
)
setupShortcuts(keyguardRootView)
keyguardLayoutManager.layoutViews(keyguardRootView)
KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
keyguardBlueprintInteractor.refreshBlueprint()
} else {
setUpBottomArea(rootView)
}
@@ -309,7 +313,7 @@ constructor(
false,
) as KeyguardBottomAreaView
bottomAreaView.init(
viewModel = bottomAreaViewModel,
viewModel = bottomAreaViewModel,
)
parentView.addView(
bottomAreaView,

View File

@@ -19,10 +19,10 @@ package com.android.systemui.keyguard.ui.view
import android.content.Context
import android.util.AttributeSet
import androidx.constraintlayout.widget.ConstraintLayout
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.res.ResourcesCompat
import com.android.keyguard.LockIconView
import com.android.systemui.R
@@ -62,8 +62,9 @@ class KeyguardRootView(
}
private fun addLeftShortcut() {
val view = LaunchableImageView(context, attrs)
.apply {
val padding = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
val view =
LaunchableImageView(context, attrs).apply {
id = R.id.start_button
scaleType = ImageView.ScaleType.FIT_CENTER
background =
@@ -79,13 +80,15 @@ class KeyguardRootView(
context.theme
)
visibility = View.INVISIBLE
setPadding(padding, padding, padding, padding)
}
addView(view)
}
private fun addRightShortcut() {
val view = LaunchableImageView(context, attrs)
.apply {
val padding = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
val view =
LaunchableImageView(context, attrs).apply {
id = R.id.end_button
scaleType = ImageView.ScaleType.FIT_CENTER
background =
@@ -101,20 +104,19 @@ class KeyguardRootView(
context.theme
)
visibility = View.INVISIBLE
setPadding(padding, padding, padding, padding)
}
addView(view)
}
private fun addSettingsPopupMenu() {
val view = LayoutInflater.from(context).inflate(
R.layout.keyguard_settings_popup_menu,
this,
false
)
.apply {
id = R.id.keyguard_settings_button
visibility = GONE
}
val view =
LayoutInflater.from(context)
.inflate(R.layout.keyguard_settings_popup_menu, this, false)
.apply {
id = R.id.keyguard_settings_button
visibility = GONE
}
addView(view)
}
}

View File

@@ -1,286 +0,0 @@
/*
* Copyright (C) 2023 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.view.layout
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.ViewGroup.MarginLayoutParams
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import javax.inject.Inject
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
@SysUISingleton
class DefaultLockscreenLayout
@Inject
constructor(
private val authController: AuthController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val windowManager: WindowManager,
private val context: Context,
) : LockscreenLayout {
override val id: String = DEFAULT
override fun layoutIndicationArea(rootView: KeyguardRootView) {
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
rootView.getConstraintSet().apply {
constrainWidth(indicationArea.id, MATCH_PARENT)
constrainHeight(indicationArea.id, WRAP_CONTENT)
connect(
indicationArea.id,
BOTTOM,
PARENT_ID,
BOTTOM,
R.dimen.keyguard_indication_margin_bottom.dp()
)
connect(indicationArea.id, START, PARENT_ID, START)
connect(indicationArea.id, END, PARENT_ID, END)
applyTo(rootView)
}
}
override fun layoutLockIcon(rootView: KeyguardRootView) {
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
val bounds = windowManager.currentWindowMetrics.bounds
val widthPixels = bounds.right.toFloat()
val heightPixels = bounds.bottom.toFloat()
val defaultDensity =
DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
DisplayMetrics.DENSITY_DEFAULT.toFloat()
val lockIconRadiusPx = (defaultDensity * 36).toInt()
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
}
} else {
centerLockIcon(
Point(
(widthPixels / 2).toInt(),
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
),
lockIconRadiusPx * scaleFactor,
scaledPadding,
rootView
)
}
}
@VisibleForTesting
internal fun centerLockIcon(
center: Point,
radius: Float,
drawablePadding: Int,
rootView: KeyguardRootView,
) {
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
val sensorRect =
Rect().apply {
set(
center.x - radius.toInt(),
center.y - radius.toInt(),
center.x + radius.toInt(),
center.y + radius.toInt(),
)
}
rootView.getConstraintSet().apply {
constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
applyTo(rootView)
}
}
override fun layoutShortcuts(rootView: KeyguardRootView) {
val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
val width =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
val horizontalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
val verticalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
val padding =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
leftShortcut.apply {
updateLayoutParams<MarginLayoutParams> {
marginStart = horizontalOffsetMargin
bottomMargin = verticalOffsetMargin
}
setPadding(padding)
}
rightShortcut.apply {
updateLayoutParams<MarginLayoutParams> {
marginEnd = horizontalOffsetMargin
bottomMargin = verticalOffsetMargin
}
setPadding(padding)
}
rootView.getConstraintSet().apply {
constrainWidth(leftShortcut.id, width)
constrainHeight(leftShortcut.id, height)
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
constrainWidth(rightShortcut.id, width)
constrainHeight(rightShortcut.id, height)
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
applyTo(rootView)
}
}
override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
val ambientIndicationContainer =
rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
rootView.getConstraintSet().apply {
constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
if (keyguardUpdateMonitor.isUdfpsSupported) {
//constrain below udfps and above indication area
constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
} else {
//constrain above lock icon
constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
}
applyTo(rootView)
}
}
override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
val popupMenu =
rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
val horizontalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
icon.updateLayoutParams<LinearLayout.LayoutParams> {
height =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
width =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
marginEnd =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
}
textView.updateLayoutParams<LinearLayout.LayoutParams> {
height = WRAP_CONTENT
width = WRAP_CONTENT
}
popupMenu.updateLayoutParams<MarginLayoutParams> {
bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
marginStart = horizontalOffsetMargin
marginEnd = horizontalOffsetMargin
}
popupMenu.setPadding(
context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
)
rootView.getConstraintSet().apply {
constrainWidth(popupMenu.id, WRAP_CONTENT)
constrainHeight(popupMenu.id, WRAP_CONTENT)
constrainMinHeight(
popupMenu.id,
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
)
connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
applyTo(rootView)
}
}
private fun Int.dp(): Int {
return context.resources.getDimensionPixelSize(this)
}
private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
ConstraintSet().also {
it.clone(this)
}
companion object {
const val DEFAULT = "default"
}
}

View File

@@ -16,17 +16,20 @@
package com.android.systemui.keyguard.ui.view.layout
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import java.io.PrintWriter
import javax.inject.Inject
/** Uses $ adb shell cmd statusbar layout <LayoutId> */
class KeyguardLayoutManagerCommandListener
/** Uses $ adb shell cmd statusbar blueprint <BlueprintId> */
class KeyguardBlueprintCommandListener
@Inject
constructor(
private val commandRegistry: CommandRegistry,
private val keyguardLayoutManager: KeyguardLayoutManager
private val keyguardBlueprintRepository: KeyguardBlueprintRepository,
private val keyguardBlueprintInteractor: KeyguardBlueprintInteractor,
) {
private val layoutCommand = KeyguardLayoutManagerCommand()
@@ -42,22 +45,22 @@ constructor(
return
}
if (keyguardLayoutManager.transitionToLayout(arg)) {
if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
pw.println("Transition succeeded!")
} else {
pw.println("Invalid argument! To see available layout ids, run:")
pw.println("$ adb shell cmd statusbar layout help")
pw.println("Invalid argument! To see available blueprint ids, run:")
pw.println("$ adb shell cmd statusbar blueprint help")
}
}
override fun help(pw: PrintWriter) {
pw.println("Usage: $ adb shell cmd statusbar layout <layoutId>")
pw.println("Existing Layout Ids: ")
keyguardLayoutManager.layoutIdMap.forEach { entry -> pw.println("${entry.key}") }
pw.println("Usage: $ adb shell cmd statusbar blueprint <blueprintId>")
pw.println("Existing Blueprint Ids: ")
keyguardBlueprintRepository.printBlueprints(pw)
}
}
companion object {
internal const val COMMAND = "layout"
internal const val COMMAND = "blueprint"
}
}

View File

@@ -1,97 +0,0 @@
/*
* Copyright (C) 2023 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.view.layout
import android.content.res.Configuration
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
import com.android.systemui.statusbar.policy.ConfigurationController
import javax.inject.Inject
/**
* Manages layout changes for the lockscreen.
*
* To add a layout, add an entry to the map with a unique id and call #transitionToLayout(string).
*/
@SysUISingleton
class KeyguardLayoutManager
@Inject
constructor(
configurationController: ConfigurationController,
layouts: Set<@JvmSuppressWildcards LockscreenLayout>,
private val keyguardRootView: KeyguardRootView,
) {
internal val layoutIdMap: Map<String, LockscreenLayout> = layouts.associateBy { it.id }
private var layout: LockscreenLayout? = layoutIdMap[DEFAULT]
init {
configurationController.addCallback(
object : ConfigurationController.ConfigurationListener {
override fun onConfigChanged(newConfig: Configuration?) {
layoutViews()
}
}
)
}
/**
* Transitions to a layout.
*
* @param layoutId
* @return whether the transition has succeeded.
*/
fun transitionToLayout(layoutId: String): Boolean {
layout = layoutIdMap[layoutId] ?: return false
layoutViews()
return true
}
fun layoutViews(rootView: KeyguardRootView = keyguardRootView) {
layout?.layoutViews(rootView)
}
companion object {
const val TAG = "KeyguardLayoutManager"
}
}
interface LockscreenLayout {
val id: String
fun layoutViews(rootView: KeyguardRootView) {
// Clear constraints.
ConstraintSet()
.apply {
clone(rootView)
knownIds.forEach { getConstraint(it).layout.copyFrom(ConstraintSet.Layout()) }
}
.applyTo(rootView)
layoutIndicationArea(rootView)
layoutLockIcon(rootView)
layoutShortcuts(rootView)
layoutAmbientIndicationArea(rootView)
layoutSettingsPopupMenu(rootView)
}
fun layoutIndicationArea(rootView: KeyguardRootView)
fun layoutLockIcon(rootView: KeyguardRootView)
fun layoutShortcuts(rootView: KeyguardRootView)
fun layoutAmbientIndicationArea(rootView: KeyguardRootView)
fun layoutSettingsPopupMenu(rootView: KeyguardRootView)
}

View File

@@ -1,305 +0,0 @@
/*
* Copyright (C) 2023 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.view.layout
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.animation.view.LaunchableLinearLayout
import com.android.systemui.biometrics.AuthController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import javax.inject.Inject
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
@SysUISingleton
class ShortcutsBesideUdfpsLockscreenLayout
@Inject
constructor(
private val authController: AuthController,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val windowManager: WindowManager,
private val context: Context,
) : LockscreenLayout {
override val id: String = SHORTCUTS_BESIDE_UDFPS
override fun layoutIndicationArea(rootView: KeyguardRootView) {
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
rootView.getConstraintSet().apply {
constrainWidth(indicationArea.id, MATCH_PARENT)
constrainHeight(indicationArea.id, WRAP_CONTENT)
connect(
indicationArea.id,
BOTTOM,
PARENT_ID,
BOTTOM,
R.dimen.keyguard_indication_margin_bottom.dp()
)
connect(indicationArea.id, START, PARENT_ID, START)
connect(indicationArea.id, END, PARENT_ID, END)
applyTo(rootView)
}
}
override fun layoutLockIcon(rootView: KeyguardRootView) {
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx = R.dimen.lock_icon_margin_bottom.dp()
val mDefaultPaddingPx = R.dimen.lock_icon_padding.dp()
val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
val bounds = windowManager.currentWindowMetrics.bounds
val widthPixels = bounds.right.toFloat()
val heightPixels = bounds.bottom.toFloat()
val defaultDensity =
DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
DisplayMetrics.DENSITY_DEFAULT.toFloat()
val lockIconRadiusPx = (defaultDensity * 36).toInt()
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
centerLockIcon(udfpsLocation, authController.udfpsRadius, scaledPadding, rootView)
}
} else {
centerLockIcon(
Point(
(widthPixels / 2).toInt(),
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
),
lockIconRadiusPx * scaleFactor,
scaledPadding,
rootView
)
}
}
@VisibleForTesting
internal fun centerLockIcon(
center: Point,
radius: Float,
drawablePadding: Int,
rootView: KeyguardRootView,
) {
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val lockIcon = lockIconView.findViewById<View>(R.id.lock_icon) ?: return
lockIcon.setPadding(drawablePadding, drawablePadding, drawablePadding, drawablePadding)
val sensorRect =
Rect().apply {
set(
center.x - radius.toInt(),
center.y - radius.toInt(),
center.x + radius.toInt(),
center.y + radius.toInt(),
)
}
rootView.getConstraintSet().apply {
constrainWidth(lockIconView.id, sensorRect.right - sensorRect.left)
constrainHeight(lockIconView.id, sensorRect.bottom - sensorRect.top)
connect(lockIconView.id, TOP, PARENT_ID, TOP, sensorRect.top)
connect(lockIconView.id, START, PARENT_ID, START, sensorRect.left)
applyTo(rootView)
}
}
override fun layoutShortcuts(rootView: KeyguardRootView) {
val leftShortcut = rootView.findViewById<View>(R.id.start_button) ?: return
val rightShortcut = rootView.findViewById<View>(R.id.end_button) ?: return
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val udfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val width =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
val horizontalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
val verticalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
val padding =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_padding)
if (!udfpsSupported) {
leftShortcut.apply {
updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginStart = horizontalOffsetMargin
bottomMargin = verticalOffsetMargin
}
setPadding(padding)
}
rightShortcut.apply {
updateLayoutParams<ViewGroup.MarginLayoutParams> {
marginEnd = horizontalOffsetMargin
bottomMargin = verticalOffsetMargin
}
setPadding(padding)
}
}
rootView.getConstraintSet().apply {
if (udfpsSupported) {
constrainWidth(leftShortcut.id, width)
constrainHeight(leftShortcut.id, height)
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
connect(leftShortcut.id, RIGHT, lockIconView.id, LEFT)
connect(leftShortcut.id, TOP, lockIconView.id, TOP)
connect(leftShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
constrainWidth(rightShortcut.id, width)
constrainHeight(rightShortcut.id, height)
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
connect(rightShortcut.id, LEFT, lockIconView.id, RIGHT)
connect(rightShortcut.id, TOP, lockIconView.id, TOP)
connect(rightShortcut.id, BOTTOM, lockIconView.id, BOTTOM)
} else {
constrainWidth(leftShortcut.id, width)
constrainHeight(leftShortcut.id, height)
connect(leftShortcut.id, LEFT, PARENT_ID, LEFT)
connect(leftShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
constrainWidth(rightShortcut.id, width)
constrainHeight(rightShortcut.id, height)
connect(rightShortcut.id, RIGHT, PARENT_ID, RIGHT)
connect(rightShortcut.id, BOTTOM, PARENT_ID, BOTTOM)
}
applyTo(rootView)
}
}
override fun layoutAmbientIndicationArea(rootView: KeyguardRootView) {
val ambientIndicationContainer =
rootView.findViewById<View>(R.id.ambient_indication_container) ?: return
val lockIconView = rootView.findViewById<View>(R.id.lock_icon_view) ?: return
val indicationArea = rootView.findViewById<View>(R.id.keyguard_indication_area) ?: return
rootView.getConstraintSet().apply {
constrainWidth(ambientIndicationContainer.id, MATCH_PARENT)
if (keyguardUpdateMonitor.isUdfpsSupported) {
//constrain below udfps and above indication area
constrainHeight(ambientIndicationContainer.id, MATCH_CONSTRAINT)
connect(ambientIndicationContainer.id, TOP, lockIconView.id, BOTTOM)
connect(ambientIndicationContainer.id, BOTTOM, indicationArea.id, TOP)
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
} else {
//constrain above lock icon
constrainHeight(ambientIndicationContainer.id, WRAP_CONTENT)
connect(ambientIndicationContainer.id, BOTTOM, lockIconView.id, TOP)
connect(ambientIndicationContainer.id, LEFT, PARENT_ID, LEFT)
connect(ambientIndicationContainer.id, RIGHT, PARENT_ID, RIGHT)
}
applyTo(rootView)
}
}
override fun layoutSettingsPopupMenu(rootView: KeyguardRootView) {
val popupMenu =
rootView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button) ?: return
val icon = popupMenu.findViewById<ImageView>(R.id.icon) ?: return
val textView = popupMenu.findViewById<TextView>(R.id.text) ?: return
val horizontalOffsetMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
icon.updateLayoutParams<LinearLayout.LayoutParams> {
height =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_height)
width =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_width)
marginEnd =
context
.resources
.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_icon_end_margin)
}
textView.updateLayoutParams<LinearLayout.LayoutParams> {
height = WRAP_CONTENT
width = WRAP_CONTENT
}
popupMenu.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
marginStart = horizontalOffsetMargin
marginEnd = horizontalOffsetMargin
}
popupMenu.setPadding(
context.resources.getDimensionPixelSize(R.dimen.keyguard_settings_popup_menu_padding)
)
rootView.getConstraintSet().apply {
constrainWidth(popupMenu.id, WRAP_CONTENT)
constrainHeight(popupMenu.id, WRAP_CONTENT)
constrainMinHeight(
popupMenu.id,
context.resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
)
connect(popupMenu.id, LEFT, PARENT_ID, LEFT)
connect(popupMenu.id, RIGHT, PARENT_ID, RIGHT)
connect(popupMenu.id, BOTTOM, PARENT_ID, BOTTOM)
applyTo(rootView)
}
}
private fun Int.dp(): Int {
return context.resources.getDimensionPixelSize(this)
}
private fun ConstraintLayout.getConstraintSet(): ConstraintSet =
ConstraintSet().also {
it.clone(this)
}
companion object {
const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2023 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.view.layout.blueprints
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import javax.inject.Inject
/**
* Positions elements of the lockscreen to the default position.
*
* This will be the most common use case for phones in portrait mode.
*/
@SysUISingleton
@JvmSuppressWildcards
class DefaultKeyguardBlueprint
@Inject
constructor(
private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
private val defaultLockIconSection: DefaultLockIconSection,
private val defaultShortcutsSection: DefaultShortcutsSection,
private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
) : KeyguardBlueprint {
override val id: String = DEFAULT
override fun apply(constraintSet: ConstraintSet) {
defaultIndicationAreaSection.apply(constraintSet)
defaultLockIconSection.apply(constraintSet)
defaultShortcutsSection.apply(constraintSet)
defaultAmbientIndicationAreaSection.apply(constraintSet)
defaultSettingsPopupMenuSection.apply(constraintSet)
}
companion object {
const val DEFAULT = "default"
}
}

View File

@@ -15,23 +15,24 @@
*
*/
package com.android.systemui.keyguard.ui.view.layout
package com.android.systemui.keyguard.ui.view.layout.blueprints
import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@Module
abstract class LockscreenLayoutModule {
abstract class KeyguardBlueprintModule {
@Binds
@IntoSet
abstract fun bindDefaultLayout(
defaultLockscreenLayout: DefaultLockscreenLayout
): LockscreenLayout
abstract fun bindDefaultBlueprint(
defaultLockscreenBlueprint: DefaultKeyguardBlueprint
): KeyguardBlueprint
@Binds
@IntoSet
abstract fun bindShortcutsBesideUdfpsLockscreenLayout(
shortcutsBesideUdfpsLockscreenLayout: ShortcutsBesideUdfpsLockscreenLayout
): LockscreenLayout
abstract fun bindShortcutsBesideUdfpsLockscreenBlueprint(
shortcutsBesideUdfpsLockscreenBlueprint: ShortcutsBesideUdfpsKeyguardBlueprint
): KeyguardBlueprint
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 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.view.layout.blueprints
import androidx.constraintlayout.widget.ConstraintSet
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdfpsSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import javax.inject.Inject
/** Vertically aligns the shortcuts with the udfps. */
@SysUISingleton
class ShortcutsBesideUdfpsKeyguardBlueprint
@Inject
constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val defaultIndicationAreaSection: DefaultIndicationAreaSection,
private val defaultLockIconSection: DefaultLockIconSection,
private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
private val defaultShortcutsSection: DefaultShortcutsSection,
) : KeyguardBlueprint {
override val id: String = SHORTCUTS_BESIDE_UDFPS
override fun apply(constraintSet: ConstraintSet) {
defaultIndicationAreaSection.apply(constraintSet)
defaultLockIconSection.apply(constraintSet)
defaultAmbientIndicationAreaSection.apply(constraintSet)
defaultSettingsPopupMenuSection.apply(constraintSet)
if (keyguardUpdateMonitor.isUdfpsSupported) {
alignShortcutsToUdfpsSection.apply(constraintSet)
} else {
defaultShortcutsSection.apply(constraintSet)
}
}
companion object {
const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.content.res.Resources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class AlignShortcutsToUdfpsSection @Inject constructor(@Main private val resources: Resources) :
KeyguardSection {
override fun apply(constraintSet: ConstraintSet) {
val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
constraintSet.apply {
constrainWidth(R.id.start_button, width)
constrainHeight(R.id.start_button, height)
connect(R.id.start_button, LEFT, PARENT_ID, LEFT)
connect(R.id.start_button, RIGHT, R.id.lock_icon_view, LEFT)
connect(R.id.start_button, TOP, R.id.lock_icon_view, TOP)
connect(R.id.start_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
constrainWidth(R.id.end_button, width)
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT)
connect(R.id.end_button, LEFT, R.id.lock_icon_view, RIGHT)
connect(R.id.end_button, TOP, R.id.lock_icon_view, TOP)
connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class DefaultAmbientIndicationAreaSection
@Inject
constructor(private val keyguardUpdateMonitor: KeyguardUpdateMonitor) : KeyguardSection {
override fun apply(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(R.id.ambient_indication_container, MATCH_PARENT)
if (keyguardUpdateMonitor.isUdfpsSupported) {
// constrain below udfps and above indication area
constrainHeight(R.id.ambient_indication_container, MATCH_CONSTRAINT)
connect(R.id.ambient_indication_container, TOP, R.id.lock_icon_view, BOTTOM)
connect(
R.id.ambient_indication_container,
BOTTOM,
R.id.keyguard_indication_area,
TOP
)
connect(R.id.ambient_indication_container, START, PARENT_ID, START)
connect(R.id.ambient_indication_container, END, PARENT_ID, END)
} else {
// constrain above lock icon
constrainHeight(R.id.ambient_indication_container, WRAP_CONTENT)
connect(R.id.ambient_indication_container, BOTTOM, R.id.lock_icon_view, TOP)
connect(R.id.ambient_indication_container, START, PARENT_ID, START)
connect(R.id.ambient_indication_container, END, PARENT_ID, END)
}
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.content.Context
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.R
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class DefaultIndicationAreaSection @Inject constructor(private val context: Context) :
KeyguardSection {
private val indicationAreaViewId = R.id.keyguard_indication_area
override fun apply(constraintSet: ConstraintSet) {
constraintSet.apply {
constrainWidth(indicationAreaViewId, ViewGroup.LayoutParams.MATCH_PARENT)
constrainHeight(indicationAreaViewId, ViewGroup.LayoutParams.WRAP_CONTENT)
connect(
indicationAreaViewId,
ConstraintSet.BOTTOM,
ConstraintSet.PARENT_ID,
ConstraintSet.BOTTOM,
context.resources.getDimensionPixelSize(R.dimen.keyguard_indication_margin_bottom)
)
connect(
indicationAreaViewId,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START
)
connect(
indicationAreaViewId,
ConstraintSet.END,
ConstraintSet.PARENT_ID,
ConstraintSet.END
)
}
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.content.Context
import android.graphics.Point
import android.graphics.Rect
import android.util.DisplayMetrics
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class DefaultLockIconSection
@Inject
constructor(
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val authController: AuthController,
private val windowManager: WindowManager,
private val context: Context,
) : KeyguardSection {
private val lockIconViewId = R.id.lock_icon_view
override fun apply(constraintSet: ConstraintSet) {
val isUdfpsSupported = keyguardUpdateMonitor.isUdfpsSupported
val scaleFactor: Float = authController.scaleFactor
val mBottomPaddingPx =
context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom)
val mDefaultPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_padding)
val scaledPadding: Int = (mDefaultPaddingPx * scaleFactor).toInt()
val bounds = windowManager.currentWindowMetrics.bounds
val widthPixels = bounds.right.toFloat()
val heightPixels = bounds.bottom.toFloat()
val defaultDensity =
DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() /
DisplayMetrics.DENSITY_DEFAULT.toFloat()
val lockIconRadiusPx = (defaultDensity * 36).toInt()
if (isUdfpsSupported) {
authController.udfpsLocation?.let { udfpsLocation ->
centerLockIcon(
udfpsLocation,
authController.udfpsRadius,
scaledPadding,
constraintSet
)
}
} else {
centerLockIcon(
Point(
(widthPixels / 2).toInt(),
(heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt()
),
lockIconRadiusPx * scaleFactor,
scaledPadding,
constraintSet,
)
}
}
@VisibleForTesting
internal fun centerLockIcon(
center: Point,
radius: Float,
drawablePadding: Int,
constraintSet: ConstraintSet
) {
val sensorRect =
Rect().apply {
set(
center.x - radius.toInt(),
center.y - radius.toInt(),
center.x + radius.toInt(),
center.y + radius.toInt(),
)
}
constraintSet.apply {
constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left)
constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top)
connect(
lockIconViewId,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
sensorRect.top
)
connect(
lockIconViewId,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
sensorRect.left
)
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.content.res.Resources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class DefaultSettingsPopupMenuSection @Inject constructor(@Main private val resources: Resources) :
KeyguardSection {
override fun apply(constraintSet: ConstraintSet) {
val horizontalOffsetMargin =
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
constraintSet.apply {
constrainWidth(R.id.keyguard_settings_button, WRAP_CONTENT)
constrainHeight(R.id.keyguard_settings_button, WRAP_CONTENT)
constrainMinHeight(
R.id.keyguard_settings_button,
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
)
connect(R.id.keyguard_settings_button, START, PARENT_ID, START, horizontalOffsetMargin)
connect(R.id.keyguard_settings_button, END, PARENT_ID, END, horizontalOffsetMargin)
connect(
R.id.keyguard_settings_button,
BOTTOM,
PARENT_ID,
BOTTOM,
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
)
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.content.res.Resources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.data.repository.KeyguardSection
import javax.inject.Inject
class DefaultShortcutsSection @Inject constructor(@Main private val resources: Resources) :
KeyguardSection {
override fun apply(constraintSet: ConstraintSet) {
val width = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width)
val height = resources.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height)
val horizontalOffsetMargin =
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_horizontal_offset)
val verticalOffsetMargin =
resources.getDimensionPixelSize(R.dimen.keyguard_affordance_vertical_offset)
constraintSet.apply {
constrainWidth(R.id.start_button, width)
constrainHeight(R.id.start_button, height)
connect(R.id.start_button, LEFT, PARENT_ID, LEFT, horizontalOffsetMargin)
connect(R.id.start_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
constrainWidth(R.id.end_button, width)
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
}
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 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.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import javax.inject.Inject
@SysUISingleton
class KeyguardBlueprintViewModel
@Inject
constructor(keyguardBlueprintInteractor: KeyguardBlueprintInteractor) {
val blueprint = keyguardBlueprintInteractor.blueprint
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2023 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.data.repository
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint
import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
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.Mock
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintRepository
@Mock lateinit var configurationRepository: ConfigurationRepository
@Mock lateinit var defaultLockscreenBlueprint: DefaultKeyguardBlueprint
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private val configurationFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(defaultLockscreenBlueprint.id).thenReturn(DEFAULT)
whenever(configurationRepository.onAnyConfigurationChange).thenReturn(configurationFlow)
underTest =
KeyguardBlueprintRepository(
configurationRepository,
setOf(defaultLockscreenBlueprint),
testScope.backgroundScope,
)
}
@Test
fun testApplyBlueprint_DefaultLayout() {
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
runCurrent()
underTest.applyBlueprint(defaultLockscreenBlueprint)
runCurrent()
assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
}
}
@Test
fun testConfigurationChange() {
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
runCurrent()
configurationFlow.tryEmit(Unit)
runCurrent()
assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
}
}
@Test
fun testRefreshBlueprint() {
testScope.runTest {
val blueprint by collectLastValue(underTest.blueprint)
runCurrent()
underTest.refreshBlueprint()
runCurrent()
assertThat(blueprint).isEqualTo(defaultLockscreenBlueprint)
}
}
@Test
fun testTransitionToLayout_validId() {
assertThat(underTest.applyBlueprint(DEFAULT)).isTrue()
}
@Test
fun testTransitionToLayout_invalidId() {
assertThat(underTest.applyBlueprint("abc")).isFalse()
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2023 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.domain.interactor
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardBlueprintInteractorTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintInteractor
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
underTest = KeyguardBlueprintInteractor(keyguardBlueprintRepository)
}
@Test
fun testRefreshBlueprint() {
underTest.refreshBlueprint()
verify(keyguardBlueprintRepository).refreshBlueprint()
}
@Test
fun testTransitionToBlueprint() {
underTest.transitionToBlueprint("abc")
verify(keyguardBlueprintRepository).applyBlueprint("abc")
}
}

View File

@@ -18,10 +18,11 @@ package com.android.systemui.keyguard.ui.view.layout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import java.io.PrintWriter
import org.junit.Before
@@ -31,32 +32,33 @@ import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() {
private lateinit var keyguardLayoutManagerCommandListener: KeyguardLayoutManagerCommandListener
class KeyguardBlueprintCommandListenerTest : SysuiTestCase() {
private lateinit var keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener
@Mock private lateinit var commandRegistry: CommandRegistry
@Mock private lateinit var keyguardLayoutManager: KeyguardLayoutManager
@Mock private lateinit var keyguardBlueprintRepository: KeyguardBlueprintRepository
@Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
@Mock private lateinit var pw: PrintWriter
private lateinit var command: () -> Command
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
keyguardLayoutManagerCommandListener =
KeyguardLayoutManagerCommandListener(
keyguardBlueprintCommandListener =
KeyguardBlueprintCommandListener(
commandRegistry,
keyguardLayoutManager,
keyguardBlueprintRepository,
keyguardBlueprintInteractor,
)
keyguardLayoutManagerCommandListener.start()
keyguardBlueprintCommandListener.start()
command =
withArgCaptor<() -> Command> {
verify(commandRegistry).registerCommand(eq("layout"), capture())
verify(commandRegistry).registerCommand(eq("blueprint"), capture())
}
}
@@ -64,25 +66,19 @@ class KeyguardLayoutManagerCommandListenerTest : SysuiTestCase() {
fun testHelp() {
command().execute(pw, listOf("help"))
verify(pw, atLeastOnce()).println(anyString())
verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
}
@Test
fun testBlank() {
command().execute(pw, listOf())
verify(pw, atLeastOnce()).println(anyString())
verify(keyguardLayoutManager, never()).transitionToLayout(anyString())
verify(keyguardBlueprintInteractor, never()).transitionToBlueprint(anyString())
}
@Test
fun testValidArg() {
bindFakeIdMapToLayoutManager()
command().execute(pw, listOf("fake"))
verify(keyguardLayoutManager).transitionToLayout("fake")
}
private fun bindFakeIdMapToLayoutManager() {
val map = mapOf("fake" to mock(LockscreenLayout::class.java))
whenever(keyguardLayoutManager.layoutIdMap).thenReturn(map)
verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright (C) 2023 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.view.layout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.DefaultLockscreenLayout.Companion.DEFAULT
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class KeyguardLayoutManagerTest : SysuiTestCase() {
private lateinit var keyguardLayoutManager: KeyguardLayoutManager
@Mock lateinit var configurationController: ConfigurationController
@Mock lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
@Mock lateinit var keyguardRootView: KeyguardRootView
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
whenever(defaultLockscreenLayout.id).thenReturn(DEFAULT)
keyguardLayoutManager =
KeyguardLayoutManager(
configurationController,
setOf(defaultLockscreenLayout),
keyguardRootView
)
}
@Test
fun testDefaultLayout() {
keyguardLayoutManager.transitionToLayout(DEFAULT)
verify(defaultLockscreenLayout).layoutViews(keyguardRootView)
}
@Test
fun testTransitionToLayout_validId() {
assertThat(keyguardLayoutManager.transitionToLayout(DEFAULT)).isTrue()
}
@Test
fun testTransitionToLayout_invalidId() {
assertThat(keyguardLayoutManager.transitionToLayout("abc")).isFalse()
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2023 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.view.layout.blueprints
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class DefaultKeyguardBlueprintTest : SysuiTestCase() {
private lateinit var underTest: DefaultKeyguardBlueprint
private lateinit var rootView: KeyguardRootView
@Mock private lateinit var defaultIndicationAreaSection: DefaultIndicationAreaSection
@Mock private lateinit var defaultLockIconSection: DefaultLockIconSection
@Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection
@Mock
private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
@Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
rootView = KeyguardRootView(context, null)
underTest =
DefaultKeyguardBlueprint(
defaultIndicationAreaSection,
defaultLockIconSection,
defaultShortcutsSection,
defaultAmbientIndicationAreaSection,
defaultSettingsPopupMenuSection,
)
}
@Test
fun apply() {
val cs = ConstraintSet()
underTest.apply(cs)
verify(defaultIndicationAreaSection).apply(cs)
verify(defaultLockIconSection).apply(cs)
verify(defaultShortcutsSection).apply(cs)
verify(defaultAmbientIndicationAreaSection).apply(cs)
verify(defaultSettingsPopupMenuSection).apply(cs)
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 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.view.layout.sections
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
@SmallTest
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
private val underTest = DefaultIndicationAreaSection(context)
@Test
fun apply() {
val cs = ConstraintSet()
underTest.apply(cs)
val constraint = cs.getConstraint(R.id.keyguard_indication_area)
assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
}
}

View File

@@ -15,10 +15,9 @@
*
*/
package com.android.systemui.keyguard.ui.view.layout
package com.android.systemui.keyguard.ui.view.layout.sections
import android.graphics.Point
import android.view.ViewGroup
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintSet
import androidx.test.filters.SmallTest
@@ -26,9 +25,6 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.AuthController
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.ui.view.KeyguardRootView
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -40,45 +36,36 @@ import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class DefaultLockscreenLayoutTest : SysuiTestCase() {
private lateinit var defaultLockscreenLayout: DefaultLockscreenLayout
private lateinit var rootView: KeyguardRootView
class DefaultLockIconSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var authController: AuthController
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var windowManager: WindowManager
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: DefaultLockIconSection
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
rootView = KeyguardRootView(context, null)
defaultLockscreenLayout =
DefaultLockscreenLayout(authController, keyguardUpdateMonitor, windowManager, context)
underTest =
DefaultLockIconSection(keyguardUpdateMonitor, authController, windowManager, context)
}
@Test
fun testLayoutViews_KeyguardIndicationArea() {
defaultLockscreenLayout.layoutViews(rootView)
val constraint = getViewConstraint(R.id.keyguard_indication_area)
assertThat(constraint.layout.bottomToBottom).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.mWidth).isEqualTo(ViewGroup.LayoutParams.MATCH_PARENT)
assertThat(constraint.layout.mHeight).isEqualTo(ViewGroup.LayoutParams.WRAP_CONTENT)
}
fun apply() {
val cs = ConstraintSet()
underTest.apply(cs)
val constraint = cs.getConstraint(R.id.lock_icon_view)
@Test
fun testLayoutViews_lockIconView() {
defaultLockscreenLayout.layoutViews(rootView)
val constraint = getViewConstraint(R.id.lock_icon_view)
assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID)
assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID)
}
@Test
fun testCenterLockIcon() {
defaultLockscreenLayout.centerLockIcon(Point(5, 6), 1F, 5, rootView)
val constraint = getViewConstraint(R.id.lock_icon_view)
val cs = ConstraintSet()
underTest.centerLockIcon(Point(5, 6), 1F, 5, cs)
val constraint = cs.getConstraint(R.id.lock_icon_view)
assertThat(constraint.layout.mWidth).isEqualTo(2)
assertThat(constraint.layout.mHeight).isEqualTo(2)
@@ -87,11 +74,4 @@ class DefaultLockscreenLayoutTest : SysuiTestCase() {
assertThat(constraint.layout.topMargin).isEqualTo(5)
assertThat(constraint.layout.startMargin).isEqualTo(4)
}
/** Get the ConstraintLayout constraint of the view. */
private fun getViewConstraint(viewId: Int): ConstraintSet.Constraint {
val constraintSet = ConstraintSet()
constraintSet.clone(rootView)
return constraintSet.getConstraint(viewId)
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2023 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.viewmodel
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(JUnit4::class)
@SmallTest
class KeyguardBlueprintViewModelTest : SysuiTestCase() {
@Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
private lateinit var undertest: KeyguardBlueprintViewModel
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
undertest =
KeyguardBlueprintViewModel(keyguardBlueprintInteractor = keyguardBlueprintInteractor)
}
@Test
fun testBlueprintFlow() {
verify(keyguardBlueprintInteractor).blueprint
}
}