• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * 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.settingslib.preference
18 
19 import android.content.Context
20 import androidx.annotation.CallSuper
21 import androidx.preference.DialogPreference
22 import androidx.preference.ListPreference
23 import androidx.preference.Preference
24 import androidx.preference.PreferenceScreen
25 import androidx.preference.SeekBarPreference
26 import com.android.settingslib.metadata.DiscreteIntValue
27 import com.android.settingslib.metadata.DiscreteValue
28 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_ARGS
29 import com.android.settingslib.metadata.EXTRA_BINDING_SCREEN_KEY
30 import com.android.settingslib.metadata.IntRangeValuePreference
31 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
32 import com.android.settingslib.metadata.PreferenceMetadata
33 import com.android.settingslib.metadata.PreferenceScreenMetadata
34 import com.android.settingslib.metadata.getPreferenceIcon
35 import com.android.settingslib.metadata.getPreferenceScreenTitle
36 import com.android.settingslib.metadata.getPreferenceSummary
37 import com.android.settingslib.metadata.getPreferenceTitle
38 
39 /** Binding of preference widget and preference metadata. */
40 interface PreferenceBinding {
41 
42     /**
43      * Provides a new [Preference] widget instance.
44      *
45      * By default, it returns a new [Preference] object. Subclass could override this method to
46      * provide customized widget and do **one-off** initialization (e.g.
47      * [Preference.setOnPreferenceClickListener]). To update widget everytime when state is changed,
48      * override the [bind] method.
49      *
50      * Notes:
51      * - DO NOT set any properties defined in [PreferenceMetadata]. For example,
52      *   title/summary/icon/extras/isEnabled/isVisible/isPersistent/dependency. These properties
53      *   will be reset by [bind].
54      * - Override [bind] if needed to provide more information for customized widget.
55      */
createWidgetnull56     fun createWidget(context: Context): Preference = Preference(context)
57 
58     /**
59      * Binds preference widget with given metadata.
60      *
61      * Whenever metadata state is changed, this callback is invoked to update widget. By default,
62      * the common states like title, summary, enabled, etc. are already applied. Subclass should
63      * override this method to bind more data (e.g. read preference value from storage and apply it
64      * to widget).
65      *
66      * @param preference preference widget created by [createWidget]
67      * @param metadata metadata to apply
68      */
69     @CallSuper
70     fun bind(preference: Preference, metadata: PreferenceMetadata) {
71         metadata.apply {
72             preference.key = key
73             val context = preference.context
74             val preferenceIcon = metadata.getPreferenceIcon(context)
75             if (preferenceIcon != 0) {
76                 preference.setIcon(preferenceIcon)
77             } else {
78                 preference.icon = null
79             }
80             val isPreferenceScreen = preference is PreferenceScreen
81             val screenMetadata = this as? PreferenceScreenMetadata
82             // extras
83             preference.peekExtras()?.clear()
84             extras(context)?.let { preference.extras.putAll(it) }
85             if (!isPreferenceScreen && screenMetadata != null) {
86                 val extras = preference.extras
87                 // Pass the preference key to fragment, so that the fragment could find associated
88                 // preference screen registered in PreferenceScreenRegistry
89                 extras.putString(EXTRA_BINDING_SCREEN_KEY, preference.key)
90                 screenMetadata.arguments?.let { extras.putBundle(EXTRA_BINDING_SCREEN_ARGS, it) }
91             }
92             preference.title =
93                 when {
94                     isPreferenceScreen -> screenMetadata?.getPreferenceScreenTitle(context)
95                     else -> getPreferenceTitle(context)
96                 }
97             if (!isPreferenceScreen) {
98                 preference.summary = getPreferenceSummary(context)
99             }
100             preference.isEnabled = isEnabled(context)
101             preference.isVisible =
102                 (this as? PreferenceAvailabilityProvider)?.isAvailable(context) != false
103             preference.isPersistent = isPersistent(context)
104             // PreferenceScreenBindingHelper will notify dependency change, so we do not need to set
105             // dependency here. This simplifies dependency management and avoid the
106             // IllegalStateException when call Preference.setDependency
107             preference.dependency = null
108             if (!isPreferenceScreen) { // avoid recursive loop when build graph
109                 preference.fragment = screenMetadata?.fragmentClass()?.name
110                 preference.intent = intent(context)
111             }
112             if (preference is DialogPreference) {
113                 preference.dialogTitle = preference.title
114             }
115             if (preference is ListPreference && this is DiscreteValue<*>) {
116                 preference.setEntries(valuesDescription)
117                 if (this is DiscreteIntValue) {
118                     val intValues = context.resources.getIntArray(values)
119                     preference.entryValues = Array(intValues.size) { intValues[it].toString() }
120                 } else {
121                     preference.setEntryValues(values)
122                 }
123             } else if (preference is SeekBarPreference && this is IntRangeValuePreference) {
124                 preference.min = getMinValue(context)
125                 preference.max = getMaxValue(context)
126                 preference.seekBarIncrement = getIncrementStep(context)
127             }
128         }
129     }
130 }
131 
132 /** Interface indicates that a virtual [Preference] should be created for binding. */
133 interface PreferenceBindingPlaceholder
134 
135 /** Abstract preference screen to provide preference hierarchy and binding factory. */
136 interface PreferenceScreenCreator : PreferenceScreenMetadata, PreferenceScreenProvider {
137 
138     /** Returns if the flag (e.g. for rollout) is enabled on current screen. */
isFlagEnablednull139     fun isFlagEnabled(context: Context): Boolean = true
140 
141     val preferenceBindingFactory: PreferenceBindingFactory
142         get() = PreferenceBindingFactory.defaultFactory
143 
144     override fun createPreferenceScreen(factory: PreferenceScreenFactory) =
145         factory.getOrCreatePreferenceScreen().apply {
146             inflatePreferenceHierarchy(preferenceBindingFactory, getPreferenceHierarchy(context))
147         }
148 }
149