1 /*
<lambda>null2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.scene.session.ui.composable
18 
19 import androidx.compose.runtime.Composable
20 import androidx.compose.runtime.SideEffect
21 import androidx.compose.runtime.currentCompositeKeyHash
22 import androidx.compose.runtime.getValue
23 import androidx.compose.runtime.mutableStateOf
24 import androidx.compose.runtime.saveable.Saver
25 import androidx.compose.runtime.saveable.SaverScope
26 import androidx.compose.runtime.saveable.mapSaver
27 import androidx.compose.runtime.saveable.rememberSaveable
28 import androidx.compose.runtime.setValue
29 import com.android.systemui.scene.session.shared.SessionStorage
30 import com.android.systemui.util.kotlin.mapValuesNotNullTo
31 
32 /**
33  * An explicit storage for remembering composable state outside of the lifetime of a composition.
34  *
35  * Specifically, this allows easy conversion of standard
36  * [remember][androidx.compose.runtime.remember] invocations to ones that are preserved beyond the
37  * callsite's existence in the composition.
38  *
39  * ```kotlin
40  * @Composable
41  * fun Parent() {
42  *   val session = remember { Session() }
43  *   ...
44  *   if (someCondition) {
45  *     Child(session)
46  *   }
47  * }
48  *
49  * @Composable
50  * fun Child(session: Session) {
51  *   val state by session.rememberSession { mutableStateOf(0f) }
52  *   ...
53  * }
54  * ```
55  */
56 interface Session {
57     /**
58      * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had
59      * in the previous composition, otherwise produce and remember a new value by calling [init].
60      *
61      * @param inputs A set of inputs such that, when any of them have changed, will cause the state
62      *   to reset and [init] to be rerun
63      * @param key An optional key to be used as a key for the saved value. If `null`, we use the one
64      *   automatically generated by the Compose runtime which is unique for the every exact code
65      *   location in the composition tree
66      * @param init A factory function to create the initial value of this state
67      * @see androidx.compose.runtime.remember
68      */
69     @Composable fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T
70 }
71 
72 /** Returns a new [Session], optionally backed by the provided [SessionStorage]. */
Sessionnull73 fun Session(storage: SessionStorage = SessionStorage()): Session = SessionImpl(storage)
74 
75 /**
76  * Remember the value returned by [init] if all [inputs] are equal (`==`) to the values they had in
77  * the previous composition, otherwise produce and remember a new value by calling [init].
78  *
79  * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
80  *   reset and [init] to be rerun
81  * @param key An optional key to be used as a key for the saved value. If not provided we use the
82  *   one automatically generated by the Compose runtime which is unique for the every exact code
83  *   location in the composition tree
84  * @param init A factory function to create the initial value of this state
85  * @see androidx.compose.runtime.remember
86  */
87 @Composable
88 fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T =
89     rememberSession(key, inputs, init = init)
90 
91 /**
92  * An explicit storage for remembering composable state outside of the lifetime of a composition.
93  *
94  * Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that
95  * are preserved beyond the callsite's existence in the composition.
96  *
97  * ```kotlin
98  * @Composable
99  * fun Parent() {
100  *   val session = rememberSaveableSession()
101  *   ...
102  *   if (someCondition) {
103  *     Child(session)
104  *   }
105  * }
106  *
107  * @Composable
108  * fun Child(session: SaveableSession) {
109  *   val state by session.rememberSaveableSession { mutableStateOf(0f) }
110  *   ...
111  * }
112  * ```
113  */
114 interface SaveableSession : Session {
115     /**
116      * Remember the value produced by [init].
117      *
118      * It behaves similarly to [rememberSession], but the stored value will survive the activity or
119      * process recreation using the saved instance state mechanism (for example it happens when the
120      * screen is rotated in the Android application).
121      *
122      * @param inputs A set of inputs such that, when any of them have changed, will cause the state
123      *   to reset and [init] to be rerun
124      * @param saver The [Saver] object which defines how the state is saved and restored.
125      * @param key An optional key to be used as a key for the saved value. If not provided we use
126      *   the automatically generated by the Compose runtime which is unique for the every exact code
127      *   location in the composition tree
128      * @param init A factory function to create the initial value of this state
129      * @see rememberSaveable
130      */
131     @Composable
132     fun <T : Any> rememberSaveableSession(
133         vararg inputs: Any?,
134         saver: Saver<T, out Any>,
135         key: String?,
136         init: () -> T,
137     ): T
138 }
139 
140 /**
141  * Returns a new [SaveableSession] that is preserved across configuration changes.
142  *
143  * @param inputs A set of inputs such that, when any of them have changed, will cause the state to
144  *   reset.
145  * @param key An optional key to be used as a key for the saved value. If not provided we use the
146  *   automatically generated by the Compose runtime which is unique for the every exact code
147  *   location in the composition tree.
148  */
149 @Composable
rememberSaveableSessionnull150 fun rememberSaveableSession(
151     vararg inputs: Any?,
152     key: String? = null,
153 ): SaveableSession =
154     rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
155 
156 private class SessionImpl(
157     private val storage: SessionStorage = SessionStorage(),
158 ) : Session {
159     @Composable
rememberSessionnull160     override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T {
161         val storage = storage.storage
162         val compositeKey = currentCompositeKeyHash
163         // key is the one provided by the user or the one generated by the compose runtime
164         val finalKey =
165             if (!key.isNullOrEmpty()) {
166                 key
167             } else {
168                 compositeKey.toString(MAX_SUPPORTED_RADIX)
169             }
170         if (finalKey !in storage) {
171             val value = init()
172             SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) }
173             return value
174         }
175         val entry = storage[finalKey]!!
176         if (!inputs.contentEquals(entry.keys)) {
177             val value = init()
178             SideEffect { entry.stored = value }
179             return value
180         }
181         @Suppress("UNCHECKED_CAST") return entry.stored as T
182     }
183 }
184 
185 private class SaveableSessionImpl(
186     saveableStorage: MutableMap<String, StorageEntry> = mutableMapOf(),
187     sessionStorage: SessionStorage = SessionStorage(),
<lambda>null188 ) : SaveableSession, Session by Session(sessionStorage) {
189 
190     var saveableStorage: MutableMap<String, StorageEntry> by mutableStateOf(saveableStorage)
191 
192     @Composable
193     override fun <T : Any> rememberSaveableSession(
194         vararg inputs: Any?,
195         saver: Saver<T, out Any>,
196         key: String?,
197         init: () -> T,
198     ): T {
199         val compositeKey = currentCompositeKeyHash
200         // key is the one provided by the user or the one generated by the compose runtime
201         val finalKey =
202             if (!key.isNullOrEmpty()) {
203                 key
204             } else {
205                 compositeKey.toString(MAX_SUPPORTED_RADIX)
206             }
207 
208         @Suppress("UNCHECKED_CAST") (saver as Saver<T, Any>)
209 
210         if (finalKey !in saveableStorage) {
211             val value = init()
212             SideEffect { saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver) }
213             return value
214         }
215         when (val entry = saveableStorage[finalKey]!!) {
216             is StorageEntry.Unrestored -> {
217                 val value = saver.restore(entry.unrestored) ?: init()
218                 SideEffect {
219                     saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
220                 }
221                 return value
222             }
223             is StorageEntry.Restored<*> -> {
224                 if (!inputs.contentEquals(entry.inputs)) {
225                     val value = init()
226                     SideEffect {
227                         saveableStorage[finalKey] = StorageEntry.Restored(inputs, value, saver)
228                     }
229                     return value
230                 }
231                 @Suppress("UNCHECKED_CAST") return entry.stored as T
232             }
233         }
234     }
235 
236     sealed class StorageEntry {
237         class Unrestored(val unrestored: Any) : StorageEntry()
238 
239         class Restored<T>(val inputs: Array<out Any?>, var stored: T, val saver: Saver<T, Any>) :
240             StorageEntry() {
241             fun SaverScope.saveEntry() {
242                 with(saver) { stored?.let { save(it) } }
243             }
244         }
245     }
246 
247     object SessionSaver :
248         Saver<SaveableSessionImpl, Any> by mapSaver(
249             save = { sessionScope: SaveableSessionImpl ->
250                 sessionScope.saveableStorage.mapValues { (k, v) ->
251                     when (v) {
252                         is StorageEntry.Unrestored -> v.unrestored
253                         is StorageEntry.Restored<*> -> {
254                             with(v) { saveEntry() }
255                         }
256                     }
257                 }
258             },
259             restore = { savedMap: Map<String, Any?> ->
260                 SaveableSessionImpl(
261                     saveableStorage =
262                         savedMap.mapValuesNotNullTo(mutableMapOf()) { (k, v) ->
263                             v?.let { StorageEntry.Unrestored(v) }
264                         }
265                 )
266             }
267         )
268 }
269 
270 private const val MAX_SUPPORTED_RADIX = 36
271