• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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