1 /* 2 * Copyright (C) 2023 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.eco; 18 19 import android.annotation.ArrayRes; 20 import android.annotation.BoolRes; 21 import android.annotation.ColorRes; 22 import android.annotation.DrawableRes; 23 import android.annotation.IntegerRes; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.StringRes; 27 import android.content.Context; 28 import android.content.res.Resources; 29 import android.os.PowerManager; 30 import android.os.PowerManager.LowPowerStandbyPolicy; 31 import android.provider.DeviceConfig; 32 import android.text.TextUtils; 33 import android.util.ArraySet; 34 35 import com.android.tv.settings.R; 36 import com.android.tv.settings.overlay.FlavorUtils; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 import java.util.Set; 42 43 /** 44 * Provides available energy modes, and allows to update the current energy mode. 45 */ 46 public final class EnergyModesHelper { 47 public static final String NAMESPACE_LOW_POWER_STANDBY = "low_power_standby"; 48 public static final String KEY_ENABLE_POLICY = "enable_policy"; 49 private static final String LIST_ITEM_BULLET = "\u2022 "; 50 51 private final Context mContext; 52 53 /** Describes an Energy Mode. */ 54 public static final class EnergyMode { 55 @StringRes 56 public final int identifierRes; 57 public final boolean ecoHighlighted; 58 public final boolean enableLowPowerStandby; 59 @BoolRes 60 public final int enabledRes; 61 @StringRes 62 public final int titleRes; 63 @StringRes 64 public final int subtitleRes; 65 @ColorRes 66 public final int colorRes; 67 @DrawableRes 68 public final int iconRes; 69 @StringRes 70 public final int infoTextRes; 71 @ArrayRes 72 public final int featuresRes; 73 @StringRes 74 public final int ecoHintRes; 75 @DrawableRes 76 public final int ecoHintIconRes; 77 78 @ArrayRes 79 public final int baseExemptPackagesRes; 80 @ArrayRes 81 public final int vendorExemptPackagesRes; 82 @IntegerRes 83 public final int baseAllowedReasonsRes; 84 @IntegerRes 85 public final int vendorAllowedReasonsRes; 86 @ArrayRes 87 public final int baseAllowedFeaturesRes; 88 @ArrayRes 89 public final int vendorAllowedFeaturesRes; 90 91 /** 92 * Base mode from which all allowed reasons, allowed features, and exempt packages 93 * will be inherited. 94 */ 95 @Nullable 96 public final EnergyMode baseMode; 97 EnergyMode(@tringRes int identifierRes, boolean ecoHighlighted, boolean enableLowPowerStandby, @BoolRes int enabledRes, @StringRes int titleRes, @StringRes int subtitleRes, int colorRes, @DrawableRes int iconRes, @StringRes int infoTextRes, @ArrayRes int featuresRes, @StringRes int ecoHintRes, @DrawableRes int ecoHintIconRes, @ArrayRes int baseExemptPackagesRes, @ArrayRes int vendorExemptPackagesRes, @IntegerRes int baseAllowedReasonsRes, @IntegerRes int vendorAllowedReasonsRes, @ArrayRes int baseAllowedFeaturesRes, @ArrayRes int vendorAllowedFeaturesRes, @Nullable EnergyMode baseMode)98 public EnergyMode(@StringRes int identifierRes, boolean ecoHighlighted, 99 boolean enableLowPowerStandby, @BoolRes int enabledRes, @StringRes int titleRes, 100 @StringRes int subtitleRes, int colorRes, @DrawableRes int iconRes, 101 @StringRes int infoTextRes, @ArrayRes int featuresRes, @StringRes int ecoHintRes, 102 @DrawableRes int ecoHintIconRes, @ArrayRes int baseExemptPackagesRes, 103 @ArrayRes int vendorExemptPackagesRes, @IntegerRes int baseAllowedReasonsRes, 104 @IntegerRes int vendorAllowedReasonsRes, @ArrayRes int baseAllowedFeaturesRes, 105 @ArrayRes int vendorAllowedFeaturesRes, @Nullable EnergyMode baseMode) { 106 this.ecoHighlighted = ecoHighlighted; 107 this.enableLowPowerStandby = enableLowPowerStandby; 108 this.enabledRes = enabledRes; 109 this.titleRes = titleRes; 110 this.subtitleRes = subtitleRes; 111 this.colorRes = colorRes; 112 this.iconRes = iconRes; 113 this.infoTextRes = infoTextRes; 114 this.featuresRes = featuresRes; 115 this.ecoHintRes = ecoHintRes; 116 this.ecoHintIconRes = ecoHintIconRes; 117 this.identifierRes = identifierRes; 118 this.baseExemptPackagesRes = baseExemptPackagesRes; 119 this.vendorExemptPackagesRes = vendorExemptPackagesRes; 120 this.baseAllowedReasonsRes = baseAllowedReasonsRes; 121 this.vendorAllowedReasonsRes = vendorAllowedReasonsRes; 122 this.baseAllowedFeaturesRes = baseAllowedFeaturesRes; 123 this.vendorAllowedFeaturesRes = vendorAllowedFeaturesRes; 124 this.baseMode = baseMode; 125 } 126 } 127 128 public static EnergyMode MODE_LOW_ENERGY = new EnergyMode( 129 R.string.energy_mode_low_identifier, 130 /* ecoHighlighted= */ true, 131 /* enableLowPowerStandby= */ true, 132 R.bool.energy_mode_low_enabled, 133 R.string.energy_mode_low_title, 134 R.string.energy_mode_low_subtitle, 135 R.color.energy_mode_low_color, 136 R.drawable.energy_mode_low_icon, 137 R.string.energy_mode_low_info, 138 R.array.energy_mode_low_features, 139 R.string.energy_mode_low_eco_hint, 140 R.drawable.ic_tips_and_updates, 141 R.array.energy_mode_low_baseExemptPackages, 142 R.array.energy_mode_low_vendorExemptPackages, 143 R.integer.energy_mode_low_baseAllowedReasons, 144 R.integer.energy_mode_low_vendorAllowedReasons, 145 R.array.energy_mode_low_baseAllowedFeatures, 146 R.array.energy_mode_low_vendorAllowedFeatures, 147 /* baseMode= */ null); 148 149 public static EnergyMode MODE_MODERATE_ENERGY = new EnergyMode( 150 R.string.energy_mode_moderate_identifier, 151 /* ecoHighlighted= */ false, 152 /* enableLowPowerStandby= */ true, 153 R.bool.energy_mode_moderate_enabled, 154 R.string.energy_mode_moderate_title, 155 R.string.energy_mode_moderate_subtitle, 156 R.color.energy_mode_moderate_color, 157 R.drawable.energy_mode_moderate_icon, 158 R.string.energy_mode_moderate_info, 159 R.array.energy_mode_moderate_features, 160 R.string.energy_mode_moderate_eco_hint, 161 /* ecoHintIconRes= */ 0, 162 R.array.energy_mode_moderate_baseExemptPackages, 163 R.array.energy_mode_moderate_vendorExemptPackages, 164 R.integer.energy_mode_moderate_baseAllowedReasons, 165 R.integer.energy_mode_moderate_vendorAllowedReasons, 166 R.array.energy_mode_moderate_baseAllowedFeatures, 167 R.array.energy_mode_moderate_vendorAllowedFeatures, 168 MODE_LOW_ENERGY); 169 170 public static EnergyMode MODE_HIGH_ENERGY = new EnergyMode( 171 R.string.energy_mode_high_identifier, 172 /* ecoHighlighted= */ false, 173 /* enableLowPowerStandby= */ true, 174 R.bool.energy_mode_high_enabled, 175 R.string.energy_mode_high_title, 176 R.string.energy_mode_high_subtitle, 177 R.color.energy_mode_high_color, 178 R.drawable.energy_mode_high_icon, 179 R.string.energy_mode_high_info, 180 R.array.energy_mode_high_features, 181 R.string.energy_mode_high_eco_hint, 182 R.drawable.ic_bolt, 183 R.array.energy_mode_high_baseExemptPackages, 184 R.array.energy_mode_high_vendorExemptPackages, 185 R.integer.energy_mode_high_baseAllowedReasons, 186 R.integer.energy_mode_high_vendorAllowedReasons, 187 R.array.energy_mode_high_baseAllowedFeatures, 188 R.array.energy_mode_high_vendorAllowedFeatures, 189 MODE_MODERATE_ENERGY); 190 191 public static EnergyMode MODE_UNRESTRICTED = new EnergyMode( 192 R.string.energy_mode_unrestricted_identifier, 193 false, 194 false, 195 R.bool.energy_mode_unrestricted_enabled, 196 R.string.energy_mode_high_title, 197 R.string.energy_mode_high_subtitle, 198 R.color.energy_mode_high_color, 199 R.drawable.energy_mode_high_icon, 200 R.string.energy_mode_high_info, 201 R.array.energy_mode_high_features, 202 R.string.energy_mode_high_eco_hint, 203 R.drawable.ic_bolt, 204 0, 0, 0, 0, 0, 0, null); 205 206 public static EnergyMode[] ENERGY_MODES = new EnergyMode[] { 207 MODE_LOW_ENERGY, MODE_MODERATE_ENERGY, MODE_HIGH_ENERGY, MODE_UNRESTRICTED }; 208 EnergyModesHelper(Context context)209 public EnergyModesHelper(Context context) { 210 mContext = context; 211 } 212 213 /** 214 * Returns whether this device supports Low Power Standby. 215 * 216 * If false, energy modes are not supported. 217 */ isLowPowerStandbySupported(Context context)218 public static boolean isLowPowerStandbySupported(Context context) { 219 if (FlavorUtils.getFeatureFactory(context).getBasicModeFeatureProvider() 220 .isBasicMode(context)) { 221 return false; // Basic mode does no background processing during standby. 222 } 223 final PowerManager powerManager = context.getSystemService(PowerManager.class); 224 return powerManager.isLowPowerStandbySupported(); 225 } 226 areEnergyModesEnabled()227 private boolean areEnergyModesEnabled() { 228 boolean enableEnergyModes = mContext.getResources().getBoolean(R.bool.enable_energy_modes); 229 boolean customPoliciesEnabled = DeviceConfig.getBoolean(NAMESPACE_LOW_POWER_STANDBY, 230 KEY_ENABLE_POLICY, true); 231 232 return enableEnergyModes && customPoliciesEnabled && isLowPowerStandbySupported(mContext); 233 } 234 235 /** Returns whether Energy Modes should be shown and used on this device */ areEnergyModesAvailable()236 public boolean areEnergyModesAvailable() { 237 return !getEnergyModes().isEmpty(); 238 } 239 240 /** Returns all enabled energy modes in the order they should be presented. */ 241 @NonNull getEnergyModes()242 public List<EnergyMode> getEnergyModes() { 243 ArrayList<EnergyMode> enabledModes = new ArrayList<>(); 244 if (!areEnergyModesEnabled()) { 245 return enabledModes; 246 } 247 248 if (isEnergyModeEnabled(MODE_LOW_ENERGY)) { 249 enabledModes.add(MODE_LOW_ENERGY); 250 } 251 252 if (isEnergyModeEnabled(MODE_MODERATE_ENERGY)) { 253 enabledModes.add(MODE_MODERATE_ENERGY); 254 } 255 256 if (isEnergyModeEnabled(MODE_UNRESTRICTED)) { 257 enabledModes.add(MODE_UNRESTRICTED); 258 } else if (isEnergyModeEnabled(MODE_HIGH_ENERGY)) { 259 enabledModes.add(MODE_HIGH_ENERGY); 260 } 261 262 return enabledModes; 263 } 264 isEnergyModeEnabled(EnergyMode mode)265 private boolean isEnergyModeEnabled(EnergyMode mode) { 266 if (mode == null) { 267 return false; 268 } 269 270 if (mode == MODE_HIGH_ENERGY && isEnergyModeEnabled(MODE_UNRESTRICTED)) { 271 // unrestricted mode overrides high energy mode 272 return false; 273 } 274 275 Resources resources = mContext.getResources(); 276 boolean baseEnabled = resources.getBoolean(mode.enabledRes); 277 String identifier = mContext.getString(mode.identifierRes); 278 return DeviceConfig.getBoolean(NAMESPACE_LOW_POWER_STANDBY, 279 "policy_" + identifier + "_enabled", baseEnabled); 280 } 281 282 /** Returns an energy mode by its identifier, or null if not found. */ 283 @Nullable getEnergyMode(@tringRes int identifierRes)284 public EnergyMode getEnergyMode(@StringRes int identifierRes) { 285 for (EnergyMode energyMode : ENERGY_MODES) { 286 if (energyMode.identifierRes == identifierRes) { 287 return energyMode; 288 } 289 } 290 291 return null; 292 } 293 294 /** Returns an energy mode by its identifier, or null if not found. */ 295 @Nullable getEnergyMode(String identifier)296 public EnergyMode getEnergyMode(String identifier) { 297 for (EnergyMode energyMode : ENERGY_MODES) { 298 if (mContext.getString(energyMode.identifierRes).equals(identifier)) { 299 return energyMode; 300 } 301 } 302 303 return null; 304 } 305 306 /** Returns the description of the energy mode, incl. list of features */ getSummary(EnergyMode mode)307 public String getSummary(EnergyMode mode) { 308 StringBuilder summary = new StringBuilder(); 309 summary.append(mContext.getString(mode.infoTextRes)); 310 311 String featuresList = getFeaturesList(mode); 312 if (featuresList != null) { 313 summary.append("\n\n"); 314 summary.append(mContext.getString(R.string.energy_mode_enables)); 315 summary.append("\n"); 316 summary.append(featuresList); 317 } 318 319 return summary.toString(); 320 } 321 322 /** Returns the list of features formatted for display in the Settings UI */ 323 @Nullable getFeaturesList(EnergyMode mode)324 public String getFeaturesList(EnergyMode mode) { 325 String[] features = mContext.getResources().getStringArray(mode.featuresRes); 326 if (features.length == 0) { 327 return null; 328 } 329 330 StringBuilder featureList = new StringBuilder(); 331 332 for (int i = 0; i < features.length; i++) { 333 featureList.append(LIST_ITEM_BULLET); 334 featureList.append(features[i]); 335 if (i < features.length - 1) { 336 featureList.append("\n"); 337 } 338 } 339 340 return featureList.toString(); 341 } 342 343 @Nullable getDeviceConfigStringArray(String key)344 private String[] getDeviceConfigStringArray(String key) { 345 String string = DeviceConfig.getString(NAMESPACE_LOW_POWER_STANDBY, key, null); 346 if (string == null) { 347 return null; 348 } 349 return string.split(","); 350 } 351 getPolicy(EnergyMode mode)352 LowPowerStandbyPolicy getPolicy(EnergyMode mode) { 353 if (!mode.enableLowPowerStandby) { 354 return new LowPowerStandbyPolicy( 355 mContext.getString(mode.identifierRes), 356 Collections.emptySet(), 357 0, 358 Collections.emptySet()); 359 } 360 361 return new LowPowerStandbyPolicy( 362 mContext.getString(mode.identifierRes), 363 getExemptPackages(mode), 364 getAllowedReasons(mode), 365 getAllowedFeatures(mode)); 366 } 367 368 @NonNull getExemptPackages(@onNull EnergyMode mode)369 private Set<String> getExemptPackages(@NonNull EnergyMode mode) { 370 final String identifier = mContext.getString(mode.identifierRes); 371 final Set<String> exemptPackages = combineStringArrays(mode.baseExemptPackagesRes, 372 "policy_" + identifier + "_exempt_packages", mode.vendorExemptPackagesRes); 373 374 if (mode.baseMode != null) { 375 exemptPackages.addAll(getExemptPackages(mode.baseMode)); 376 } 377 378 return exemptPackages; 379 } 380 381 @NonNull getAllowedFeatures(@onNull EnergyMode mode)382 Set<String> getAllowedFeatures(@NonNull EnergyMode mode) { 383 final String identifier = mContext.getString(mode.identifierRes); 384 final Set<String> allowedFeatures = combineStringArrays(mode.baseAllowedFeaturesRes, 385 "policy_" + identifier + "_allowed_features", mode.vendorAllowedFeaturesRes); 386 387 if (mode.baseMode != null) { 388 allowedFeatures.addAll(getAllowedFeatures(mode.baseMode)); 389 } 390 391 return allowedFeatures; 392 } 393 combineStringArrays(@rrayRes int baseArrayRes, String baseOverrideKey, @ArrayRes int vendorArrayRes)394 private Set<String> combineStringArrays(@ArrayRes int baseArrayRes, String baseOverrideKey, 395 @ArrayRes int vendorArrayRes) { 396 final Resources resources = mContext.getResources(); 397 final String[] baseArray = resources.getStringArray(baseArrayRes); 398 final String[] baseOverrideArray = getDeviceConfigStringArray(baseOverrideKey); 399 final String[] vendorArray = resources.getStringArray(vendorArrayRes); 400 401 ArraySet<String> result = new ArraySet<>(); 402 result.addAll(new ArraySet<>(baseOverrideArray != null 403 ? baseOverrideArray 404 : baseArray)); 405 result.addAll(new ArraySet<>(vendorArray)); 406 return result; 407 } 408 getAllowedReasons(@onNull EnergyMode mode)409 private int getAllowedReasons(@NonNull EnergyMode mode) { 410 final Resources resources = mContext.getResources(); 411 final String identifier = mContext.getString(mode.identifierRes); 412 413 final int baseAllowedReasons = resources.getInteger(mode.baseAllowedReasonsRes); 414 final int deviceConfigAllowedReasonOverride = DeviceConfig.getInt( 415 NAMESPACE_LOW_POWER_STANDBY, "policy_" + identifier + "_allowed_reasons", -1); 416 final int vendorAllowedReasons = resources.getInteger(mode.vendorAllowedReasonsRes); 417 int allowedReasons = ((deviceConfigAllowedReasonOverride != -1 418 ? deviceConfigAllowedReasonOverride 419 : baseAllowedReasons) | vendorAllowedReasons); 420 421 if (mode.baseMode != null) { 422 allowedReasons |= getAllowedReasons(mode.baseMode); 423 } 424 425 return allowedReasons; 426 } 427 428 /** Sets the given energy mode in the system. */ setEnergyMode(@onNull EnergyMode energyMode)429 public void setEnergyMode(@NonNull EnergyMode energyMode) { 430 LowPowerStandbyPolicy policy = getPolicy(energyMode); 431 PowerManager powerManager = mContext.getSystemService(PowerManager.class); 432 powerManager.setLowPowerStandbyEnabled(energyMode.enableLowPowerStandby); 433 powerManager.setLowPowerStandbyPolicy(policy); 434 } 435 436 /** 437 * Returns the default energy mode. 438 * 439 * This energy mode is used if the current Low Power Standby policy doesn't match any valid 440 * and enabled energy modes. 441 */ 442 @Nullable getDefaultEnergyMode()443 public EnergyMode getDefaultEnergyMode() { 444 if (!areEnergyModesAvailable()) { 445 return null; 446 } 447 return getEnergyMode(mContext.getString(R.string.default_energy_mode)); 448 } 449 450 /** 451 * Returns true if going from the current energy mode to the given energy mode requires 452 * the user to confirm the change. 453 */ requiresConfirmation(EnergyMode currentMode, EnergyMode newMode)454 public boolean requiresConfirmation(EnergyMode currentMode, EnergyMode newMode) { 455 int currentModeIndex = getEnergyModeIndex(currentMode); 456 int newModeIndex = getEnergyModeIndex(newMode); 457 458 if (currentModeIndex == -1) { 459 return newModeIndex > 0; 460 } 461 462 return newModeIndex > currentModeIndex; 463 } 464 getEnergyModeIndex(EnergyMode mode)465 private int getEnergyModeIndex(EnergyMode mode) { 466 if (mode == null) { 467 return -1; 468 } 469 for (int i = 0; i < ENERGY_MODES.length; i++) { 470 if (mode == ENERGY_MODES[i]) { 471 return i; 472 } 473 } 474 return -1; 475 } 476 477 /** 478 * Makes sure a valid energy mode is set, if energy modes are enabled, and returns the current 479 * energy mode. 480 */ 481 @Nullable updateEnergyMode()482 public EnergyMode updateEnergyMode() { 483 if (!areEnergyModesAvailable()) { 484 return null; 485 } 486 487 PowerManager powerManager = mContext.getSystemService(PowerManager.class); 488 final LowPowerStandbyPolicy currentPolicy = powerManager.getLowPowerStandbyPolicy(); 489 if (currentPolicy == null) { 490 return null; 491 } 492 493 final EnergyMode matchingEnergyMode = getEnergyMode(currentPolicy.getIdentifier()); 494 EnergyMode targetEnergyMode = matchingEnergyMode; 495 if (!isEnergyModeEnabled(matchingEnergyMode)) { 496 if (matchingEnergyMode == MODE_HIGH_ENERGY && isEnergyModeEnabled(MODE_UNRESTRICTED)) { 497 targetEnergyMode = MODE_UNRESTRICTED; 498 } else if (matchingEnergyMode == MODE_UNRESTRICTED && isEnergyModeEnabled( 499 MODE_HIGH_ENERGY)) { 500 targetEnergyMode = MODE_HIGH_ENERGY; 501 } else { 502 targetEnergyMode = getDefaultEnergyMode(); 503 if (targetEnergyMode == null) { 504 // Fall back to lowest energy mode if default is not set or invalid 505 targetEnergyMode = getEnergyModes().get(0); 506 } 507 } 508 } 509 510 setEnergyMode(targetEnergyMode); 511 return targetEnergyMode; 512 } 513 } 514