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