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

Commit ac06c257 authored by Josh Tsuji's avatar Josh Tsuji
Browse files

Adds support for the in-window unlock animation to the Keyguard refactor.

When the flag is enabled, this swaps out the KeyguardUnlockAnimationController
in OverviewProxyService and replaces it with the
InWindowLauncherUnlockAnimationManager, which exclusively collects state from
the new flows.

Bug: 293894758
Test: unlock a lot
Test: atest SystemUITests
Flag: keyguard_wm_state_refactor
Change-Id: I17f8d18144d6bfa0d9dd507a6b041be988fc3c16
parent 17fe3ebe
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.ui.binder.KeyguardRootViewBinder;
import com.android.systemui.keyguard.ui.view.InWindowLauncherUnlockAnimationManager;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.core.LogLevel;
@@ -123,6 +124,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
    private View mSmartspaceView;

    private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
    private final InWindowLauncherUnlockAnimationManager mInWindowLauncherUnlockAnimationManager;

    private boolean mShownOnSecondaryDisplay = false;
    private boolean mOnlyClock = false;
@@ -190,7 +192,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
            AlwaysOnDisplayNotificationIconViewStore aodIconViewStore,
            KeyguardInteractor keyguardInteractor,
            KeyguardClockInteractor keyguardClockInteractor,
            FeatureFlagsClassic featureFlags) {
            FeatureFlagsClassic featureFlags,
            InWindowLauncherUnlockAnimationManager inWindowLauncherUnlockAnimationManager) {
        super(keyguardClockSwitch);
        mStatusBarStateController = statusBarStateController;
        mClockRegistry = clockRegistry;
@@ -214,6 +217,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
        mFeatureFlags = featureFlags;
        mKeyguardInteractor = keyguardInteractor;
        mKeyguardClockInteractor = keyguardClockInteractor;
        mInWindowLauncherUnlockAnimationManager = inWindowLauncherUnlockAnimationManager;

        mClockChangedListener = new ClockRegistry.ClockChangeListener() {
            @Override
@@ -438,6 +442,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
        mSmartspaceView.setPaddingRelative(startPadding, 0, endPadding, 0);

        mKeyguardUnlockAnimationController.setLockscreenSmartspace(mSmartspaceView);
        mInWindowLauncherUnlockAnimationManager.setLockscreenSmartspace(mSmartspaceView);

        mView.setSmartspace(mSmartspaceView);
    }

+89 −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.data.repository

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController
import com.android.systemui.shared.system.smartspace.SmartspaceState
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow

/**
 * State related to System UI's handling of the in-window Launcher unlock animations. This includes
 * the staggered icon entry animation that plays during unlock, as well as the smartspace shared
 * element animation, if supported.
 *
 * While the animations themselves occur fully in the Launcher window, System UI is responsible for
 * preparing/starting the animations, as well as synchronizing the smartspace state so that the two
 * smartspaces appear visually identical for the shared element animation.
 */
@SysUISingleton
class InWindowLauncherUnlockAnimationRepository @Inject constructor() {

    /**
     * Whether we have called [ILauncherUnlockAnimationController.playUnlockAnimation] during this
     * unlock sequence. This value is set back to false once
     * [InWindowLauncherUnlockAnimationInteractor.shouldStartInWindowAnimation] reverts to false,
     * which happens when we're no longer in transition to GONE or if the remote animation ends or
     * is cancelled.
     */
    val startedUnlockAnimation = MutableStateFlow(false)

    /**
     * The unlock amount we've explicitly passed to
     * [ILauncherUnlockAnimationController.setUnlockAmount]. This is used whenever System UI is
     * directly controlling the amount of the unlock animation, such as during a manual swipe to
     * unlock gesture.
     *
     * This value is *not* updated if we called
     * [ILauncherUnlockAnimationController.playUnlockAnimation] to ask Launcher to animate all the
     * way unlocked, since that animator is running in the Launcher window.
     */
    val manualUnlockAmount: MutableStateFlow<Float?> = MutableStateFlow(null)

    /**
     * The class name of the Launcher activity that provided us with a
     * [ILauncherUnlockAnimationController], if applicable. We can use this to check if that
     * launcher is underneath the lockscreen before playing in-window animations.
     *
     * If null, we have not been provided with a launcher unlock animation controller.
     */
    val launcherActivityClass: MutableStateFlow<String?> = MutableStateFlow(null)

    /**
     * Information about the Launcher's smartspace, which is passed to us via
     * [ILauncherUnlockAnimationController].
     */
    val launcherSmartspaceState: MutableStateFlow<SmartspaceState?> = MutableStateFlow(null)

    fun setStartedUnlockAnimation(started: Boolean) {
        startedUnlockAnimation.value = started
    }

    fun setManualUnlockAmount(amount: Float?) {
        manualUnlockAmount.value = amount
    }

    fun setLauncherActivityClass(className: String) {
        launcherActivityClass.value = className
    }

    fun setLauncherSmartspaceState(state: SmartspaceState?) {
        launcherSmartspaceState.value = state
    }
}
+14 −0
Original line number Diff line number Diff line
@@ -31,8 +31,14 @@ interface KeyguardSurfaceBehindRepository {
    /** Whether we're running animations on the surface. */
    val isAnimatingSurface: Flow<Boolean>

    /** Whether we have a RemoteAnimationTarget to run animations on the surface. */
    val isSurfaceRemoteAnimationTargetAvailable: Flow<Boolean>

    /** Set whether we're running animations on the surface. */
    fun setAnimatingSurface(animating: Boolean)

    /** Set whether we have a RemoteAnimationTarget with which to run animations on the surface. */
    fun setSurfaceRemoteAnimationTargetAvailable(available: Boolean)
}

@SysUISingleton
@@ -40,7 +46,15 @@ class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfac
    private val _isAnimatingSurface = MutableStateFlow(false)
    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()

    private val _isSurfaceRemoteAnimationTargetAvailable = MutableStateFlow(false)
    override val isSurfaceRemoteAnimationTargetAvailable =
        _isSurfaceRemoteAnimationTargetAvailable.asStateFlow()

    override fun setAnimatingSurface(animating: Boolean) {
        _isAnimatingSurface.value = animating
    }

    override fun setSurfaceRemoteAnimationTargetAvailable(available: Boolean) {
        _isSurfaceRemoteAnimationTargetAvailable.value = available
    }
}
+13 −2
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import java.util.UUID
import javax.inject.Inject
import kotlin.time.Duration.Companion.milliseconds
@@ -56,6 +57,7 @@ constructor(
    private val flags: FeatureFlags,
    private val shadeRepository: ShadeRepository,
    private val powerInteractor: PowerInteractor,
    inWindowLauncherUnlockAnimationInteractor: Lazy<InWindowLauncherUnlockAnimationInteractor>,
) :
    TransitionInteractor(
        fromState = KeyguardState.LOCKSCREEN,
@@ -104,12 +106,21 @@ constructor(
    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
        combine(
                transitionInteractor.startedKeyguardTransitionStep,
                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
            ) { startedStep, fromLockscreenStep ->
                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN),
                inWindowLauncherUnlockAnimationInteractor
                    .get()
                    .transitioningToGoneWithInWindowAnimation,
            ) { startedStep, fromLockscreenStep, transitioningToGoneWithInWindowAnimation ->
                if (startedStep.to != KeyguardState.GONE) {
                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
                    // animation).
                    return@combine null
                } else if (transitioningToGoneWithInWindowAnimation) {
                    // If we're prepared for the in-window unlock, we're going to play an animation
                    // in the window. Make it fully visible.
                    KeyguardSurfaceBehindModel(
                        alpha = 1f,
                    )
                } else if (fromLockscreenStep.value > 0.5f) {
                    // Start the animation once we're 50% transitioned to GONE.
                    KeyguardSurfaceBehindModel(
+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.domain.interactor

import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.InWindowLauncherUnlockAnimationRepository
import com.android.systemui.keyguard.data.repository.KeyguardSurfaceBehindRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.shared.system.ActivityManagerWrapper
import com.android.systemui.shared.system.smartspace.SmartspaceState
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn

@SysUISingleton
class InWindowLauncherUnlockAnimationInteractor
@Inject
constructor(
    private val repository: InWindowLauncherUnlockAnimationRepository,
    @Application scope: CoroutineScope,
    transitionInteractor: KeyguardTransitionInteractor,
    surfaceBehindRepository: dagger.Lazy<KeyguardSurfaceBehindRepository>,
    private val activityManager: ActivityManagerWrapper,
) {
    val startedUnlockAnimation = repository.startedUnlockAnimation.asStateFlow()

    /**
     * Whether we've STARTED but not FINISHED a transition to GONE, and the preconditions are met to
     * play the in-window unlock animation.
     */
    val transitioningToGoneWithInWindowAnimation: StateFlow<Boolean> =
        transitionInteractor
            .isInTransitionToState(KeyguardState.GONE)
            .sample(repository.launcherActivityClass, ::Pair)
            .map { (isTransitioningToGone, launcherActivityClass) ->
                isTransitioningToGone && isActivityClassUnderneath(launcherActivityClass)
            }
            .stateIn(scope, SharingStarted.Eagerly, false)

    /**
     * Whether we should start the in-window unlock animation.
     *
     * This emits true once the Launcher surface becomes available while we're
     * [transitioningToGoneWithInWindowAnimation].
     */
    val shouldStartInWindowAnimation: StateFlow<Boolean> =
        combine(
                transitioningToGoneWithInWindowAnimation,
                surfaceBehindRepository.get().isSurfaceRemoteAnimationTargetAvailable,
            ) { transitioningWithInWindowAnimation, isSurfaceAvailable ->
                transitioningWithInWindowAnimation && isSurfaceAvailable
            }
            .stateIn(scope, SharingStarted.Eagerly, false)

    /** Sets whether we've started */
    fun setStartedUnlockAnimation(started: Boolean) {
        repository.setStartedUnlockAnimation(started)
    }

    fun setManualUnlockAmount(amount: Float) {
        repository.setManualUnlockAmount(amount)
    }

    fun setLauncherActivityClass(className: String) {
        repository.setLauncherActivityClass(className)
    }

    fun setLauncherSmartspaceState(state: SmartspaceState?) {
        repository.setLauncherSmartspaceState(state)
    }

    /**
     * Whether an activity with the given [activityClass] name is currently underneath the
     * lockscreen (it's at the top of the activity task stack).
     */
    private fun isActivityClassUnderneath(activityClass: String?): Boolean {
        return activityClass?.let {
            activityManager.runningTask?.topActivity?.className?.equals(it)
        }
            ?: false
    }
}
Loading