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