1 /*
2  * Copyright (C) 2018 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.settings.fuelgauge;
18 
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.os.BatteryManager;
22 import android.util.Log;
23 
24 import androidx.annotation.VisibleForTesting;
25 import androidx.preference.Preference;
26 import androidx.preference.PreferenceScreen;
27 
28 import com.android.settings.R;
29 import com.android.settings.core.BasePreferenceController;
30 import com.android.settings.overlay.FeatureFactory;
31 import com.android.settingslib.Utils;
32 import com.android.settingslib.core.lifecycle.LifecycleObserver;
33 import com.android.settingslib.core.lifecycle.events.OnStart;
34 import com.android.settingslib.core.lifecycle.events.OnStop;
35 import com.android.settingslib.utils.ThreadUtils;
36 
37 public class TopLevelBatteryPreferenceController extends BasePreferenceController
38         implements LifecycleObserver, OnStart, OnStop, BatteryPreferenceController {
39 
40     private static final String TAG = "TopLvBatteryPrefControl";
41 
42     @VisibleForTesting Preference mPreference;
43     @VisibleForTesting protected boolean mIsBatteryPresent = true;
44 
45     private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
46 
47     private BatteryInfo mBatteryInfo;
48     private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
49     private String mBatteryStatusLabel;
50 
TopLevelBatteryPreferenceController(Context context, String preferenceKey)51     public TopLevelBatteryPreferenceController(Context context, String preferenceKey) {
52         super(context, preferenceKey);
53         mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
54         mBatteryBroadcastReceiver.setBatteryChangedListener(
55                 type -> {
56                     Log.d(TAG, "onBatteryChanged: type=" + type);
57                     if (type == BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT) {
58                         mIsBatteryPresent = false;
59                     }
60                     BatteryInfo.getBatteryInfo(
61                             mContext,
62                             info -> {
63                                 Log.d(TAG, "getBatteryInfo: " + info);
64                                 mBatteryInfo = info;
65                                 updateState(mPreference);
66                                 // Update the preference summary text to the latest state.
67                                 setSummaryAsync(info);
68                             },
69                             true /* shortString */);
70                 });
71 
72         mBatteryStatusFeatureProvider =
73                 FeatureFactory.getFeatureFactory().getBatteryStatusFeatureProvider();
74     }
75 
76     @Override
getAvailabilityStatus()77     public int getAvailabilityStatus() {
78         return mContext.getResources().getBoolean(R.bool.config_show_top_level_battery)
79                 ? AVAILABLE
80                 : UNSUPPORTED_ON_DEVICE;
81     }
82 
83     @Override
displayPreference(PreferenceScreen screen)84     public void displayPreference(PreferenceScreen screen) {
85         super.displayPreference(screen);
86         mPreference = screen.findPreference(getPreferenceKey());
87     }
88 
89     @Override
onStart()90     public void onStart() {
91         mBatteryBroadcastReceiver.register();
92     }
93 
94     @Override
onStop()95     public void onStop() {
96         mBatteryBroadcastReceiver.unRegister();
97     }
98 
99     @Override
getSummary()100     public CharSequence getSummary() {
101         return getSummary(true /* batteryStatusUpdate */);
102     }
103 
getSummary(boolean batteryStatusUpdate)104     private CharSequence getSummary(boolean batteryStatusUpdate) {
105         // Display help message if battery is not present.
106         if (!mIsBatteryPresent) {
107             return mContext.getText(R.string.battery_missing_message);
108         }
109         return getDashboardLabel(mContext, mBatteryInfo, batteryStatusUpdate);
110     }
111 
getDashboardLabel( Context context, BatteryInfo info, boolean batteryStatusUpdate)112     protected CharSequence getDashboardLabel(
113             Context context, BatteryInfo info, boolean batteryStatusUpdate) {
114         if (info == null || context == null) {
115             return null;
116         }
117         Log.d(
118                 TAG,
119                 "getDashboardLabel: "
120                         + mBatteryStatusLabel
121                         + " batteryStatusUpdate="
122                         + batteryStatusUpdate);
123 
124         if (batteryStatusUpdate) {
125             setSummaryAsync(info);
126         }
127         return mBatteryStatusLabel == null ? generateLabel(info) : mBatteryStatusLabel;
128     }
129 
setSummaryAsync(BatteryInfo info)130     private void setSummaryAsync(BatteryInfo info) {
131         ThreadUtils.postOnBackgroundThread(
132                 () -> {
133                     // Return false if built-in status should be used, will use
134                     // updateBatteryStatus()
135                     // method to inject the customized battery status label.
136                     final boolean triggerBatteryStatusUpdate =
137                             mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info);
138                     ThreadUtils.postOnMainThread(
139                             () -> {
140                                 if (!triggerBatteryStatusUpdate) {
141                                     mBatteryStatusLabel = null; // will generateLabel()
142                                 }
143                                 mPreference.setSummary(
144                                         mBatteryStatusLabel == null
145                                                 ? generateLabel(info)
146                                                 : mBatteryStatusLabel);
147                             });
148                 });
149     }
150 
generateLabel(BatteryInfo info)151     private CharSequence generateLabel(BatteryInfo info) {
152         if (Utils.containsIncompatibleChargers(mContext, TAG)) {
153             return mContext.getString(
154                     com.android.settingslib.R.string.power_incompatible_charging_settings_home_page,
155                     info.batteryPercentString);
156         }
157         if (BatteryUtils.isBatteryDefenderOn(info)) {
158             return mContext.getString(
159                     com.android.settingslib.R.string.power_charging_on_hold_settings_home_page,
160                     info.batteryPercentString);
161         }
162         final BatterySettingsFeatureProvider featureProvider =
163                 FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider();
164         if (info.chargeLabel != null && featureProvider.isChargingOptimizationMode(mContext)) {
165             return info.chargeLabel;
166         }
167         if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
168             // Present status only if no remaining time or status anomalous
169             return info.statusLabel;
170         } else if (!info.discharging && info.chargeLabel != null) {
171             return info.chargeLabel;
172         } else if (info.remainingLabel == null) {
173             return info.batteryPercentString;
174         } else {
175             return mContext.getString(
176                     com.android.settingslib.R.string.power_remaining_settings_home_page,
177                     info.batteryPercentString,
178                     info.remainingLabel);
179         }
180     }
181 
182     /** Callback which receives text for the label. */
183     @Override
updateBatteryStatus(String label, BatteryInfo info)184     public void updateBatteryStatus(String label, BatteryInfo info) {
185         mBatteryStatusLabel = label; // Null if adaptive charging is not active
186         if (mPreference == null) {
187             return;
188         }
189         // Do not triggerBatteryStatusUpdate() here to cause infinite loop
190         final CharSequence summary = getSummary(false /* batteryStatusUpdate */);
191         if (summary != null) {
192             mPreference.setSummary(summary);
193         }
194         Log.d(TAG, "updateBatteryStatus: " + label + " summary: " + summary);
195     }
196 
197     @VisibleForTesting
convertClassPathToComponentName(String classPath)198     protected static ComponentName convertClassPathToComponentName(String classPath) {
199         if (classPath == null || classPath.isEmpty()) {
200             return null;
201         }
202         String[] split = classPath.split("\\.");
203         int classNameIndex = split.length - 1;
204         if (classNameIndex < 0) {
205             return null;
206         }
207         int lastPkgIndex = classPath.length() - split[classNameIndex].length() - 1;
208         String pkgName = lastPkgIndex > 0 ? classPath.substring(0, lastPkgIndex) : "";
209         return new ComponentName(pkgName, split[classNameIndex]);
210     }
211 }
212