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 package com.android.settings.display
17 
18 import android.content.Context
19 import android.hardware.display.DisplayManager
20 import android.provider.DeviceConfig
21 import android.provider.Settings.System.PEAK_REFRESH_RATE
22 import com.android.internal.display.RefreshRateSettingsUtils.DEFAULT_REFRESH_RATE
23 import com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateAmongAllDisplays
24 import com.android.internal.display.RefreshRateSettingsUtils.findHighestRefreshRateForDefaultDisplay
25 import com.android.server.display.feature.flags.Flags
26 import com.android.settings.R
27 import com.android.settingslib.datastore.HandlerExecutor
28 import com.android.settingslib.datastore.KeyValueStore
29 import com.android.settingslib.datastore.KeyedObservableDelegate
30 import com.android.settingslib.datastore.SettingsStore
31 import com.android.settingslib.datastore.SettingsSystemStore
32 import com.android.settingslib.metadata.PreferenceAvailabilityProvider
33 import com.android.settingslib.metadata.PreferenceLifecycleContext
34 import com.android.settingslib.metadata.PreferenceLifecycleProvider
35 import com.android.settingslib.metadata.PreferenceSummaryProvider
36 import com.android.settingslib.metadata.ReadWritePermit
37 import com.android.settingslib.metadata.SensitivityLevel
38 import com.android.settingslib.metadata.SwitchPreference
39 import kotlin.math.roundToInt
40 
41 // LINT.IfChange
42 class PeakRefreshRateSwitchPreference :
43     SwitchPreference(KEY, R.string.peak_refresh_rate_title),
44     PreferenceAvailabilityProvider,
45     PreferenceSummaryProvider,
46     PreferenceLifecycleProvider {
47 
48     private var propertiesChangedListener: DeviceConfig.OnPropertiesChangedListener? = null
49 
storagenull50     override fun storage(context: Context): KeyValueStore =
51         PeakRefreshRateStore(context, SettingsSystemStore.get(context))
52 
53     override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
54         ReadWritePermit.ALLOW
55 
56     override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
57         ReadWritePermit.ALLOW
58 
59     override val sensitivityLevel
60         get() = SensitivityLevel.NO_SENSITIVITY
61 
62     override fun isAvailable(context: Context) =
63         context.resources.getBoolean(R.bool.config_show_smooth_display) &&
64             context.peakRefreshRate > DEFAULT_REFRESH_RATE
65 
66     override fun getSummary(context: Context): CharSequence? =
67         context.getString(R.string.peak_refresh_rate_summary, context.peakRefreshRate.roundToInt())
68 
69     override fun onStart(context: PreferenceLifecycleContext) {
70         val listener =
71             DeviceConfig.OnPropertiesChangedListener {
72                 // Got notified if any property has been changed in NAMESPACE_DISPLAY_MANAGER. The
73                 // KEY_PEAK_REFRESH_RATE_DEFAULT value could be added, changed, removed or
74                 // unchanged.
75                 // Just force a UI update for any case.
76                 context.notifyPreferenceChange(KEY)
77             }
78 
79         propertiesChangedListener = listener
80 
81         DeviceConfig.addOnPropertiesChangedListener(
82             DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
83             HandlerExecutor.main,
84             listener,
85         )
86     }
87 
onStopnull88     override fun onStop(context: PreferenceLifecycleContext) {
89         propertiesChangedListener?.let {
90             DeviceConfig.removeOnPropertiesChangedListener(it)
91             propertiesChangedListener = null
92         }
93     }
94 
95     @Suppress("UNCHECKED_CAST")
96     private class PeakRefreshRateStore(
97         private val context: Context,
98         private val settingsStore: SettingsStore,
99     ) : KeyedObservableDelegate<String>(settingsStore), KeyValueStore {
100 
containsnull101         override fun contains(key: String) = settingsStore.contains(key)
102 
103         override fun <T : Any> getDefaultValue(key: String, valueType: Class<T>): T? {
104             if (key != KEY) return super.getDefaultValue(key, valueType)
105             return context.defaultPeakRefreshRate.refreshRateAsBoolean(context) as T
106         }
107 
getValuenull108         override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
109             if (key != KEY) return null
110             val refreshRate = settingsStore.getFloat(KEY) ?: context.defaultPeakRefreshRate
111             return refreshRate.refreshRateAsBoolean(context) as T
112         }
113 
Floatnull114         private fun Float.refreshRateAsBoolean(context: Context) =
115             this.isInfinite() || roundToInt() == context.peakRefreshRate.roundToInt()
116 
117         override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
118             when {
119                 key != KEY -> {}
120                 value == null -> settingsStore.setFloat(KEY, null)
121                 else -> {
122                     val peakRefreshRate =
123                         if (value as Boolean) context.refreshRateIfON() else DEFAULT_REFRESH_RATE
124                     settingsStore.setFloat(KEY, peakRefreshRate)
125                 }
126             }
127 
Contextnull128         private fun Context.refreshRateIfON() =
129             when {
130                 Flags.backUpSmoothDisplayAndForcePeakRefreshRate() -> Float.POSITIVE_INFINITY
131                 else -> peakRefreshRate
132             }
133     }
134 
135     companion object {
136         const val KEY = PEAK_REFRESH_RATE
137         private const val INVALIDATE_REFRESH_RATE: Float = -1f
138 
139         private val Context.peakRefreshRate: Float
140             get() =
141                 Math.round(
142                         when {
143                             Flags.backUpSmoothDisplayAndForcePeakRefreshRate() ->
144                                 findHighestRefreshRateAmongAllDisplays(this)
145                             else -> findHighestRefreshRateForDefaultDisplay(this)
146                         }
147                     )
148                     .toFloat()
149 
150         private val Context.defaultPeakRefreshRate: Float
151             get() {
152                 val defaultPeakRefreshRate =
153                     DeviceConfig.getFloat(
154                         DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
155                         DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT,
156                         INVALIDATE_REFRESH_RATE,
157                     )
158                 if (defaultPeakRefreshRate != INVALIDATE_REFRESH_RATE) return defaultPeakRefreshRate
159                 return resources
160                     .getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate)
161                     .toFloat()
162             }
163     }
164 }
165 // LINT.ThenChange(PeakRefreshRatePreferenceController.java)
166