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 
22 import androidx.annotation.VisibleForTesting;
23 import androidx.preference.Preference;
24 import androidx.preference.PreferenceScreen;
25 
26 import com.android.settings.R;
27 import com.android.settings.core.BasePreferenceController;
28 import com.android.settings.overlay.FeatureFactory;
29 import com.android.settingslib.core.lifecycle.LifecycleObserver;
30 import com.android.settingslib.core.lifecycle.events.OnStart;
31 import com.android.settingslib.core.lifecycle.events.OnStop;
32 import com.android.settingslib.utils.ThreadUtils;
33 
34 public class TopLevelBatteryPreferenceController extends BasePreferenceController implements
35         LifecycleObserver, OnStart, OnStop, BatteryPreferenceController {
36 
37     @VisibleForTesting
38     protected boolean mIsBatteryPresent = true;
39     @VisibleForTesting
40     Preference mPreference;
41     private final BatteryBroadcastReceiver mBatteryBroadcastReceiver;
42     private BatteryInfo mBatteryInfo;
43     private BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
44     private String mBatteryStatusLabel;
45 
TopLevelBatteryPreferenceController(Context context, String preferenceKey)46     public TopLevelBatteryPreferenceController(Context context, String preferenceKey) {
47         super(context, preferenceKey);
48         mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
49         mBatteryBroadcastReceiver.setBatteryChangedListener(type -> {
50             if (type == BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT) {
51                 mIsBatteryPresent = false;
52             }
53             BatteryInfo.getBatteryInfo(mContext, info -> {
54                 mBatteryInfo = info;
55                 updateState(mPreference);
56             }, true /* shortString */);
57         });
58 
59         mBatteryStatusFeatureProvider = FeatureFactory.getFactory(context)
60                 .getBatteryStatusFeatureProvider(context);
61     }
62 
63     @Override
getAvailabilityStatus()64     public int getAvailabilityStatus() {
65         return mContext.getResources().getBoolean(R.bool.config_show_top_level_battery)
66                 ? AVAILABLE : UNSUPPORTED_ON_DEVICE;
67     }
68 
69     @Override
displayPreference(PreferenceScreen screen)70     public void displayPreference(PreferenceScreen screen) {
71         super.displayPreference(screen);
72         mPreference = screen.findPreference(getPreferenceKey());
73     }
74 
75     @Override
onStart()76     public void onStart() {
77         mBatteryBroadcastReceiver.register();
78     }
79 
80     @Override
onStop()81     public void onStop() {
82         mBatteryBroadcastReceiver.unRegister();
83     }
84 
85     @Override
getSummary()86     public CharSequence getSummary() {
87         return getSummary(true /* batteryStatusUpdate */);
88     }
89 
getSummary(boolean batteryStatusUpdate)90     private CharSequence getSummary(boolean batteryStatusUpdate) {
91         // Display help message if battery is not present.
92         if (!mIsBatteryPresent) {
93             return mContext.getText(R.string.battery_missing_message);
94         }
95         return getDashboardLabel(mContext, mBatteryInfo, batteryStatusUpdate);
96     }
97 
getDashboardLabel(Context context, BatteryInfo info, boolean batteryStatusUpdate)98     protected CharSequence getDashboardLabel(Context context, BatteryInfo info,
99             boolean batteryStatusUpdate) {
100         if (info == null || context == null) {
101             return null;
102         }
103 
104         if (batteryStatusUpdate) {
105             setSummaryAsync(info);
106         }
107 
108         return (mBatteryStatusLabel == null) ? generateLabel(info) : mBatteryStatusLabel;
109     }
110 
setSummaryAsync(BatteryInfo info)111     private void setSummaryAsync(BatteryInfo info) {
112         ThreadUtils.postOnBackgroundThread(() -> {
113             final boolean triggerBatteryStatusUpdate =
114                     mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info);
115             ThreadUtils.postOnMainThread(() -> {
116                 if (!triggerBatteryStatusUpdate) {
117                     mBatteryStatusLabel = null; // will generateLabel()
118                 }
119                 mPreference.setSummary(
120                         (mBatteryStatusLabel == null) ? generateLabel(info) : mBatteryStatusLabel);
121             });
122         });
123     }
124 
generateLabel(BatteryInfo info)125     private CharSequence generateLabel(BatteryInfo info) {
126         if (!info.discharging && info.chargeLabel != null) {
127             return info.chargeLabel;
128         } else if (info.remainingLabel == null) {
129             return info.batteryPercentString;
130         } else {
131             return mContext.getString(R.string.power_remaining_settings_home_page,
132                     info.batteryPercentString,
133                     info.remainingLabel);
134         }
135     }
136 
137     /**
138      * Callback which receives text for the label.
139      */
updateBatteryStatus(String label, BatteryInfo info)140     public void updateBatteryStatus(String label, BatteryInfo info) {
141         mBatteryStatusLabel = label; // Null if adaptive charging is not active
142 
143         if (mPreference != null) {
144             // Do not triggerBatteryStatusUpdate(), otherwise there will be an infinite loop
145             final CharSequence summary = getSummary(false /* batteryStatusUpdate */);
146             if (summary != null) {
147                 mPreference.setSummary(summary);
148             }
149         }
150     }
151 
152     @VisibleForTesting
convertClassPathToComponentName(String classPath)153     protected static ComponentName convertClassPathToComponentName(String classPath) {
154         if (classPath == null || classPath.isEmpty()) {
155             return null;
156         }
157         String[] split = classPath.split("\\.");
158         int classNameIndex = split.length - 1;
159         if (classNameIndex < 0) {
160             return null;
161         }
162         int lastPkgIndex = classPath.length() - split[classNameIndex].length() - 1;
163         String pkgName = lastPkgIndex > 0 ? classPath.substring(0, lastPkgIndex) : "";
164         return new ComponentName(pkgName, split[classNameIndex]);
165     }
166 }
167