• 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 
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