1 /*
2  * Copyright (C) 2019 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.tv.settings.device.display.daydream;
18 
19 import static android.provider.Settings.Secure.ATTENTIVE_TIMEOUT;
20 import static android.provider.Settings.Secure.SLEEP_TIMEOUT;
21 
22 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected;
23 
24 import android.app.AlertDialog;
25 import android.app.tvsettings.TvSettingsEnums;
26 import android.content.Context;
27 import android.content.SharedPreferences;
28 import android.os.Bundle;
29 import android.os.UserManager;
30 import android.provider.Settings;
31 import android.text.format.DateUtils;
32 import android.util.Log;
33 
34 import androidx.annotation.Keep;
35 import androidx.preference.ListPreference;
36 import androidx.preference.Preference;
37 
38 import com.android.tv.settings.R;
39 import com.android.tv.settings.RestrictedPreferenceAdapter;
40 import com.android.tv.settings.SettingsPreferenceFragment;
41 import com.android.tv.twopanelsettings.TwoPanelSettingsFragment;
42 
43 /**
44  * The energy saver screen in TV settings.
45  */
46 @Keep
47 public class EnergySaverFragment extends SettingsPreferenceFragment implements
48         Preference.OnPreferenceChangeListener {
49     private static final String TAG = "EnergySaverFragment";
50     private static final String KEY_SLEEP_TIME = "sleepTime";
51     private static final String KEY_ATTENTIVE_TIME = "attentiveTime";
52 
53     private static final String SHARED_PREFS_NAME = "energy_saver";
54     private static final String PREF_RESET_ATTENTIVE_TIMEOUT = "reset_attentive_timeout";
55 
56     private static final int DEFAULT_SLEEP_TIME_MS = (int) (20 * DateUtils.MINUTE_IN_MILLIS);
57     private static final int WARNING_THRESHOLD_SLEEP_TIME_MS =
58             (int) (20 * DateUtils.MINUTE_IN_MILLIS);
59     private static final int WARNING_THRESHOLD_ATTENTIVE_TIME_MS =
60             (int) (4 * DateUtils.HOUR_IN_MILLIS);
61 
62     private ListPreference mSleepTimePref;
63     private ListPreference mAttentiveTimePref;
64     private RestrictedPreferenceAdapter<ListPreference> mRestrictedSleepTime;
65     private RestrictedPreferenceAdapter<ListPreference> mRestrictedAttentiveTime;
66     private int mDefaultAttentiveTimeoutConfig;
67 
68     @Override
onCreatePreferences(Bundle bundle, String s)69     public void onCreatePreferences(Bundle bundle, String s) {
70         setPreferencesFromResource(R.xml.energy_saver, null);
71 
72         mDefaultAttentiveTimeoutConfig = getResources()
73             .getInteger(com.android.internal.R.integer.config_attentiveTimeout);
74 
75         mSleepTimePref = findPreference(KEY_SLEEP_TIME);
76         int validatedSleepTime = getValidatedTimeout(getSleepTime(), true);
77         mSleepTimePref.setValue(String.valueOf(validatedSleepTime));
78         if (getSleepTime() != validatedSleepTime) {
79             setSleepTime(validatedSleepTime);
80         }
81         mSleepTimePref.setOnPreferenceChangeListener(this);
82         mSleepTimePref.setOnPreferenceClickListener(
83                 preference -> {
84                     logEntrySelected(TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY);
85                     return false;
86                 });
87 
88         mAttentiveTimePref = findPreference(KEY_ATTENTIVE_TIME);
89         mAttentiveTimePref.setOnPreferenceChangeListener(this);
90 
91         mRestrictedSleepTime = RestrictedPreferenceAdapter.adapt(
92                 mSleepTimePref, UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT);
93         mRestrictedAttentiveTime = RestrictedPreferenceAdapter.adapt(
94                 mAttentiveTimePref, UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT);
95 
96         if (!showAttentiveSleepTimeoutSetting()) {
97             mAttentiveTimePref.setVisible(false);
98             mRestrictedAttentiveTime.updatePreference();
99         } else {
100             int validatedAttentiveSleepTime = getValidatedTimeout(getAttentiveSleepTime(), false);
101             mAttentiveTimePref.setValue(String.valueOf(validatedAttentiveSleepTime));
102             if (getAttentiveSleepTime() != validatedAttentiveSleepTime) {
103                 setAttentiveSleepTime(validatedAttentiveSleepTime);
104             }
105         }
106     }
107 
showAttentiveSleepTimeoutSetting()108     private boolean showAttentiveSleepTimeoutSetting() {
109         return getResources().getBoolean(R.bool.config_show_standby_timeout);
110     }
111 
112     @Override
onPreferenceChange(Preference preference, Object newValue)113     public boolean onPreferenceChange(Preference preference, Object newValue) {
114         switch (preference.getKey()) {
115             case KEY_SLEEP_TIME:
116                 final int newSleepTime = Integer.parseInt((String) newValue);
117                 if (getSleepTimeEntryId(newSleepTime) != -1) {
118                     logEntrySelected(getSleepTimeEntryId(newSleepTime));
119                 }
120                 if (showAttentiveSleepTimeoutSetting() && isTimeLargerThan(
121                         newSleepTime, getAttentiveSleepTime())) {
122                     new AlertDialog.Builder(getContext())
123                             .setMessage(R.string.device_energy_saver_validation_sleep)
124                             .setPositiveButton(
125                                     R.string.settings_ok, (dialog, which) -> dialog.dismiss())
126                             .create()
127                             .show();
128                     return false;
129                 }  else if (newSleepTime > WARNING_THRESHOLD_SLEEP_TIME_MS || newSleepTime == -1) {
130                     // Some regions require a warning to be presented.
131                     showConfirmChangeSettingDialog(false, newSleepTime);
132                     return false;
133                 } else {
134                     confirmNewSleepTime(newSleepTime);
135                     return true;
136                 }
137             case KEY_ATTENTIVE_TIME:
138                 final int attentiveTime = Integer.parseInt((String) newValue);
139                 if (isTimeLargerThan(getSleepTime(), attentiveTime)) {
140                     new AlertDialog.Builder(getContext())
141                             .setMessage(R.string.device_energy_saver_validation_attentive)
142                             .setPositiveButton(
143                                     R.string.settings_ok, (dialog, which) -> dialog.dismiss())
144                             .create()
145                             .show();
146                     return false;
147                 } else if (attentiveTime > WARNING_THRESHOLD_ATTENTIVE_TIME_MS
148                         || attentiveTime == -1) {
149                     showConfirmChangeSettingDialog(true, attentiveTime);
150                     return false;
151                 }
152                 confirmAttentiveSleepTime(attentiveTime);
153                 return true;
154             default:
155                 return false;
156         }
157     }
158 
isTimeLargerThan(int x, int y)159     private boolean isTimeLargerThan(int x, int y) {
160         if (x == -1 && y == -1) {
161             return false;
162         }
163         if (x == -1) {
164             return true;
165         }
166         if (y == -1) {
167             return false;
168         }
169         return x > y;
170     }
171 
showConfirmChangeSettingDialog(boolean isAttentiveTimer, int newTime)172     private void showConfirmChangeSettingDialog(boolean isAttentiveTimer, int newTime) {
173         new AlertDialog.Builder(getContext())
174                 .setTitle(R.string.device_energy_saver_confirmation_title)
175                 .setMessage(R.string.device_energy_saver_confirmation_message)
176                 .setPositiveButton(R.string.settings_confirm,
177                         (dialog, which) -> {
178                             if (isAttentiveTimer) {
179                                 confirmAttentiveSleepTime(newTime);
180                             } else {
181                                 confirmNewSleepTime(newTime);
182                             }
183                         })
184                 .setNegativeButton(R.string.settings_cancel,
185                         (dialog, which) -> dialog.dismiss())
186                 .create()
187                 .show();
188     }
189 
confirmAttentiveSleepTime(int attentiveTime)190     private void confirmAttentiveSleepTime(int attentiveTime) {
191         if (mAttentiveTimePref != null) {
192             mAttentiveTimePref.setValue(String.valueOf(attentiveTime));
193             setAttentiveSleepTime(attentiveTime);
194             mRestrictedAttentiveTime.updatePreference();
195             if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
196                 ((TwoPanelSettingsFragment) getCallbackFragment()).refocusPreference(this);
197             }
198         }
199     }
200 
getSleepTime()201     private int getSleepTime() {
202         return Settings.Secure.getInt(getActivity().getContentResolver(), SLEEP_TIMEOUT,
203                 DEFAULT_SLEEP_TIME_MS);
204     }
205 
setSleepTime(int ms)206     private void setSleepTime(int ms) {
207         Settings.Secure.putInt(getActivity().getContentResolver(), SLEEP_TIMEOUT, ms);
208     }
209 
getAttentiveSleepTime()210     private int getAttentiveSleepTime() {
211         return getAttentiveSleepTime(mDefaultAttentiveTimeoutConfig);
212     }
213 
getAttentiveSleepTime(int def)214     private int getAttentiveSleepTime(int def) {
215         return Settings.Secure.getInt(getActivity().getContentResolver(), ATTENTIVE_TIMEOUT, def);
216     }
217 
setAttentiveSleepTime(int ms)218     private void setAttentiveSleepTime(int ms) {
219         Settings.Secure.putInt(getActivity().getContentResolver(), ATTENTIVE_TIMEOUT, ms);
220     }
221 
222     // The SLEEP_TIMEOUT and ATTENTIVE_TIMEOUT could be defined in overlay by OEMs. We validate the
223     // value to make sure that we select from the predefined options. If the value from overlay is
224     // not one of the predefined options, we round it to the closest predefined value, except -1.
getValidatedTimeout(int purposedTimeout, boolean isSleepTimeout)225     private int getValidatedTimeout(int purposedTimeout, boolean isSleepTimeout) {
226         int validatedTimeout =
227                 isSleepTimeout ? DEFAULT_SLEEP_TIME_MS : mDefaultAttentiveTimeoutConfig;
228         if (purposedTimeout < 0) {
229             return -1;
230 
231         }
232         String[] optionsString = isSleepTimeout
233                 ? getResources().getStringArray(R.array.device_energy_saver_sleep_timeout_values)
234                 : getResources().getStringArray(
235                         R.array.device_energy_saver_attentive_timeout_values);
236         // Find the value from the predefined values that is closest to the proposed value except -1
237         int diff = Integer.MAX_VALUE;
238         for (String option : optionsString) {
239             if (Integer.parseInt(option) != -1) {
240                 int currentDiff = Math.abs(purposedTimeout - Integer.parseInt(option));
241                 if (currentDiff < diff) {
242                     diff = currentDiff;
243                     validatedTimeout = Integer.parseInt(option);
244                 }
245             }
246         }
247         return validatedTimeout;
248     }
249 
confirmNewSleepTime(int newSleepTime)250     private void confirmNewSleepTime(int newSleepTime) {
251         if (mSleepTimePref != null) {
252             setSleepTime(newSleepTime);
253             mSleepTimePref.setValue(String.valueOf(newSleepTime));
254             mRestrictedSleepTime.updatePreference();
255             if (getCallbackFragment() instanceof TwoPanelSettingsFragment) {
256                 ((TwoPanelSettingsFragment) getCallbackFragment()).refocusPreference(this);
257             }
258         }
259     }
260 
261     // TODO(b/158783050): update logging for new options 4H, 8H, 24H.
262     // Map @array/screen_off_timeout_entries to defined log enum
getSleepTimeEntryId(int sleepTimeValue)263     private int getSleepTimeEntryId(int sleepTimeValue) {
264         switch (sleepTimeValue) {
265             case -1:
266                 return TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY_NEVER;
267             case 900000:
268                 return TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY_15M;
269             case 1800000:
270                 return TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY_30M;
271             case 3600000:
272                 return TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY_1H;
273             case 43200000:
274                 return TvSettingsEnums.SYSTEM_ENERGYSAVER_START_DELAY_12H;
275             default:
276                 return -1;
277         }
278     }
279 
280     @Override
getPageId()281     protected int getPageId() {
282         return TvSettingsEnums.SYSTEM_ENERGYSAVER;
283     }
284 
285     /**
286      * Fix for b/286356445:
287      * The attentive timeout was previously set incorrectly when this Fragment was created.
288      * This method resets the attentive timeout setting to its default value if the setting
289      * is not supposed to be shown and this hasn't been run before.
290      */
resetAttentiveTimeoutIfHidden(Context context)291     public static void resetAttentiveTimeoutIfHidden(Context context) {
292         //
293         boolean showAttentiveSleepTimeoutSetting = context.getResources().getBoolean(
294                 R.bool.config_show_standby_timeout);
295         if (showAttentiveSleepTimeoutSetting) {
296             // Keep current setting, as user can change it and may have changed it
297             return;
298         }
299 
300         try {
301             final SharedPreferences sharedPreferences = context.getSharedPreferences(
302                     SHARED_PREFS_NAME, Context.MODE_PRIVATE);
303             boolean hasResetAttentiveTimeout = sharedPreferences.getBoolean(
304                     PREF_RESET_ATTENTIVE_TIMEOUT, false);
305             if (!hasResetAttentiveTimeout) {
306                 Settings.Secure.putString(context.getContentResolver(), ATTENTIVE_TIMEOUT, "");
307                 sharedPreferences.edit()
308                         .putBoolean(PREF_RESET_ATTENTIVE_TIMEOUT, true)
309                         .apply();
310             }
311         } catch (Exception e) {
312             Log.w(TAG, "Failed to reset attentive timeout", e);
313         }
314     }
315 }
316