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

Commit f72e7989 authored by Matt Pietal's avatar Matt Pietal Committed by Automerger Merge Worker
Browse files

[DO NOT MERGE] Add KeyguardTransitionRepository am: 8c49232b

parents 80c05761 8c49232b
Loading
Loading
Loading
Loading
+71 −27
Original line number Diff line number Diff line
@@ -23,12 +23,18 @@ import android.content.res.Resources
import android.text.format.DateFormat
import android.util.TypedValue
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.REGION_SAMPLING
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.ClockController
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
@@ -38,13 +44,20 @@ import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch

/**
 * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
 * [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
 */
open class ClockEventController @Inject constructor(
    private val statusBarStateController: StatusBarStateController,
    private val keyguardInteractor: KeyguardInteractor,
    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
    private val broadcastDispatcher: BroadcastDispatcher,
    private val batteryController: BatteryController,
    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
@@ -53,7 +66,7 @@ open class ClockEventController @Inject constructor(
    private val context: Context,
    @Main private val mainExecutor: Executor,
    @Background private val bgExecutor: Executor,
    private val featureFlags: FeatureFlags,
    private val featureFlags: FeatureFlags
) {
    var clock: ClockController? = null
        set(value) {
@@ -70,9 +83,9 @@ open class ClockEventController @Inject constructor(
    private var isCharging = false
    private var dozeAmount = 0f
    private var isKeyguardVisible = false

    private val regionSamplingEnabled =
            featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
    private var isRegistered = false
    private var disposableHandle: DisposableHandle? = null
    private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)

    private fun updateColors() {
        if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
@@ -165,15 +178,6 @@ open class ClockEventController @Inject constructor(
        }
    }

    private val statusBarStateListener = object : StatusBarStateController.StateListener {
        override fun onDozeAmountChanged(linear: Float, eased: Float) {
            clock?.animations?.doze(linear)

            isDozing = linear > dozeAmount
            dozeAmount = linear
        }
    }

    private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
        override fun onKeyguardVisibilityChanged(visible: Boolean) {
            isKeyguardVisible = visible
@@ -195,13 +199,11 @@ open class ClockEventController @Inject constructor(
        }
    }

    init {
        isDozing = statusBarStateController.isDozing
    fun registerListeners(parent: View) {
        if (isRegistered) {
            return
        }

    fun registerListeners() {
        dozeAmount = statusBarStateController.dozeAmount
        isDozing = statusBarStateController.isDozing || dozeAmount != 0f
        isRegistered = true

        broadcastDispatcher.registerReceiver(
            localeBroadcastReceiver,
@@ -210,17 +212,28 @@ open class ClockEventController @Inject constructor(
        configurationController.addCallback(configListener)
        batteryController.addCallback(batteryCallback)
        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
        statusBarStateController.addCallback(statusBarStateListener)
        smallRegionSampler?.startRegionSampler()
        largeRegionSampler?.startRegionSampler()
        disposableHandle = parent.repeatWhenAttached {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                listenForDozing(this)
                listenForDozeAmount(this)
                listenForDozeAmountTransition(this)
            }
        }
    }

    fun unregisterListeners() {
        if (!isRegistered) {
            return
        }
        isRegistered = false

        disposableHandle?.dispose()
        broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
        configurationController.removeCallback(configListener)
        batteryController.removeCallback(batteryCallback)
        keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
        statusBarStateController.removeCallback(statusBarStateListener)
        smallRegionSampler?.stopRegionSampler()
        largeRegionSampler?.stopRegionSampler()
    }
@@ -235,8 +248,39 @@ open class ClockEventController @Inject constructor(
        largeRegionSampler?.dump(pw)
    }

    companion object {
        private val TAG = ClockEventController::class.simpleName
        private const val FORMAT_NUMBER = 1234567890
    @VisibleForTesting
    internal fun listenForDozeAmount(scope: CoroutineScope): Job {
        return scope.launch {
            keyguardInteractor.dozeAmount.collect {
                dozeAmount = it
                clock?.animations?.doze(dozeAmount)
            }
        }
    }

    @VisibleForTesting
    internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
        return scope.launch {
            keyguardTransitionInteractor.aodToLockscreenTransition.collect {
                // Would eventually run this:
                // dozeAmount = it.value
                // clock?.animations?.doze(dozeAmount)
            }
        }
    }

    @VisibleForTesting
    internal fun listenForDozing(scope: CoroutineScope): Job {
        return scope.launch {
            combine (
                keyguardInteractor.dozeAmount,
                keyguardInteractor.isDozing,
            ) { localDozeAmount, localIsDozing ->
                localDozeAmount > dozeAmount || localIsDozing
            }
            .collect { localIsDozing ->
                isDozing = localIsDozing
            }
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -164,7 +164,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
    protected void onViewAttached() {
        mClockRegistry.registerClockChangeListener(mClockChangedListener);
        setClock(mClockRegistry.createCurrentClock());
        mClockEventController.registerListeners();
        mClockEventController.registerListeners(mView);
        mKeyguardClockTopMargin =
                mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);

+2 −0
Original line number Diff line number Diff line
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -72,6 +73,7 @@ import dagger.Provides;
            FalsingModule.class,
            KeyguardQuickAffordanceModule.class,
            KeyguardRepositoryModule.class,
            StartKeyguardTransitionModule.class,
        })
public class KeyguardModule {
    /**
+31 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.doze.DozeHost
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
@@ -85,6 +86,9 @@ interface KeyguardRepository {
     */
    val dozeAmount: Flow<Float>

    /** Observable for the [StatusBarState] */
    val statusBarState: Flow<StatusBarState>

    /**
     * Returns `true` if the keyguard is showing; `false` otherwise.
     *
@@ -185,6 +189,24 @@ constructor(
        return keyguardStateController.isShowing
    }

    override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
        val callback =
            object : StatusBarStateController.StateListener {
                override fun onStateChanged(state: Int) {
                    trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
                }
            }

        statusBarStateController.addCallback(callback)
        trySendWithFailureLogging(
            statusBarStateIntToObject(statusBarStateController.getState()),
            TAG,
            "initial state"
        )

        awaitClose { statusBarStateController.removeCallback(callback) }
    }

    override fun setAnimateDozingTransitions(animate: Boolean) {
        _animateBottomAreaDozingTransitions.value = animate
    }
@@ -197,6 +219,15 @@ constructor(
        _clockPosition.value = Position(x, y)
    }

    private fun statusBarStateIntToObject(value: Int): StatusBarState {
        return when (value) {
            0 -> StatusBarState.SHADE
            1 -> StatusBarState.KEYGUARD
            2 -> StatusBarState.SHADE_LOCKED
            else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
        }
    }

    companion object {
        private const val TAG = "KeyguardRepositoryImpl"
    }
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License
 */
package com.android.systemui.keyguard.data.repository

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.animation.ValueAnimator.AnimatorUpdateListener
import android.annotation.FloatRange
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import java.util.UUID
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filter

@SysUISingleton
class KeyguardTransitionRepository @Inject constructor() {
    /*
     * Each transition between [KeyguardState]s will have an associated Flow.
     * In order to collect these events, clients should call [transition].
     */
    private val _transitions = MutableStateFlow(TransitionStep())
    val transitions = _transitions.asStateFlow()

    /* Information about the active transition. */
    private var currentTransitionInfo: TransitionInfo? = null
    /*
     * When manual control of the transition is requested, a unique [UUID] is used as the handle
     * to permit calls to [updateTransition]
     */
    private var updateTransitionId: UUID? = null

    /**
     * Interactors that require information about changes between [KeyguardState]s will call this to
     * register themselves for flowable [TransitionStep]s when that transition occurs.
     */
    fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
        return transitions.filter { step -> step.from == from && step.to == to }
    }

    /**
     * Begin a transition from one state to another. The [info.from] must match
     * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
     * unplanned transitions.
     */
    fun startTransition(info: TransitionInfo): UUID? {
        if (currentTransitionInfo != null) {
            // Open questions:
            // * Queue of transitions? buffer of 1?
            // * Are transitions cancellable if a new one is triggered?
            // * What validation does this need to do?
            Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
            return null
        }
        currentTransitionInfo?.animator?.cancel()

        currentTransitionInfo = info
        info.animator?.let { animator ->
            // An animator was provided, so use it to run the transition
            animator.setFloatValues(0f, 1f)
            val updateListener =
                object : AnimatorUpdateListener {
                    override fun onAnimationUpdate(animation: ValueAnimator) {
                        emitTransition(
                            info,
                            (animation.getAnimatedValue() as Float),
                            TransitionState.RUNNING
                        )
                    }
                }
            val adapter =
                object : AnimatorListenerAdapter() {
                    override fun onAnimationStart(animation: Animator) {
                        Log.i(TAG, "Starting transition: $info")
                        emitTransition(info, 0f, TransitionState.STARTED)
                    }
                    override fun onAnimationCancel(animation: Animator) {
                        Log.i(TAG, "Cancelling transition: $info")
                    }
                    override fun onAnimationEnd(animation: Animator) {
                        Log.i(TAG, "Ending transition: $info")
                        emitTransition(info, 1f, TransitionState.FINISHED)
                        animator.removeListener(this)
                        animator.removeUpdateListener(updateListener)
                    }
                }
            animator.addListener(adapter)
            animator.addUpdateListener(updateListener)
            animator.start()
            return@startTransition null
        }
            ?: run {
                Log.i(TAG, "Starting transition (manual): $info")
                emitTransition(info, 0f, TransitionState.STARTED)

                // No animator, so it's manual. Provide a mechanism to callback
                updateTransitionId = UUID.randomUUID()
                return@startTransition updateTransitionId
            }
    }

    /**
     * Allows manual control of a transition. When calling [startTransition], the consumer must pass
     * in a null animator. In return, it will get a unique [UUID] that will be validated to allow
     * further updates.
     *
     * When the transition is over, TransitionState.FINISHED must be passed into the [state]
     * parameter.
     */
    fun updateTransition(
        transitionId: UUID,
        @FloatRange(from = 0.0, to = 1.0) value: Float,
        state: TransitionState
    ) {
        if (updateTransitionId != transitionId) {
            Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
            return
        }

        if (currentTransitionInfo == null) {
            Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
            return
        }

        currentTransitionInfo?.let { info ->
            if (state == TransitionState.FINISHED) {
                updateTransitionId = null
                Log.i(TAG, "Ending transition: $info")
            }

            emitTransition(info, value, state)
        }
    }

    private fun emitTransition(
        info: TransitionInfo,
        value: Float,
        transitionState: TransitionState
    ) {
        if (transitionState == TransitionState.FINISHED) {
            currentTransitionInfo = null
        }
        _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
    }

    companion object {
        private const val TAG = "KeyguardTransitionRepository"
    }
}
Loading