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