1 /*
2 * Copyright 2020 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 androidx.compose.ui.platform
18
19 import android.content.ComponentCallbacks2
20 import android.content.Context
21 import android.content.res.Configuration
22 import android.content.res.Resources
23 import android.view.View
24 import androidx.compose.runtime.Composable
25 import androidx.compose.runtime.CompositionLocalProvider
26 import androidx.compose.runtime.DisposableEffect
27 import androidx.compose.runtime.Stable
28 import androidx.compose.runtime.compositionLocalOf
29 import androidx.compose.runtime.compositionLocalWithComputedDefaultOf
30 import androidx.compose.runtime.getValue
31 import androidx.compose.runtime.mutableStateOf
32 import androidx.compose.runtime.remember
33 import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
34 import androidx.compose.runtime.setValue
35 import androidx.compose.runtime.staticCompositionLocalOf
36 import androidx.compose.ui.ExperimentalComposeUiApi
37 import androidx.compose.ui.res.ImageVectorCache
38 import androidx.compose.ui.res.ResourceIdCache
39 import androidx.lifecycle.compose.LocalLifecycleOwner
40 import androidx.savedstate.SavedStateRegistryOwner
41
42 /**
43 * The Android [Configuration]. The [Configuration] is useful for determining how to organize the
44 * UI.
45 */
46 val LocalConfiguration =
<lambda>null47 compositionLocalOf<Configuration> { noLocalProvidedFor("LocalConfiguration") }
48
49 /** Provides a [Context] that can be used by Android applications. */
<lambda>null50 val LocalContext = staticCompositionLocalOf<Context> { noLocalProvidedFor("LocalContext") }
51
52 /**
53 * The Android [Resources]. This will be updated when [LocalConfiguration] changes, to ensure that
54 * calls to APIs such as [Resources.getString] return updated values.
55 */
56 val LocalResources =
<lambda>null57 compositionLocalWithComputedDefaultOf<Resources> {
58 // Read LocalConfiguration here to invalidate callers of LocalResources when the
59 // configuration changes. This is preferable to explicitly providing the resources object
60 // because the resources object can still have the same instance, even though the
61 // configuration changed, which would mean that callers would not get invalidated. To
62 // resolve that we would need to use neverEqualPolicy to force an invalidation even though
63 // the Resources didn't change, but then that would cause invalidations every time the
64 // providing Composable is recomposed, regardless of whether a configuration change happened
65 // or not.
66 LocalConfiguration.currentValue
67 LocalContext.currentValue.resources
68 }
69
70 internal val LocalImageVectorCache =
<lambda>null71 staticCompositionLocalOf<ImageVectorCache> { noLocalProvidedFor("LocalImageVectorCache") }
72
73 internal val LocalResourceIdCache =
<lambda>null74 staticCompositionLocalOf<ResourceIdCache> { noLocalProvidedFor("LocalResourceIdCache") }
75
76 @Deprecated(
77 "Moved to lifecycle-runtime-compose library in androidx.lifecycle.compose package.",
78 ReplaceWith("androidx.lifecycle.compose.LocalLifecycleOwner"),
79 )
80 actual val LocalLifecycleOwner
81 get() = LocalLifecycleOwner
82
83 /** The CompositionLocal containing the current [SavedStateRegistryOwner]. */
84 val LocalSavedStateRegistryOwner =
<lambda>null85 staticCompositionLocalOf<SavedStateRegistryOwner> {
86 noLocalProvidedFor("LocalSavedStateRegistryOwner")
87 }
88
89 /** The CompositionLocal containing the current Compose [View]. */
<lambda>null90 val LocalView = staticCompositionLocalOf<View> { noLocalProvidedFor("LocalView") }
91
92 @Composable
93 @OptIn(ExperimentalComposeUiApi::class)
94 internal fun ProvideAndroidCompositionLocals(
95 owner: AndroidComposeView,
96 content: @Composable () -> Unit
97 ) {
98 val view = owner
99 val context = view.context
100 // Make a deep copy to compare to later, since the same configuration object will be mutated
101 // as part of configuration changes
<lambda>null102 var configuration by remember { mutableStateOf(Configuration(context.resources.configuration)) }
103
<lambda>null104 owner.configurationChangeObserver = { configuration = Configuration(it) }
105
<lambda>null106 val uriHandler = remember { AndroidUriHandler(context) }
107 val viewTreeOwners =
108 owner.viewTreeOwners
109 ?: throw IllegalStateException(
110 "Called when the ViewTreeOwnersAvailability is not yet in Available state"
111 )
112
<lambda>null113 val saveableStateRegistry = remember {
114 DisposableSaveableStateRegistry(view, viewTreeOwners.savedStateRegistryOwner)
115 }
<lambda>null116 DisposableEffect(Unit) { onDispose { saveableStateRegistry.dispose() } }
117
<lambda>null118 val hapticFeedback = remember {
119 if (HapticDefaults.isPremiumVibratorEnabled(context)) {
120 DefaultHapticFeedback(owner.view)
121 } else {
122 NoHapticFeedback()
123 }
124 }
125
126 val imageVectorCache = obtainImageVectorCache(context, configuration)
127 val resourceIdCache = obtainResourceIdCache(context)
128 val scrollCaptureInProgress =
129 LocalScrollCaptureInProgress.current or owner.scrollCaptureInProgress
130 CompositionLocalProvider(
131 LocalConfiguration provides configuration,
132 LocalContext provides context,
133 LocalLifecycleOwner provides viewTreeOwners.lifecycleOwner,
134 LocalSavedStateRegistryOwner provides viewTreeOwners.savedStateRegistryOwner,
135 LocalSaveableStateRegistry provides saveableStateRegistry,
136 LocalView provides owner.view,
137 LocalImageVectorCache provides imageVectorCache,
138 LocalResourceIdCache provides resourceIdCache,
139 LocalProvidableScrollCaptureInProgress provides scrollCaptureInProgress,
140 LocalHapticFeedback provides hapticFeedback,
<lambda>null141 ) {
142 ProvideCommonCompositionLocals(owner = owner, uriHandler = uriHandler, content = content)
143 }
144 }
145
146 @Stable
147 @Composable
obtainResourceIdCachenull148 private fun obtainResourceIdCache(context: Context): ResourceIdCache {
149 val resourceIdCache = remember { ResourceIdCache() }
150 val callbacks = remember {
151 object : ComponentCallbacks2 {
152 override fun onConfigurationChanged(newConfig: Configuration) {
153 resourceIdCache.clear()
154 }
155
156 @Deprecated("This callback is superseded by onTrimMemory")
157 override fun onLowMemory() {
158 resourceIdCache.clear()
159 }
160
161 override fun onTrimMemory(level: Int) {
162 resourceIdCache.clear()
163 }
164 }
165 }
166 DisposableEffect(resourceIdCache) {
167 context.applicationContext.registerComponentCallbacks(callbacks)
168 onDispose { context.applicationContext.unregisterComponentCallbacks(callbacks) }
169 }
170 return resourceIdCache
171 }
172
173 @Stable
174 @Composable
obtainImageVectorCachenull175 private fun obtainImageVectorCache(
176 context: Context,
177 configuration: Configuration?
178 ): ImageVectorCache {
179 val imageVectorCache = remember { ImageVectorCache() }
180 val currentConfiguration: Configuration = remember {
181 Configuration().apply { configuration?.let { this.setTo(it) } }
182 }
183 val callbacks = remember {
184 object : ComponentCallbacks2 {
185 override fun onConfigurationChanged(configuration: Configuration) {
186 val changedFlags = currentConfiguration.updateFrom(configuration)
187 imageVectorCache.prune(changedFlags)
188 currentConfiguration.setTo(configuration)
189 }
190
191 @Deprecated("This callback is superseded by onTrimMemory")
192 override fun onLowMemory() {
193 imageVectorCache.clear()
194 }
195
196 override fun onTrimMemory(level: Int) {
197 imageVectorCache.clear()
198 }
199 }
200 }
201 DisposableEffect(imageVectorCache) {
202 context.applicationContext.registerComponentCallbacks(callbacks)
203 onDispose { context.applicationContext.unregisterComponentCallbacks(callbacks) }
204 }
205 return imageVectorCache
206 }
207
noLocalProvidedFornull208 private fun noLocalProvidedFor(name: String): Nothing {
209 error("CompositionLocal $name not present")
210 }
211