• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 package com.android.systemui.util.settings
17 
18 import android.annotation.UserIdInt
19 import android.content.ContentResolver
20 import android.database.ContentObserver
21 import android.net.Uri
22 import android.provider.Settings
23 import android.provider.Settings.SettingNotFoundException
24 import androidx.annotation.AnyThread
25 import androidx.annotation.WorkerThread
26 import com.android.app.tracing.TraceUtils.trace
27 import com.android.app.tracing.coroutines.launchTraced as launch
28 import com.android.app.tracing.coroutines.withContextTraced as withContext
29 import kotlin.coroutines.coroutineContext
30 import kotlinx.coroutines.CoroutineDispatcher
31 import kotlinx.coroutines.CoroutineScope
32 import kotlinx.coroutines.Job
33 
34 /**
35  * Used to interact with mainly with Settings.Global, but can also be used for Settings.System and
36  * Settings.Secure. To use the per-user System and Secure settings, [UserSettingsProxy] must be used
37  * instead.
38  *
39  * This interface can be implemented to give instance method (instead of static method) versions of
40  * Settings.Global. It can be injected into class constructors and then faked or mocked as needed in
41  * tests.
42  *
43  * You can ask for [GlobalSettings] to be injected as needed.
44  *
45  * This class also provides [.registerContentObserver] methods, normally found on [ContentResolver]
46  * instances, unifying setting related actions in one place.
47  */
48 public interface SettingsProxy {
49     /** Returns the [ContentResolver] this instance was constructed with. */
getContentResolvernull50     public fun getContentResolver(): ContentResolver
51 
52     /** Returns the [CoroutineScope] that the async APIs will use. */
53     public val settingsScope: CoroutineScope
54 
55     @OptIn(ExperimentalStdlibApi::class)
56     public suspend fun executeOnSettingsScopeDispatcher(name: String, block: () -> Unit) {
57         val settingsDispatcher = settingsScope.coroutineContext[CoroutineDispatcher]
58         if (
59             settingsDispatcher != null &&
60                 settingsDispatcher != coroutineContext[CoroutineDispatcher]
61         ) {
62             withContext(name, settingsDispatcher) { block() }
63         } else {
64             trace(name) { block() }
65         }
66     }
67 
68     /**
69      * Construct the content URI for a particular name/value pair, useful for monitoring changes
70      * with a ContentObserver.
71      *
72      * @param name to look up in the table
73      * @return the corresponding content URI, or null if not present
74      */
getUriFornull75     @AnyThread public fun getUriFor(name: String): Uri
76 
77     /**
78      * Registers listener for a given content observer <b>while blocking the current thread</b>.
79      * Implicitly calls [getUriFor] on the passed in name.
80      *
81      * This should not be called from the main thread, use [registerContentObserver] or
82      * [registerContentObserverAsync] instead.
83      */
84     @WorkerThread
85     public fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) {
86         registerContentObserverSync(getUriFor(name), settingsObserver)
87     }
88 
89     /**
90      * Convenience wrapper around [ContentResolver.registerContentObserver].'
91      *
92      * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
93      * registration happens on a worker thread. Caller may wrap the API in an async block if they
94      * wish to synchronize execution.
95      */
registerContentObservernull96     public suspend fun registerContentObserver(name: String, settingsObserver: ContentObserver) {
97         executeOnSettingsScopeDispatcher("registerContentObserver-A") {
98             registerContentObserverSync(getUriFor(name), settingsObserver)
99         }
100     }
101 
102     /**
103      * Convenience wrapper around [ContentResolver.registerContentObserver].'
104      *
105      * API corresponding to [registerContentObserver] for Java usage.
106      */
107     @AnyThread
registerContentObserverAsyncnull108     public fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver): Job =
109         settingsScope.launch("registerContentObserverAsync-A") {
110             registerContentObserverSync(getUriFor(name), settingsObserver)
111         }
112 
113     /**
114      * Convenience wrapper around [ContentResolver.registerContentObserver].'
115      *
116      * API corresponding to [registerContentObserver] for Java usage. After registration is
117      * complete, the callback block is called on the <b>background thread</b> to allow for update of
118      * value.
119      */
120     @AnyThread
registerContentObserverAsyncnull121     public fun registerContentObserverAsync(
122         name: String,
123         settingsObserver: ContentObserver,
124         @WorkerThread registered: Runnable,
125     ): Job =
126         settingsScope.launch("registerContentObserverAsync-B") {
127             registerContentObserverSync(getUriFor(name), settingsObserver)
128             registered.run()
129         }
130 
131     /**
132      * Registers listener for a given content observer <b>while blocking the current thread</b>.
133      *
134      * This should not be called from the main thread, use [registerContentObserver] or
135      * [registerContentObserverAsync] instead.
136      */
137     @WorkerThread
registerContentObserverSyncnull138     public fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) {
139         registerContentObserverSync(uri, false, settingsObserver)
140     }
141 
142     /**
143      * Convenience wrapper around [ContentResolver.registerContentObserver].'
144      *
145      * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
146      * registration happens on a worker thread. Caller may wrap the API in an async block if they
147      * wish to synchronize execution.
148      */
registerContentObservernull149     public suspend fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) {
150         executeOnSettingsScopeDispatcher("registerContentObserver-B") {
151             registerContentObserverSync(uri, settingsObserver)
152         }
153     }
154 
155     /**
156      * Convenience wrapper around [ContentResolver.registerContentObserver].'
157      *
158      * API corresponding to [registerContentObserver] for Java usage.
159      */
160     @AnyThread
registerContentObserverAsyncnull161     public fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver): Job =
162         settingsScope.launch("registerContentObserverAsync-C") {
163             registerContentObserverSync(uri, settingsObserver)
164         }
165 
166     /**
167      * Convenience wrapper around [ContentResolver.registerContentObserver].'
168      *
169      * API corresponding to [registerContentObserver] for Java usage. After registration is
170      * complete, the callback block is called on the <b>background thread</b> to allow for update of
171      * value.
172      */
173     @AnyThread
registerContentObserverAsyncnull174     public fun registerContentObserverAsync(
175         uri: Uri,
176         settingsObserver: ContentObserver,
177         @WorkerThread registered: Runnable,
178     ): Job =
179         settingsScope.launch("registerContentObserverAsync-D") {
180             registerContentObserverSync(uri, settingsObserver)
181             registered.run()
182         }
183 
184     /**
185      * Convenience wrapper around [ContentResolver.registerContentObserver].'
186      *
187      * Implicitly calls [getUriFor] on the passed in name.
188      */
189     @WorkerThread
registerContentObserverSyncnull190     public fun registerContentObserverSync(
191         name: String,
192         notifyForDescendants: Boolean,
193         settingsObserver: ContentObserver,
194     ) {
195         registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
196     }
197 
198     /**
199      * Convenience wrapper around [ContentResolver.registerContentObserver].'
200      *
201      * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
202      * registration happens on a worker thread. Caller may wrap the API in an async block if they
203      * wish to synchronize execution.
204      */
registerContentObservernull205     public suspend fun registerContentObserver(
206         name: String,
207         notifyForDescendants: Boolean,
208         settingsObserver: ContentObserver,
209     ) {
210         executeOnSettingsScopeDispatcher("registerContentObserver-C") {
211             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
212         }
213     }
214 
215     /**
216      * Convenience wrapper around [ContentResolver.registerContentObserver].'
217      *
218      * API corresponding to [registerContentObserver] for Java usage.
219      */
220     @AnyThread
registerContentObserverAsyncnull221     public fun registerContentObserverAsync(
222         name: String,
223         notifyForDescendants: Boolean,
224         settingsObserver: ContentObserver,
225     ): Job =
226         settingsScope.launch("registerContentObserverAsync-E") {
227             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
228         }
229 
230     /**
231      * Convenience wrapper around [ContentResolver.registerContentObserver].'
232      *
233      * API corresponding to [registerContentObserver] for Java usage. After registration is
234      * complete, the callback block is called on the <b>background thread</b> to allow for update of
235      * value.
236      */
237     @AnyThread
registerContentObserverAsyncnull238     public fun registerContentObserverAsync(
239         name: String,
240         notifyForDescendants: Boolean,
241         settingsObserver: ContentObserver,
242         @WorkerThread registered: Runnable,
243     ): Job =
244         settingsScope.launch("registerContentObserverAsync-F") {
245             registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver)
246             registered.run()
247         }
248 
249     /**
250      * Registers listener for a given content observer <b>while blocking the current thread</b>.
251      *
252      * This should not be called from the main thread, use [registerContentObserver] or
253      * [registerContentObserverAsync] instead.
254      */
255     @WorkerThread
registerContentObserverSyncnull256     public fun registerContentObserverSync(
257         uri: Uri,
258         notifyForDescendants: Boolean,
259         settingsObserver: ContentObserver,
260     ) {
261         trace({ "SP#registerObserver#[$uri]" }) {
262             getContentResolver()
263                 .registerContentObserver(uri, notifyForDescendants, settingsObserver)
264         }
265     }
266 
267     /**
268      * Convenience wrapper around [ContentResolver.registerContentObserver].'
269      *
270      * suspend API corresponding to [registerContentObserver] to ensure that [ContentObserver]
271      * registration happens on a worker thread. Caller may wrap the API in an async block if they
272      * wish to synchronize execution.
273      */
registerContentObservernull274     public suspend fun registerContentObserver(
275         uri: Uri,
276         notifyForDescendants: Boolean,
277         settingsObserver: ContentObserver,
278     ) {
279         executeOnSettingsScopeDispatcher("registerContentObserver-D") {
280             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
281         }
282     }
283 
284     /**
285      * Convenience wrapper around [ContentResolver.registerContentObserver].'
286      *
287      * API corresponding to [registerContentObserver] for Java usage.
288      */
289     @AnyThread
registerContentObserverAsyncnull290     public fun registerContentObserverAsync(
291         uri: Uri,
292         notifyForDescendants: Boolean,
293         settingsObserver: ContentObserver,
294     ): Job =
295         settingsScope.launch("registerContentObserverAsync-G") {
296             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
297         }
298 
299     /**
300      * Convenience wrapper around [ContentResolver.registerContentObserver].'
301      *
302      * API corresponding to [registerContentObserver] for Java usage. After registration is
303      * complete, the callback block is called on the <b>background thread</b> to allow for update of
304      * value.
305      */
306     @AnyThread
registerContentObserverAsyncnull307     public fun registerContentObserverAsync(
308         uri: Uri,
309         notifyForDescendants: Boolean,
310         settingsObserver: ContentObserver,
311         @WorkerThread registered: Runnable,
312     ): Job =
313         settingsScope.launch("registerContentObserverAsync-H") {
314             registerContentObserverSync(uri, notifyForDescendants, settingsObserver)
315             registered.run()
316         }
317 
318     /**
319      * Unregisters the given content observer <b>while blocking the current thread</b>.
320      *
321      * This should not be called from the main thread, use [unregisterContentObserver] or
322      * [unregisterContentObserverAsync] instead.
323      */
324     @WorkerThread
unregisterContentObserverSyncnull325     public fun unregisterContentObserverSync(settingsObserver: ContentObserver) {
326         trace({ "SP#unregisterObserver" }) {
327             getContentResolver().unregisterContentObserver(settingsObserver)
328         }
329     }
330 
331     /**
332      * Convenience wrapper around [ContentResolver.unregisterContentObserver].'
333      *
334      * API corresponding to [unregisterContentObserver] for Java usage to ensure that
335      * [ContentObserver] un-registration happens on a worker thread. Caller may wrap the API in an
336      * async block if they wish to synchronize execution.
337      */
unregisterContentObservernull338     public suspend fun unregisterContentObserver(settingsObserver: ContentObserver) {
339         executeOnSettingsScopeDispatcher("unregisterContentObserver") {
340             unregisterContentObserverSync(settingsObserver)
341         }
342     }
343 
344     /**
345      * Convenience wrapper around [ContentResolver.unregisterContentObserver].'
346      *
347      * API corresponding to [unregisterContentObserver] for Java usage to ensure that
348      * [ContentObserver] registration happens on a worker thread.
349      */
350     @AnyThread
unregisterContentObserverAsyncnull351     public fun unregisterContentObserverAsync(settingsObserver: ContentObserver): Job =
352         settingsScope.launch("unregisterContentObserverAsync") {
353             unregisterContentObserver(settingsObserver)
354         }
355 
356     /**
357      * Look up a name in the database.
358      *
359      * @param name to look up in the table
360      * @return the corresponding value, or null if not present
361      */
getStringnull362     public fun getString(name: String): String?
363 
364     /**
365      * Store a name/value pair into the database.
366      *
367      * @param name to store
368      * @param value to associate with the name
369      * @return true if the value was set, false on database errors
370      */
371     public fun putString(name: String, value: String?): Boolean
372 
373     /**
374      * Store a name/value pair into the database.
375      *
376      * The method takes an optional tag to associate with the setting which can be used to clear
377      * only settings made by your package and associated with this tag by passing the tag to
378      * [ ][.resetToDefaults]. Anyone can override the current tag. Also if another package changes
379      * the setting then the tag will be set to the one specified in the set call which can be null.
380      * Also any of the settings setters that do not take a tag as an argument effectively clears the
381      * tag.
382      *
383      * For example, if you set settings A and B with tags T1 and T2 and another app changes setting
384      * A (potentially to the same value), it can assign to it a tag T3 (note that now the package
385      * that changed the setting is not yours). Now if you reset your changes for T1 and T2 only
386      * setting B will be reset and A not (as it was changed by another package) but since A did not
387      * change you are in the desired initial state. Now if the other app changes the value of A
388      * (assuming you registered an observer in the beginning) you would detect that the setting was
389      * changed by another app and handle this appropriately (ignore, set back to some value, etc).
390      *
391      * Also the method takes an argument whether to make the value the default for this setting. If
392      * the system already specified a default value, then the one passed in here will **not** be set
393      * as the default.
394      *
395      * @param name to store.
396      * @param value to associate with the name.
397      * @param tag to associate with the setting.
398      * @param makeDefault whether to make the value the default one.
399      * @return true if the value was set, false on database errors.
400      * @see .resetToDefaults
401      */
402     public fun putString(name: String, value: String?, tag: String?, makeDefault: Boolean): Boolean
403 
404     /**
405      * Convenience function for retrieving a single secure settings value as an integer. Note that
406      * internally setting values are always stored as strings; this function converts the string to
407      * an integer for you. The default value will be returned if the setting is not defined or not
408      * an integer.
409      *
410      * @param name The name of the setting to retrieve.
411      * @param default Value to return if the setting is not defined.
412      * @return The setting's current value, or default if it is not defined or not a valid integer.
413      */
414     public fun getInt(name: String, default: Int): Int {
415         val v = getString(name)
416         return try {
417             v?.toInt() ?: default
418         } catch (e: NumberFormatException) {
419             default
420         }
421     }
422 
423     /**
424      * Convenience function for retrieving a single secure settings value as an integer. Note that
425      * internally setting values are always stored as strings; this function converts the string to
426      * an integer for you.
427      *
428      * This version does not take a default value. If the setting has not been set, or the string
429      * value is not a number, it throws [Settings.SettingNotFoundException].
430      *
431      * @param name The name of the setting to retrieve.
432      * @return The setting's current value.
433      * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
434      *   found or the setting value is not an integer.
435      */
436     @Throws(SettingNotFoundException::class)
getIntnull437     public fun getInt(name: String): Int {
438         val v = getString(name) ?: throw SettingNotFoundException(name)
439         return try {
440             v.toInt()
441         } catch (e: NumberFormatException) {
442             throw SettingNotFoundException(name)
443         }
444     }
445 
446     /**
447      * Convenience function for updating a single settings value as an integer. This will either
448      * create a new entry in the table if the given name does not exist, or modify the value of the
449      * existing row with that name. Note that internally setting values are always stored as
450      * strings, so this function converts the given value to a string before storing it.
451      *
452      * @param name The name of the setting to modify.
453      * @param value The new value for the setting.
454      * @return true if the value was set, false on database errors
455      */
putIntnull456     public fun putInt(name: String, value: Int): Boolean {
457         return putString(name, value.toString())
458     }
459 
460     /**
461      * Convenience function for retrieving a single secure settings value as a boolean. Note that
462      * internally setting values are always stored as strings; this function converts the string to
463      * a boolean for you. The default value will be returned if the setting is not defined or not a
464      * boolean.
465      *
466      * @param name The name of the setting to retrieve.
467      * @param default Value to return if the setting is not defined.
468      * @return The setting's current value, or default if it is not defined or not a valid boolean.
469      */
getBoolnull470     public fun getBool(name: String, default: Boolean): Boolean {
471         return getInt(name, if (default) 1 else 0) != 0
472     }
473 
474     /**
475      * Convenience function for retrieving a single secure settings value as a boolean. Note that
476      * internally setting values are always stored as strings; this function converts the string to
477      * a boolean for you.
478      *
479      * This version does not take a default value. If the setting has not been set, or the string
480      * value is not a number, it throws [Settings.SettingNotFoundException].
481      *
482      * @param name The name of the setting to retrieve.
483      * @return The setting's current value.
484      * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
485      *   found or the setting value is not a boolean.
486      */
487     @Throws(SettingNotFoundException::class)
getBoolnull488     public fun getBool(name: String): Boolean {
489         return getInt(name) != 0
490     }
491 
492     /**
493      * Convenience function for updating a single settings value as a boolean. This will either
494      * create a new entry in the table if the given name does not exist, or modify the value of the
495      * existing row with that name. Note that internally setting values are always stored as
496      * strings, so this function converts the given value to a string before storing it.
497      *
498      * @param name The name of the setting to modify.
499      * @param value The new value for the setting.
500      * @return true if the value was set, false on database errors
501      */
putBoolnull502     public fun putBool(name: String, value: Boolean): Boolean {
503         return putInt(name, if (value) 1 else 0)
504     }
505 
506     /**
507      * Convenience function for retrieving a single secure settings value as a `long`. Note that
508      * internally setting values are always stored as strings; this function converts the string to
509      * a `long` for you. The default value will be returned if the setting is not defined or not a
510      * `long`.
511      *
512      * @param name The name of the setting to retrieve.
513      * @param def Value to return if the setting is not defined.
514      * @return The setting's current value, or 'def' if it is not defined or not a valid `long`.
515      */
getLongnull516     public fun getLong(name: String, def: Long): Long {
517         val valString = getString(name)
518         return parseLongOrUseDefault(valString, def)
519     }
520 
521     /**
522      * Convenience function for retrieving a single secure settings value as a `long`. Note that
523      * internally setting values are always stored as strings; this function converts the string to
524      * a `long` for you.
525      *
526      * This version does not take a default value. If the setting has not been set, or the string
527      * value is not a number, it throws [Settings.SettingNotFoundException].
528      *
529      * @param name The name of the setting to retrieve.
530      * @return The setting's current value.
531      * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
532      *   found or the setting value is not an integer.
533      */
534     @Throws(SettingNotFoundException::class)
getLongnull535     public fun getLong(name: String): Long {
536         val valString = getString(name)
537         return parseLongOrThrow(name, valString)
538     }
539 
540     /**
541      * Convenience function for updating a secure settings value as a long integer. This will either
542      * create a new entry in the table if the given name does not exist, or modify the value of the
543      * existing row with that name. Note that internally setting values are always stored as
544      * strings, so this function converts the given value to a string before storing it.
545      *
546      * @param name The name of the setting to modify.
547      * @param value The new value for the setting.
548      * @return true if the value was set, false on database errors
549      */
putLongnull550     public fun putLong(name: String, value: Long): Boolean {
551         return putString(name, value.toString())
552     }
553 
554     /**
555      * Convenience function for retrieving a single secure settings value as a floating point
556      * number. Note that internally setting values are always stored as strings; this function
557      * converts the string to an float for you. The default value will be returned if the setting is
558      * not defined or not a valid float.
559      *
560      * @param name The name of the setting to retrieve.
561      * @param def Value to return if the setting is not defined.
562      * @return The setting's current value, or 'def' if it is not defined or not a valid float.
563      */
getFloatnull564     public fun getFloat(name: String, def: Float): Float {
565         val v = getString(name)
566         return parseFloat(v, def)
567     }
568 
569     /**
570      * Convenience function for retrieving a single secure settings value as a float. Note that
571      * internally setting values are always stored as strings; this function converts the string to
572      * a float for you.
573      *
574      * This version does not take a default value. If the setting has not been set, or the string
575      * value is not a number, it throws [Settings.SettingNotFoundException].
576      *
577      * @param name The name of the setting to retrieve.
578      * @return The setting's current value.
579      * @throws Settings.SettingNotFoundException Thrown if a setting by the given name can't be
580      *   found or the setting value is not a float.
581      */
582     @Throws(SettingNotFoundException::class)
getFloatnull583     public fun getFloat(name: String): Float {
584         val v = getString(name)
585         return parseFloatOrThrow(name, v)
586     }
587 
588     /**
589      * Convenience function for updating a single settings value as a floating point number. This
590      * will either create a new entry in the table if the given name does not exist, or modify the
591      * value of the existing row with that name. Note that internally setting values are always
592      * stored as strings, so this function converts the given value to a string before storing it.
593      *
594      * @param name The name of the setting to modify.
595      * @param value The new value for the setting.
596      * @return true if the value was set, false on database errors
597      */
putFloatnull598     public fun putFloat(name: String, value: Float): Boolean {
599         return putString(name, value.toString())
600     }
601 
602     public companion object {
603         /** Convert a string to a long, or uses a default if the string is malformed or null */
604         @JvmStatic
parseLongOrUseDefaultnull605         public fun parseLongOrUseDefault(valString: String?, default: Long): Long {
606             val value: Long =
607                 try {
608                     valString?.toLong() ?: default
609                 } catch (e: NumberFormatException) {
610                     default
611                 }
612             return value
613         }
614 
615         /** Convert a string to a long, or throws an exception if the string is malformed or null */
616         @JvmStatic
617         @Throws(SettingNotFoundException::class)
parseLongOrThrownull618         public fun parseLongOrThrow(name: String, valString: String?): Long {
619             if (valString == null) {
620                 throw SettingNotFoundException(name)
621             }
622             return try {
623                 valString.toLong()
624             } catch (e: NumberFormatException) {
625                 throw SettingNotFoundException(name)
626             }
627         }
628 
629         /** Convert a string to a float, or uses a default if the string is malformed or null */
630         @JvmStatic
parseFloatnull631         public fun parseFloat(v: String?, def: Float): Float {
632             return try {
633                 v?.toFloat() ?: def
634             } catch (e: NumberFormatException) {
635                 def
636             }
637         }
638 
639         /**
640          * Convert a string to a float, or throws an exception if the string is malformed or null
641          */
642         @JvmStatic
643         @Throws(SettingNotFoundException::class)
parseFloatOrThrownull644         public fun parseFloatOrThrow(name: String, v: String?): Float {
645             if (v == null) {
646                 throw SettingNotFoundException(name)
647             }
648             return try {
649                 v.toFloat()
650             } catch (e: NumberFormatException) {
651                 throw SettingNotFoundException(name)
652             }
653         }
654     }
655 
656     public fun interface CurrentUserIdProvider {
getUserIdnull657         @UserIdInt public fun getUserId(): Int
658     }
659 }
660