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