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 17 package com.android.settings.display; 18 19 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA; 20 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT; 21 22 import android.app.admin.DevicePolicyManager; 23 import android.app.settings.SettingsEnums; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.res.Resources; 29 import android.graphics.drawable.Drawable; 30 import android.hardware.SensorPrivacyManager; 31 import android.os.PowerManager; 32 import android.os.UserHandle; 33 import android.provider.Settings; 34 import android.text.SpannableString; 35 import android.text.Spanned; 36 import android.text.style.ClickableSpan; 37 import android.util.Log; 38 import android.view.View; 39 40 import androidx.annotation.NonNull; 41 import androidx.preference.Preference; 42 import androidx.preference.PreferenceScreen; 43 44 import com.android.settings.R; 45 import com.android.settings.overlay.FeatureFactory; 46 import com.android.settings.search.BaseSearchIndexProvider; 47 import com.android.settings.support.actionbar.HelpResourceProvider; 48 import com.android.settings.widget.RadioButtonPickerFragment; 49 import com.android.settingslib.RestrictedLockUtils; 50 import com.android.settingslib.RestrictedLockUtilsInternal; 51 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 52 import com.android.settingslib.search.SearchIndexable; 53 import com.android.settingslib.search.SearchIndexableRaw; 54 import com.android.settingslib.widget.CandidateInfo; 55 import com.android.settingslib.widget.FooterPreference; 56 import com.android.settingslib.widget.RadioButtonPreference; 57 58 import com.google.common.annotations.VisibleForTesting; 59 60 import java.util.ArrayList; 61 import java.util.List; 62 63 /** 64 * Fragment that is used to control screen timeout. 65 */ 66 @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) 67 public class ScreenTimeoutSettings extends RadioButtonPickerFragment implements 68 HelpResourceProvider { 69 private static final String TAG = "ScreenTimeout"; 70 /** If there is no setting in the provider, use this. */ 71 public static final int FALLBACK_SCREEN_TIMEOUT_VALUE = 30000; 72 73 private static final int DEFAULT_ORDER_OF_LOWEST_PREFERENCE = Integer.MAX_VALUE - 1; 74 75 private CharSequence[] mInitialEntries; 76 private CharSequence[] mInitialValues; 77 private FooterPreference mPrivacyPreference; 78 private final MetricsFeatureProvider mMetricsFeatureProvider; 79 private SensorPrivacyManager mPrivacyManager; 80 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 81 @Override 82 public void onReceive(Context context, Intent intent) { 83 mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); 84 mAdaptiveSleepController.updatePreference(); 85 } 86 }; 87 88 @VisibleForTesting 89 Context mContext; 90 91 @VisibleForTesting 92 RestrictedLockUtils.EnforcedAdmin mAdmin; 93 94 @VisibleForTesting 95 Preference mDisableOptionsPreference; 96 97 @VisibleForTesting 98 AdaptiveSleepPermissionPreferenceController mAdaptiveSleepPermissionController; 99 100 @VisibleForTesting 101 AdaptiveSleepCameraStatePreferenceController mAdaptiveSleepCameraStatePreferenceController; 102 103 @VisibleForTesting 104 AdaptiveSleepPreferenceController mAdaptiveSleepController; 105 106 @VisibleForTesting 107 AdaptiveSleepBatterySaverPreferenceController mAdaptiveSleepBatterySaverPreferenceController; 108 ScreenTimeoutSettings()109 public ScreenTimeoutSettings() { 110 super(); 111 mMetricsFeatureProvider = FeatureFactory.getFactory(getContext()) 112 .getMetricsFeatureProvider(); 113 } 114 115 @Override onAttach(Context context)116 public void onAttach(Context context) { 117 super.onAttach(context); 118 mContext = context; 119 mInitialEntries = getResources().getStringArray(R.array.screen_timeout_entries); 120 mInitialValues = getResources().getStringArray(R.array.screen_timeout_values); 121 mAdaptiveSleepController = new AdaptiveSleepPreferenceController(context); 122 mAdaptiveSleepPermissionController = new AdaptiveSleepPermissionPreferenceController( 123 context); 124 mAdaptiveSleepCameraStatePreferenceController = 125 new AdaptiveSleepCameraStatePreferenceController(context); 126 mAdaptiveSleepBatterySaverPreferenceController = 127 new AdaptiveSleepBatterySaverPreferenceController(context); 128 mPrivacyPreference = new FooterPreference(context); 129 mPrivacyPreference.setIcon(R.drawable.ic_privacy_shield_24dp); 130 mPrivacyPreference.setTitle(R.string.adaptive_sleep_privacy); 131 mPrivacyPreference.setSelectable(false); 132 mPrivacyPreference.setLayoutResource(R.layout.preference_footer); 133 mPrivacyManager = SensorPrivacyManager.getInstance(context); 134 mPrivacyManager.addSensorPrivacyListener(CAMERA, 135 (sensor, enabled) -> mAdaptiveSleepController.updatePreference()); 136 } 137 138 @Override getCandidates()139 protected List<? extends CandidateInfo> getCandidates() { 140 final List<CandidateInfo> candidates = new ArrayList<>(); 141 final long maxTimeout = getMaxScreenTimeout(getContext()); 142 if (mInitialValues != null) { 143 for (int i = 0; i < mInitialValues.length; ++i) { 144 if (Long.parseLong(mInitialValues[i].toString()) <= maxTimeout) { 145 candidates.add(new TimeoutCandidateInfo(mInitialEntries[i], 146 mInitialValues[i].toString(), true)); 147 } 148 } 149 } else { 150 Log.e(TAG, "Screen timeout options do not exist."); 151 } 152 return candidates; 153 } 154 155 @Override onStart()156 public void onStart() { 157 super.onStart(); 158 mAdaptiveSleepPermissionController.updateVisibility(); 159 mAdaptiveSleepCameraStatePreferenceController.updateVisibility(); 160 mAdaptiveSleepBatterySaverPreferenceController.updateVisibility(); 161 mAdaptiveSleepController.updatePreference(); 162 mContext.registerReceiver(mReceiver, 163 new IntentFilter(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)); 164 } 165 166 @Override onStop()167 public void onStop() { 168 super.onStop(); 169 mContext.unregisterReceiver(mReceiver); 170 } 171 172 @Override updateCandidates()173 public void updateCandidates() { 174 final String defaultKey = getDefaultKey(); 175 final PreferenceScreen screen = getPreferenceScreen(); 176 screen.removeAll(); 177 178 final List<? extends CandidateInfo> candidateList = getCandidates(); 179 if (candidateList == null) { 180 return; 181 } 182 183 for (CandidateInfo info : candidateList) { 184 RadioButtonPreference pref = 185 new RadioButtonPreference(getPrefContext()); 186 bindPreference(pref, info.getKey(), info, defaultKey); 187 screen.addPreference(pref); 188 } 189 190 final long selectedTimeout = Long.parseLong(defaultKey); 191 final long maxTimeout = getMaxScreenTimeout(getContext()); 192 if (!candidateList.isEmpty() && (selectedTimeout > maxTimeout)) { 193 // The selected time out value is longer than the max timeout allowed by the admin. 194 // Select the largest value from the list by default. 195 final RadioButtonPreference preferenceWithLargestTimeout = 196 (RadioButtonPreference) screen.getPreference(candidateList.size() - 1); 197 preferenceWithLargestTimeout.setChecked(true); 198 } 199 200 mPrivacyPreference = new FooterPreference(mContext); 201 mPrivacyPreference.setIcon(R.drawable.ic_privacy_shield_24dp); 202 mPrivacyPreference.setTitle(R.string.adaptive_sleep_privacy); 203 mPrivacyPreference.setSelectable(false); 204 mPrivacyPreference.setLayoutResource(R.layout.preference_footer); 205 206 if (isScreenAttentionAvailable(getContext())) { 207 mAdaptiveSleepPermissionController.addToScreen(screen); 208 mAdaptiveSleepCameraStatePreferenceController.addToScreen(screen); 209 mAdaptiveSleepBatterySaverPreferenceController.addToScreen(screen); 210 mAdaptiveSleepController.addToScreen(screen); 211 screen.addPreference(mPrivacyPreference); 212 } 213 214 if (mAdmin != null) { 215 setupDisabledFooterPreference(); 216 screen.addPreference(mDisableOptionsPreference); 217 } 218 } 219 220 @VisibleForTesting setupDisabledFooterPreference()221 void setupDisabledFooterPreference() { 222 final String textDisabledByAdmin = getResources().getString( 223 R.string.admin_disabled_other_options); 224 final String textMoreDetails = getResources().getString(R.string.admin_more_details); 225 226 final SpannableString spannableString = new SpannableString( 227 textDisabledByAdmin + System.lineSeparator() 228 + System.lineSeparator() + textMoreDetails); 229 final ClickableSpan clickableSpan = new ClickableSpan() { 230 @Override 231 public void onClick(@NonNull View widget) { 232 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mAdmin); 233 } 234 }; 235 236 if (textDisabledByAdmin != null && textMoreDetails != null) { 237 spannableString.setSpan(clickableSpan, textDisabledByAdmin.length() + 1, 238 textDisabledByAdmin.length() + textMoreDetails.length() + 2, 239 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 240 } 241 242 mDisableOptionsPreference = new FooterPreference(getContext()); 243 mDisableOptionsPreference.setLayoutResource(R.layout.preference_footer); 244 mDisableOptionsPreference.setTitle(spannableString); 245 mDisableOptionsPreference.setSelectable(false); 246 mDisableOptionsPreference.setIcon(R.drawable.ic_info_outline_24dp); 247 248 // The 'disabled by admin' preference should always be at the end of the setting page. 249 mDisableOptionsPreference.setOrder(DEFAULT_ORDER_OF_LOWEST_PREFERENCE); 250 mPrivacyPreference.setOrder(DEFAULT_ORDER_OF_LOWEST_PREFERENCE - 1); 251 } 252 253 @Override getDefaultKey()254 protected String getDefaultKey() { 255 return getCurrentSystemScreenTimeout(getContext()); 256 } 257 258 @Override setDefaultKey(String key)259 protected boolean setDefaultKey(String key) { 260 setCurrentSystemScreenTimeout(getContext(), key); 261 return true; 262 } 263 264 @Override getMetricsCategory()265 public int getMetricsCategory() { 266 return SettingsEnums.SCREEN_TIMEOUT; 267 } 268 269 @Override getPreferenceScreenResId()270 protected int getPreferenceScreenResId() { 271 return R.xml.screen_timeout_settings; 272 } 273 274 @Override getHelpResource()275 public int getHelpResource() { 276 return R.string.help_url_adaptive_sleep; 277 } 278 getMaxScreenTimeout(Context context)279 private Long getMaxScreenTimeout(Context context) { 280 if (context == null) { 281 return Long.MAX_VALUE; 282 } 283 final DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class); 284 if (dpm == null) { 285 return Long.MAX_VALUE; 286 } 287 mAdmin = RestrictedLockUtilsInternal.checkIfMaximumTimeToLockIsSet(context); 288 if (mAdmin != null) { 289 return dpm.getMaximumTimeToLock(null /* admin */, UserHandle.myUserId()); 290 } 291 return Long.MAX_VALUE; 292 } 293 getCurrentSystemScreenTimeout(Context context)294 private String getCurrentSystemScreenTimeout(Context context) { 295 if (context == null) { 296 return Long.toString(FALLBACK_SCREEN_TIMEOUT_VALUE); 297 } else { 298 return Long.toString(Settings.System.getLong(context.getContentResolver(), 299 SCREEN_OFF_TIMEOUT, FALLBACK_SCREEN_TIMEOUT_VALUE)); 300 } 301 } 302 setCurrentSystemScreenTimeout(Context context, String key)303 private void setCurrentSystemScreenTimeout(Context context, String key) { 304 try { 305 if (context != null) { 306 final long value = Long.parseLong(key); 307 mMetricsFeatureProvider.action(context, SettingsEnums.ACTION_SCREEN_TIMEOUT_CHANGED, 308 (int) value); 309 Settings.System.putLong(context.getContentResolver(), SCREEN_OFF_TIMEOUT, value); 310 } 311 } catch (NumberFormatException e) { 312 Log.e(TAG, "could not persist screen timeout setting", e); 313 } 314 } 315 isScreenAttentionAvailable(Context context)316 private static boolean isScreenAttentionAvailable(Context context) { 317 return AdaptiveSleepPreferenceController.isAdaptiveSleepSupported(context); 318 } 319 320 private static class TimeoutCandidateInfo extends CandidateInfo { 321 private final CharSequence mLabel; 322 private final String mKey; 323 TimeoutCandidateInfo(CharSequence label, String key, boolean enabled)324 TimeoutCandidateInfo(CharSequence label, String key, boolean enabled) { 325 super(enabled); 326 mLabel = label; 327 mKey = key; 328 } 329 330 @Override loadLabel()331 public CharSequence loadLabel() { 332 return mLabel; 333 } 334 335 @Override loadIcon()336 public Drawable loadIcon() { 337 return null; 338 } 339 340 @Override getKey()341 public String getKey() { 342 return mKey; 343 } 344 } 345 346 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 347 new BaseSearchIndexProvider(R.xml.screen_timeout_settings) { 348 public List<SearchIndexableRaw> getRawDataToIndex(Context context, 349 boolean enabled) { 350 if (!isScreenAttentionAvailable(context)) { 351 return null; 352 } 353 final Resources res = context.getResources(); 354 final SearchIndexableRaw data = new SearchIndexableRaw(context); 355 data.title = res.getString(R.string.adaptive_sleep_title); 356 data.key = AdaptiveSleepPreferenceController.PREFERENCE_KEY; 357 data.keywords = res.getString(R.string.adaptive_sleep_title); 358 359 final List<SearchIndexableRaw> result = new ArrayList<>(1); 360 result.add(data); 361 return result; 362 } 363 }; 364 } 365