diff options
11 files changed, 565 insertions, 58 deletions
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 05f4334bbe89..74d435d18823 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -248,4 +248,14 @@ <!-- Tag set on the Compose implementation of the QS footer actions. --> <item type="id" name="tag_compose_qs_footer_actions" /> + + <!-- + Ids for the device entry icon. + device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg + device_entry_icon_fg: fp/lock/unlock icon + device_entry_icon_bg: background protection behind the icon + --> + <item type="id" name="device_entry_icon_view" /> + <item type="id" name="device_entry_icon_fg" /> + <item type="id" name="device_entry_icon_bg" /> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 272e0f21e74a..934f9f919d5d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -46,7 +46,6 @@ import androidx.annotation.VisibleForTesting import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dump.DumpManager @@ -240,15 +239,14 @@ class UdfpsControllerOverlay @JvmOverloads constructor( } REASON_AUTH_KEYGUARD -> { if (featureFlags.isEnabled(REFACTOR_UDFPS_KEYGUARD_VIEWS)) { - udfpsKeyguardViewModels.get().setSensorBounds(sensorBounds) - UdfpsKeyguardViewController( - view.addUdfpsView(R.layout.udfps_keyguard_view), - statusBarStateController, - primaryBouncerInteractor, - dialogManager, - dumpManager, - alternateBouncerInteractor, - udfpsKeyguardViewModels.get(), + // note: empty controller, currently shows no visual affordance + // instead SysUI will show the fingerprint icon in its DeviceEntryIconView + UdfpsBpViewController( + view.addUdfpsView(R.layout.udfps_bp_view), + statusBarStateController, + primaryBouncerInteractor, + dialogManager, + dumpManager ) } else { UdfpsKeyguardViewControllerLegacy( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt index 119ade48d4f7..c56dfde86573 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt @@ -48,6 +48,7 @@ import com.android.systemui.statusbar.KeyguardIndicationController import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -72,7 +73,7 @@ constructor( private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory, private val context: Context, private val keyguardIndicationController: KeyguardIndicationController, - private val lockIconViewController: LockIconViewController, + private val lockIconViewController: Lazy<LockIconViewController>, private val shadeInteractor: ShadeInteractor, private val interactionJankMonitor: InteractionJankMonitor, private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, @@ -131,7 +132,9 @@ constructor( val indicationArea = KeyguardIndicationArea(context, null) keyguardIndicationController.setIndicationArea(indicationArea) - lockIconViewController.setLockIconView(LockIconView(context, null)) + if (!featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + lockIconViewController.get().setLockIconView(LockIconView(context, null)) + } } private fun bindKeyguardRootView() { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt new file mode 100644 index 000000000000..e82ea7fecd05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -0,0 +1,103 @@ +/* + * 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.annotation.SuppressLint +import android.content.res.ColorStateList +import android.view.View +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle +import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.plugins.FalsingManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch + +@ExperimentalCoroutinesApi +object DeviceEntryIconViewBinder { + + /** + * Updates UI for the device entry icon view (lock, unlock and fingerprint icons) and its + * background. + */ + @SuppressLint("ClickableViewAccessibility") + @JvmStatic + fun bind( + view: DeviceEntryIconView, + viewModel: DeviceEntryIconViewModel, + falsingManager: FalsingManager, + ) { + val iconView = view.iconView + val bgView = view.bgView + val longPressHandlingView = view.longPressHandlingView + longPressHandlingView.listener = + object : LongPressHandlingView.Listener { + override fun onLongPressDetected(view: View, x: Int, y: Int) { + if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { + return + } + viewModel.onLongPress() + } + } + view.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + launch { + viewModel.iconViewModel.collect { iconViewModel -> + iconView.setImageState( + view.getIconState(iconViewModel.type, iconViewModel.useAodVariant), + /* merge */ false + ) + iconView.imageTintList = ColorStateList.valueOf(iconViewModel.tint) + iconView.alpha = iconViewModel.alpha + iconView.setPadding( + iconViewModel.padding, + iconViewModel.padding, + iconViewModel.padding, + iconViewModel.padding, + ) + } + } + launch { + viewModel.backgroundViewModel.collect { bgViewModel -> + bgView.alpha = bgViewModel.alpha + bgView.imageTintList = ColorStateList.valueOf(bgViewModel.tint) + } + } + launch { + viewModel.burnInViewModel.collect { burnInViewModel -> + view.translationX = burnInViewModel.x.toFloat() + view.translationY = burnInViewModel.y.toFloat() + view.aodFpDrawable.progress = burnInViewModel.progress + } + } + launch { + viewModel.isLongPressEnabled.collect { isEnabled -> + longPressHandlingView.setLongPressHandlingEnabled(isEnabled) + } + } + launch { + viewModel.accessibilityDelegateHint.collect { hint -> + view.accessibilityHintType = hint + } + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt new file mode 100644 index 000000000000..c9e395402dad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -0,0 +1,272 @@ +/* + * 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 + +import android.content.Context +import android.graphics.drawable.AnimatedStateListDrawable +import android.graphics.drawable.AnimatedVectorDrawable +import android.util.AttributeSet +import android.util.StateSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityNodeInfo +import android.widget.FrameLayout +import android.widget.ImageView +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat +import com.airbnb.lottie.LottieCompositionFactory +import com.airbnb.lottie.LottieDrawable +import com.android.systemui.common.ui.view.LongPressHandlingView +import com.android.systemui.res.R + +class DeviceEntryIconView +@JvmOverloads +constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttrs: Int = 0, +) : FrameLayout(context, attrs, defStyleAttrs) { + val longPressHandlingView: LongPressHandlingView = LongPressHandlingView(context, attrs) + val iconView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_fg } + val bgView: ImageView = ImageView(context, attrs).apply { id = R.id.device_entry_icon_bg } + val aodFpDrawable: LottieDrawable = LottieDrawable() + var accessibilityHintType: AccessibilityHintType = AccessibilityHintType.NONE + + private var animatedIconDrawable: AnimatedStateListDrawable = AnimatedStateListDrawable() + + init { + setupIconStates() + setupIconTransitions() + setupAccessibilityDelegate() + + // Ordering matters. From background to foreground we want: + // bgView, iconView, longpressHandlingView overlay + addBgImageView() + addIconImageView() + addLongpressHandlingView() + } + + private fun setupAccessibilityDelegate() { + accessibilityDelegate = + object : AccessibilityDelegate() { + private val accessibilityAuthenticateHint = + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + resources.getString(R.string.accessibility_authenticate_hint) + ) + private val accessibilityEnterHint = + AccessibilityNodeInfo.AccessibilityAction( + AccessibilityNodeInfoCompat.ACTION_CLICK, + resources.getString(R.string.accessibility_enter_hint) + ) + override fun onInitializeAccessibilityNodeInfo( + v: View, + info: AccessibilityNodeInfo + ) { + super.onInitializeAccessibilityNodeInfo(v, info) + when (accessibilityHintType) { + AccessibilityHintType.AUTHENTICATE -> + info.addAction(accessibilityAuthenticateHint) + AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint) + AccessibilityHintType.NONE -> return + } + } + } + } + + private fun setupIconStates() { + // Lockscreen States + // LOCK + animatedIconDrawable.addState( + getIconState(IconType.LOCK, false), + context.getDrawable(R.drawable.ic_lock)!!, + R.id.locked, + ) + // UNLOCK + animatedIconDrawable.addState( + getIconState(IconType.UNLOCK, false), + context.getDrawable(R.drawable.ic_unlocked)!!, + R.id.unlocked, + ) + // FINGERPRINT + animatedIconDrawable.addState( + getIconState(IconType.FINGERPRINT, false), + context.getDrawable(R.drawable.ic_kg_fingerprint)!!, + R.id.locked_fp, + ) + + // AOD states + // LOCK + animatedIconDrawable.addState( + getIconState(IconType.LOCK, true), + context.getDrawable(R.drawable.ic_lock_aod)!!, + R.id.locked_aod, + ) + // UNLOCK + animatedIconDrawable.addState( + getIconState(IconType.UNLOCK, true), + context.getDrawable(R.drawable.ic_unlocked_aod)!!, + R.id.unlocked_aod, + ) + // FINGERPRINT + LottieCompositionFactory.fromRawRes(mContext, R.raw.udfps_aod_fp).addListener { result -> + aodFpDrawable.setComposition(result) + } + animatedIconDrawable.addState( + getIconState(IconType.FINGERPRINT, true), + aodFpDrawable, + R.id.udfps_aod_fp, + ) + + // WILDCARD: should always be the last state added since any states will match with this + // and therefore won't get matched with subsequent states. + animatedIconDrawable.addState( + StateSet.WILD_CARD, + context.getDrawable(R.color.transparent)!!, + R.id.no_icon, + ) + } + + private fun setupIconTransitions() { + // LockscreenFp <=> LockscreenUnlocked + animatedIconDrawable.addTransition( + R.id.locked_fp, + R.id.unlocked, + context.getDrawable(R.drawable.fp_to_unlock) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked_fp, + context.getDrawable(R.drawable.unlock_to_fp) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenLocked <=> AodLocked + animatedIconDrawable.addTransition( + R.id.locked_aod, + R.id.locked, + context.getDrawable(R.drawable.lock_aod_to_ls) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.locked, + R.id.locked_aod, + context.getDrawable(R.drawable.lock_ls_to_aod) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenUnlocked <=> AodUnlocked + animatedIconDrawable.addTransition( + R.id.unlocked_aod, + R.id.unlocked, + context.getDrawable(R.drawable.unlocked_aod_to_ls) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.unlocked_aod, + context.getDrawable(R.drawable.unlocked_ls_to_aod) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenLocked <=> LockscreenUnlocked + animatedIconDrawable.addTransition( + R.id.locked, + R.id.unlocked, + context.getDrawable(R.drawable.lock_to_unlock) as AnimatedVectorDrawable, + /* reversible */ false, + ) + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked, + context.getDrawable(R.drawable.unlocked_to_locked) as AnimatedVectorDrawable, + /* reversible */ false, + ) + + // LockscreenFingerprint <=> LockscreenLocked + animatedIconDrawable.addTransition( + R.id.locked_fp, + R.id.locked, + context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable, + /* reversible */ true, + ) + + // LockscreenUnlocked <=> AodLocked + animatedIconDrawable.addTransition( + R.id.unlocked, + R.id.locked_aod, + context.getDrawable(R.drawable.unlocked_to_aod_lock) as AnimatedVectorDrawable, + /* reversible */ true, + ) + } + + private fun addLongpressHandlingView() { + addView(longPressHandlingView) + val lp = longPressHandlingView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + longPressHandlingView.setLayoutParams(lp) + } + + private fun addIconImageView() { + iconView.scaleType = ImageView.ScaleType.CENTER_CROP + iconView.setImageDrawable(animatedIconDrawable) + addView(iconView) + val lp = iconView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + lp.gravity = Gravity.CENTER + iconView.setLayoutParams(lp) + } + + private fun addBgImageView() { + bgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg)) + addView(bgView) + val lp = bgView.layoutParams as LayoutParams + lp.height = ViewGroup.LayoutParams.MATCH_PARENT + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + bgView.setLayoutParams(lp) + } + + fun getIconState(icon: IconType, aod: Boolean): IntArray { + val lockIconState = IntArray(2) + when (icon) { + IconType.LOCK -> lockIconState[0] = android.R.attr.state_first + IconType.UNLOCK -> lockIconState[0] = android.R.attr.state_last + IconType.FINGERPRINT -> lockIconState[0] = android.R.attr.state_middle + } + if (aod) { + lockIconState[1] = android.R.attr.state_single + } else { + lockIconState[1] = -android.R.attr.state_single + } + return lockIconState + } + + enum class IconType { + LOCK, + UNLOCK, + FINGERPRINT, + } + + enum class AccessibilityHintType { + NONE, + AUTHENTICATE, + ENTER, + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt index d8e43966990e..21eba56dcd83 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt @@ -23,8 +23,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardBlueprint import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection 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.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection @@ -44,7 +44,7 @@ class DefaultKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultLockIconSection: DefaultLockIconSection, + defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultShortcutsSection: DefaultShortcutsSection, defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, @@ -61,7 +61,7 @@ constructor( override val sections = setOf( defaultIndicationAreaSection, - defaultLockIconSection, + defaultDeviceEntryIconSection, defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt index ce76f56fad2f..f8dd7c1a58c7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt @@ -23,8 +23,8 @@ import com.android.systemui.keyguard.ui.view.layout.sections.AlignShortcutsToUdf import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection 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.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection @@ -38,7 +38,7 @@ class ShortcutsBesideUdfpsKeyguardBlueprint @Inject constructor( defaultIndicationAreaSection: DefaultIndicationAreaSection, - defaultLockIconSection: DefaultLockIconSection, + defaultDeviceEntryIconSection: DefaultDeviceEntryIconSection, defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection, @@ -54,7 +54,7 @@ constructor( override val sections = setOf( defaultIndicationAreaSection, - defaultLockIconSection, + defaultDeviceEntryIconSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, alignShortcutsToUdfpsSection, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt index f4bc7137b50c..62c5988ff42c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSection.kt @@ -33,11 +33,18 @@ import com.android.systemui.biometrics.AuthController import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.shared.model.KeyguardSection +import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView +import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi -class DefaultLockIconSection +@ExperimentalCoroutinesApi +class DefaultDeviceEntryIconSection @Inject constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, @@ -46,24 +53,47 @@ constructor( private val context: Context, private val notificationPanelView: NotificationPanelView, private val featureFlags: FeatureFlags, - private val lockIconViewController: LockIconViewController, + private val lockIconViewController: Lazy<LockIconViewController>, + private val deviceEntryIconViewModel: Lazy<DeviceEntryIconViewModel>, + private val falsingManager: Lazy<FalsingManager>, ) : KeyguardSection() { - private val lockIconViewId = R.id.lock_icon_view + private val deviceEntryIconViewId = R.id.device_entry_icon_view override fun addViews(constraintLayout: ConstraintLayout) { - if (!featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) { + if ( + !featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON) && + !featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS) + ) { return } - notificationPanelView.findViewById<View>(lockIconViewId).let { + + notificationPanelView.findViewById<View>(R.id.lock_icon_view).let { notificationPanelView.removeView(it) } - val view = LockIconView(context, null).apply { id = lockIconViewId } + + val view = + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + DeviceEntryIconView(context, null).apply { id = deviceEntryIconViewId } + } else { + // Flags.MIGRATE_LOCK_ICON + LockIconView(context, null).apply { id = deviceEntryIconViewId } + } constraintLayout.addView(view) } override fun bindData(constraintLayout: ConstraintLayout) { - constraintLayout.findViewById<LockIconView?>(lockIconViewId)?.let { - lockIconViewController.setLockIconView(it) + if (featureFlags.isEnabled(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS)) { + constraintLayout.findViewById<DeviceEntryIconView?>(deviceEntryIconViewId)?.let { + DeviceEntryIconViewBinder.bind( + it, + deviceEntryIconViewModel.get(), + falsingManager.get(), + ) + } + } else { + constraintLayout.findViewById<LockIconView?>(deviceEntryIconViewId)?.let { + lockIconViewController.get().setLockIconView(it) + } } } @@ -84,30 +114,30 @@ constructor( val defaultDensity = DisplayMetrics.DENSITY_DEVICE_STABLE.toFloat() / DisplayMetrics.DENSITY_DEFAULT.toFloat() - val lockIconRadiusPx = (defaultDensity * 36).toInt() + val iconRadiusPx = (defaultDensity * 36).toInt() if (isUdfpsSupported) { authController.udfpsLocation?.let { udfpsLocation -> - centerLockIcon(udfpsLocation, authController.udfpsRadius, constraintSet) + centerIcon(udfpsLocation, authController.udfpsRadius, constraintSet) } } else { - centerLockIcon( + centerIcon( Point( (widthPixels / 2).toInt(), - (heightPixels - ((mBottomPaddingPx + lockIconRadiusPx) * scaleFactor)).toInt() + (heightPixels - ((mBottomPaddingPx + iconRadiusPx) * scaleFactor)).toInt() ), - lockIconRadiusPx * scaleFactor, + iconRadiusPx * scaleFactor, constraintSet, ) } } override fun removeViews(constraintLayout: ConstraintLayout) { - constraintLayout.removeView(lockIconViewId) + constraintLayout.removeView(deviceEntryIconViewId) } @VisibleForTesting - internal fun centerLockIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { + internal fun centerIcon(center: Point, radius: Float, constraintSet: ConstraintSet) { val sensorRect = Rect().apply { set( @@ -119,17 +149,17 @@ constructor( } constraintSet.apply { - constrainWidth(lockIconViewId, sensorRect.right - sensorRect.left) - constrainHeight(lockIconViewId, sensorRect.bottom - sensorRect.top) + constrainWidth(deviceEntryIconViewId, sensorRect.right - sensorRect.left) + constrainHeight(deviceEntryIconViewId, sensorRect.bottom - sensorRect.top) connect( - lockIconViewId, + deviceEntryIconViewId, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, sensorRect.top ) connect( - lockIconViewId, + deviceEntryIconViewId, ConstraintSet.START, ConstraintSet.PARENT_ID, ConstraintSet.START, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt new file mode 100644 index 000000000000..842dde352c71 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -0,0 +1,68 @@ +/* + * 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 android.graphics.Color +import com.android.systemui.keyguard.ui.view.DeviceEntryIconView +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +@ExperimentalCoroutinesApi +class DeviceEntryIconViewModel @Inject constructor() { + // TODO: b/305234447 update these states from the data layer + val iconViewModel: Flow<IconViewModel> = + flowOf( + IconViewModel( + type = DeviceEntryIconView.IconType.LOCK, + useAodVariant = false, + tint = Color.WHITE, + alpha = 1f, + padding = 48, + ) + ) + val backgroundViewModel: Flow<BackgroundViewModel> = + flowOf(BackgroundViewModel(alpha = 1f, tint = Color.GRAY)) + val burnInViewModel: Flow<BurnInViewModel> = flowOf(BurnInViewModel(0, 0, 0f)) + val isLongPressEnabled: Flow<Boolean> = flowOf(true) + val accessibilityDelegateHint: Flow<DeviceEntryIconView.AccessibilityHintType> = + flowOf(DeviceEntryIconView.AccessibilityHintType.NONE) + + fun onLongPress() { + // TODO() vibrate & perform action based on current lock/unlock state + } + data class BurnInViewModel( + val x: Int, // current x burn in offset based on the aodTransitionAmount + val y: Int, // current y burn in offset based on the aodTransitionAmount + val progress: Float, // current progress based on the aodTransitionAmount + ) + + class IconViewModel( + val type: DeviceEntryIconView.IconType, + val useAodVariant: Boolean, + val tint: Int, + val alpha: Float, + val padding: Int, + ) + + class BackgroundViewModel( + val alpha: Float, + val tint: Int, + ) +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt index 50ee02609635..8cfa87d27e57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt @@ -30,8 +30,8 @@ import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultAmbientIndicationAreaSection +import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntryIconSection 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.DefaultNotificationStackScrollLayoutSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection @@ -55,7 +55,7 @@ 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 mDefaultDeviceEntryIconSection: DefaultDeviceEntryIconSection @Mock private lateinit var defaultShortcutsSection: DefaultShortcutsSection @Mock private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection @@ -75,7 +75,7 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { underTest = DefaultKeyguardBlueprint( defaultIndicationAreaSection, - defaultLockIconSection, + mDefaultDeviceEntryIconSection, defaultShortcutsSection, defaultAmbientIndicationAreaSection, defaultSettingsPopupMenuSection, @@ -101,14 +101,14 @@ class DefaultKeyguardBlueprintTest : SysuiTestCase() { val prevBlueprint = mock(KeyguardBlueprint::class.java) val someSection = mock(KeyguardSection::class.java) whenever(prevBlueprint.sections) - .thenReturn(underTest.sections.minus(defaultLockIconSection).plus(someSection)) + .thenReturn(underTest.sections.minus(mDefaultDeviceEntryIconSection).plus(someSection)) val constraintLayout = ConstraintLayout(context, null) underTest.replaceViews(prevBlueprint, constraintLayout) - underTest.sections.minus(defaultLockIconSection).forEach { + underTest.sections.minus(mDefaultDeviceEntryIconSection).forEach { verify(it, never()).addViews(constraintLayout) } - verify(defaultLockIconSection).addViews(constraintLayout) + verify(mDefaultDeviceEntryIconSection).addViews(constraintLayout) verify(someSection).removeViews(constraintLayout) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt index 74956377d345..5f22c7da3920 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntryIconSectionTest.kt @@ -24,14 +24,17 @@ import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.LockIconViewController -import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.AuthController -import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel +import com.android.systemui.plugins.FalsingManager +import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView -import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -40,35 +43,54 @@ import org.mockito.Answers import org.mockito.Mock import org.mockito.MockitoAnnotations +@ExperimentalCoroutinesApi @RunWith(JUnit4::class) @SmallTest -class DefaultLockIconSectionTest : SysuiTestCase() { +class DefaultDeviceEntryIconSectionTest : 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 notificationPanelView: NotificationPanelView - @Mock private lateinit var featureFlags: FeatureFlags + private lateinit var featureFlags: FakeFeatureFlags @Mock private lateinit var lockIconViewController: LockIconViewController - private lateinit var underTest: DefaultLockIconSection + @Mock private lateinit var falsingManager: FalsingManager + private lateinit var underTest: DefaultDeviceEntryIconSection @Before fun setup() { MockitoAnnotations.initMocks(this) + featureFlags = + FakeFeatureFlagsClassic().apply { + set(Flags.MIGRATE_LOCK_ICON, false) + set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) + set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, false) + } underTest = - DefaultLockIconSection( + DefaultDeviceEntryIconSection( keyguardUpdateMonitor, authController, windowManager, context, notificationPanelView, featureFlags, - lockIconViewController + { lockIconViewController }, + { DeviceEntryIconViewModel() }, + { falsingManager }, ) } @Test - fun addViewsConditionally() { - whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(true) + fun addViewsConditionally_migrateFlagOn() { + featureFlags.set(Flags.MIGRATE_LOCK_ICON, true) + val constraintLayout = ConstraintLayout(context, null) + underTest.addViews(constraintLayout) + assertThat(constraintLayout.childCount).isGreaterThan(0) + } + + @Test + fun addViewsConditionally_migrateAndRefactorFlagsOn() { + featureFlags.set(Flags.MIGRATE_LOCK_ICON, true) + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, true) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isGreaterThan(0) @@ -76,7 +98,8 @@ class DefaultLockIconSectionTest : SysuiTestCase() { @Test fun addViewsConditionally_migrateFlagOff() { - whenever(featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)).thenReturn(false) + featureFlags.set(Flags.MIGRATE_LOCK_ICON, false) + featureFlags.set(Flags.REFACTOR_UDFPS_KEYGUARD_VIEWS, false) val constraintLayout = ConstraintLayout(context, null) underTest.addViews(constraintLayout) assertThat(constraintLayout.childCount).isEqualTo(0) @@ -87,18 +110,18 @@ class DefaultLockIconSectionTest : SysuiTestCase() { val cs = ConstraintSet() underTest.applyConstraints(cs) - val constraint = cs.getConstraint(R.id.lock_icon_view) + val constraint = cs.getConstraint(R.id.device_entry_icon_view) assertThat(constraint.layout.topToTop).isEqualTo(ConstraintSet.PARENT_ID) assertThat(constraint.layout.startToStart).isEqualTo(ConstraintSet.PARENT_ID) } @Test - fun testCenterLockIcon() { + fun testCenterIcon() { val cs = ConstraintSet() - underTest.centerLockIcon(Point(5, 6), 1F, cs) + underTest.centerIcon(Point(5, 6), 1F, cs) - val constraint = cs.getConstraint(R.id.lock_icon_view) + val constraint = cs.getConstraint(R.id.device_entry_icon_view) assertThat(constraint.layout.mWidth).isEqualTo(2) assertThat(constraint.layout.mHeight).isEqualTo(2) |