1 /*
2  * Copyright (C) 2021 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 package com.android.systemui.battery;
17 
18 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
19 
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.database.ContentObserver;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.HandlerExecutor;
26 import android.provider.Settings;
27 import android.text.TextUtils;
28 import android.util.ArraySet;
29 import android.view.View;
30 
31 import androidx.annotation.NonNull;
32 
33 import com.android.systemui.R;
34 import com.android.systemui.dagger.qualifiers.Main;
35 import com.android.systemui.flags.FeatureFlags;
36 import com.android.systemui.flags.Flags;
37 import com.android.systemui.settings.UserTracker;
38 import com.android.systemui.statusbar.phone.StatusBarIconController;
39 import com.android.systemui.statusbar.phone.StatusBarLocation;
40 import com.android.systemui.statusbar.policy.BatteryController;
41 import com.android.systemui.statusbar.policy.ConfigurationController;
42 import com.android.systemui.tuner.TunerService;
43 import com.android.systemui.util.ViewController;
44 
45 import java.io.PrintWriter;
46 
47 import javax.inject.Inject;
48 
49 /** Controller for {@link BatteryMeterView}. **/
50 public class BatteryMeterViewController extends ViewController<BatteryMeterView> {
51     private final ConfigurationController mConfigurationController;
52     private final TunerService mTunerService;
53     private final Handler mMainHandler;
54     private final ContentResolver mContentResolver;
55     private final FeatureFlags mFeatureFlags;
56     private final BatteryController mBatteryController;
57 
58     private final String mSlotBattery;
59     private final SettingObserver mSettingObserver;
60     private final UserTracker mUserTracker;
61     private final StatusBarLocation mLocation;
62 
63     private final ConfigurationController.ConfigurationListener mConfigurationListener =
64             new ConfigurationController.ConfigurationListener() {
65                 @Override
66                 public void onDensityOrFontScaleChanged() {
67                     mView.scaleBatteryMeterViews();
68                 }
69             };
70 
71     private final TunerService.Tunable mTunable = new TunerService.Tunable() {
72         @Override
73         public void onTuningChanged(String key, String newValue) {
74             if (StatusBarIconController.ICON_HIDE_LIST.equals(key)) {
75                 ArraySet<String> icons = StatusBarIconController.getIconHideList(
76                         getContext(), newValue);
77                 mView.setVisibility(icons.contains(mSlotBattery) ? View.GONE : View.VISIBLE);
78             }
79         }
80     };
81 
82     private final BatteryController.BatteryStateChangeCallback mBatteryStateChangeCallback =
83             new BatteryController.BatteryStateChangeCallback() {
84                 @Override
85                 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
86                     mView.onBatteryLevelChanged(level, pluggedIn);
87                 }
88 
89                 @Override
90                 public void onPowerSaveChanged(boolean isPowerSave) {
91                     mView.onPowerSaveChanged(isPowerSave);
92                 }
93 
94                 @Override
95                 public void onBatteryUnknownStateChanged(boolean isUnknown) {
96                     mView.onBatteryUnknownStateChanged(isUnknown);
97                 }
98 
99                 @Override
100                 public void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
101                     mView.onIsBatteryDefenderChanged(isBatteryDefender);
102                 }
103 
104                 @Override
105                 public void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
106                     if (mFeatureFlags.isEnabled(Flags.INCOMPATIBLE_CHARGING_BATTERY_ICON)) {
107                         mView.onIsIncompatibleChargingChanged(isIncompatibleCharging);
108                     }
109                 }
110 
111                 @Override
112                 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
113                     pw.print(super.toString());
114                     pw.println(" location=" + mLocation);
115                     mView.dump(pw, args);
116                 }
117             };
118 
119     private final UserTracker.Callback mUserChangedCallback =
120             new UserTracker.Callback() {
121                 @Override
122                 public void onUserChanged(int newUser, @NonNull Context userContext) {
123                     mContentResolver.unregisterContentObserver(mSettingObserver);
124                     registerShowBatteryPercentObserver(newUser);
125                     mView.updateShowPercent();
126                 }
127             };
128 
129     // Some places may need to show the battery conditionally, and not obey the tuner
130     private boolean mIgnoreTunerUpdates;
131     private boolean mIsSubscribedForTunerUpdates;
132 
133     @Inject
BatteryMeterViewController( BatteryMeterView view, StatusBarLocation location, UserTracker userTracker, ConfigurationController configurationController, TunerService tunerService, @Main Handler mainHandler, ContentResolver contentResolver, FeatureFlags featureFlags, BatteryController batteryController)134     public BatteryMeterViewController(
135             BatteryMeterView view,
136             StatusBarLocation location,
137             UserTracker userTracker,
138             ConfigurationController configurationController,
139             TunerService tunerService,
140             @Main Handler mainHandler,
141             ContentResolver contentResolver,
142             FeatureFlags featureFlags,
143             BatteryController batteryController) {
144         super(view);
145         mLocation = location;
146         mUserTracker = userTracker;
147         mConfigurationController = configurationController;
148         mTunerService = tunerService;
149         mMainHandler = mainHandler;
150         mContentResolver = contentResolver;
151         mFeatureFlags = featureFlags;
152         mBatteryController = batteryController;
153 
154         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
155         mView.setDisplayShieldEnabled(
156                 getContext().getResources().getBoolean(R.bool.flag_battery_shield_icon));
157 
158         mSlotBattery = getResources().getString(com.android.internal.R.string.status_bar_battery);
159         mSettingObserver = new SettingObserver(mMainHandler);
160     }
161 
162     @Override
onViewAttached()163     protected void onViewAttached() {
164         mConfigurationController.addCallback(mConfigurationListener);
165         subscribeForTunerUpdates();
166         mBatteryController.addCallback(mBatteryStateChangeCallback);
167 
168         registerShowBatteryPercentObserver(mUserTracker.getUserId());
169         registerGlobalBatteryUpdateObserver();
170         mUserTracker.addCallback(mUserChangedCallback, new HandlerExecutor(mMainHandler));
171 
172         mView.updateShowPercent();
173     }
174 
175     @Override
onViewDetached()176     protected void onViewDetached() {
177         mConfigurationController.removeCallback(mConfigurationListener);
178         unsubscribeFromTunerUpdates();
179         mBatteryController.removeCallback(mBatteryStateChangeCallback);
180 
181         mUserTracker.removeCallback(mUserChangedCallback);
182         mContentResolver.unregisterContentObserver(mSettingObserver);
183     }
184 
185     /**
186      * Turn off {@link BatteryMeterView}'s subscribing to the tuner for updates, and thus avoid it
187      * controlling its own visibility.
188      */
ignoreTunerUpdates()189     public void ignoreTunerUpdates() {
190         mIgnoreTunerUpdates = true;
191         unsubscribeFromTunerUpdates();
192     }
193 
subscribeForTunerUpdates()194     private void subscribeForTunerUpdates() {
195         if (mIsSubscribedForTunerUpdates || mIgnoreTunerUpdates) {
196             return;
197         }
198 
199         mTunerService.addTunable(mTunable, StatusBarIconController.ICON_HIDE_LIST);
200         mIsSubscribedForTunerUpdates = true;
201     }
202 
unsubscribeFromTunerUpdates()203     private void unsubscribeFromTunerUpdates() {
204         if (!mIsSubscribedForTunerUpdates) {
205             return;
206         }
207 
208         mTunerService.removeTunable(mTunable);
209         mIsSubscribedForTunerUpdates = false;
210     }
211 
registerShowBatteryPercentObserver(int user)212     private void registerShowBatteryPercentObserver(int user) {
213         mContentResolver.registerContentObserver(
214                 Settings.System.getUriFor(SHOW_BATTERY_PERCENT),
215                 false,
216                 mSettingObserver,
217                 user);
218     }
219 
registerGlobalBatteryUpdateObserver()220     private void registerGlobalBatteryUpdateObserver() {
221         mContentResolver.registerContentObserver(
222                 Settings.Global.getUriFor(Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME),
223                 false,
224                 mSettingObserver);
225     }
226 
227     private final class SettingObserver extends ContentObserver {
SettingObserver(Handler handler)228         public SettingObserver(Handler handler) {
229             super(handler);
230         }
231 
232         @Override
onChange(boolean selfChange, Uri uri)233         public void onChange(boolean selfChange, Uri uri) {
234             super.onChange(selfChange, uri);
235             mView.updateShowPercent();
236             if (TextUtils.equals(uri.getLastPathSegment(),
237                     Settings.Global.BATTERY_ESTIMATES_LAST_UPDATE_TIME)) {
238                 // update the text for sure if the estimate in the cache was updated
239                 mView.updatePercentText();
240             }
241         }
242     }
243 }
244