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:
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user