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