Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0f041558 authored by Beverly Tai's avatar Beverly Tai Committed by Android (Google) Code Review
Browse files

Merge "View layer for DeviceEntryIconView" into main

parents 8336cb3a 203e5664
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -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>
+8 −10
Original line number Diff line number Diff line
@@ -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),
                    // 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,
                        alternateBouncerInteractor,
                        udfpsKeyguardViewModels.get(),
                            dumpManager
                    )
                } else {
                    UdfpsKeyguardViewControllerLegacy(
+5 −2
Original line number Diff line number Diff line
@@ -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() {
+103 −0
Original line number Diff line number Diff line
/*
 * 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
                    }
                }
            }
        }
    }
}
+272 −0
Original line number Diff line number Diff line
/*
 * 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,
    }
}
Loading