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

Commit 4a11b491 authored by Michal Brzezinski's avatar Michal Brzezinski
Browse files

Caching ui state when screen is rotating

Bug: 369945129
Test: finish tutorial, rotate the screen and see tutorial stays finished
Flag: com.android.systemui.shared.new_touchpad_gestures_tutorial
Change-Id: I8edb97327e550e151e765ca87876fdd753834616
parent 11e5ec44
Loading
Loading
Loading
Loading
+77 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.touchpad.tutorial.ui.composable

import androidx.compose.runtime.saveable.SaverScope
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith

@SmallTest
@RunWith(AndroidJUnit4::class)
class TutorialActionStateSaverTest : SysuiTestCase() {

    private val saver = TutorialActionState.stateSaver()
    private val saverScope: SaverScope =
        object : SaverScope {
            override fun canBeSaved(value: Any) = true
        }

    @Test
    fun inProgressIsRestoredToNotStartedState() {
        assertRestoredState(
            savedState = InProgress(progress = 0f),
            expectedRestoredState = NotStarted,
        )
    }

    @Test
    fun inProgressErrorIsRestoredToErrorState() {
        assertRestoredState(
            savedState = InProgressAfterError(InProgress(progress = 0f)),
            expectedRestoredState = Error,
        )
    }

    @Test
    fun otherStatesAreRestoredToTheSameState() {
        assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted)
        assertRestoredState(savedState = Error, expectedRestoredState = Error)
        assertRestoredState(
            savedState = Finished(successAnimation = R.raw.trackpad_home_success),
            expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success),
        )
    }

    private fun assertRestoredState(
        savedState: TutorialActionState,
        expectedRestoredState: TutorialActionState,
    ) {
        val savedValue = with(saver) { saverScope.save(savedState) }
        assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState)
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -45,7 +46,10 @@ import com.android.systemui.res.R
fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
    BackHandler(onBack = onBack)
    val screenConfig = buildScreenConfig()
    var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) }
    var actionState: TutorialActionState by
        rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
            mutableStateOf(NotStarted)
        }
    val focusRequester = remember { FocusRequester() }
    Box(
        modifier =
+27 −0
Original line number Diff line number Diff line
@@ -37,6 +37,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -66,6 +68,31 @@ sealed interface TutorialActionState {

    data class InProgressAfterError(val inProgress: InProgress) :
        TutorialActionState, Progress by inProgress

    companion object {
        fun stateSaver(): Saver<TutorialActionState, Any> {
            val classKey = "class"
            val successAnimationKey = "animation"
            return mapSaver(
                save = {
                    buildMap {
                        put(classKey, it::class.java.name)
                        if (it is Finished) put(successAnimationKey, it.successAnimation)
                    }
                },
                restore = { map ->
                    when (map[classKey] as? String) {
                        NotStarted::class.java.name,
                        InProgress::class.java.name -> NotStarted
                        Error::class.java.name,
                        InProgressAfterError::class.java.name -> Error
                        Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int)
                        else -> NotStarted
                    }
                },
            )
        }
    }
}

interface Progress {
+9 −1
Original line number Diff line number Diff line
@@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -48,8 +51,13 @@ fun GestureTutorialScreen(
    onBack: () -> Unit,
) {
    BackHandler(onBack = onBack)
    var cachedTutorialState: TutorialActionState by
        rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
            mutableStateOf(NotStarted)
        }
    val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
    val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
    val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState)
    cachedTutorialState = tutorialState
    TouchpadGesturesHandlingBox(
        motionEventConsumer,
        tutorialState,