blob: d79849ccafc63f1e3971ff4d6fc8d8bf139ee12a [file] [log] [blame]
Anthony Chenda62fdcd52016-04-06 16:15:14 -07001/*
2 * Copyright (C) 2016 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
17package com.android.systemui.statusbar.car;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothHeadsetClient;
22import android.bluetooth.BluetoothProfile;
23import android.bluetooth.BluetoothProfile.ServiceListener;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.os.Bundle;
29import android.util.Log;
30
31import com.android.systemui.statusbar.policy.BatteryController;
32
33import java.io.FileDescriptor;
34import java.io.PrintWriter;
35import java.util.ArrayList;
36
37/**
38 * A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon
39 * displays the battery status of a device that is connected via bluetooth and not the system's
40 * battery.
41 */
42public class CarBatteryController extends BroadcastReceiver implements BatteryController {
43 private static final String TAG = "CarBatteryController";
44
45 // According to the Bluetooth HFP 1.5 specification, battery levels are indicated by a
46 // value from 1-5, where these values represent the following:
47 // 0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
48 // As a result, set the level as the average within that range.
49 private static final int BATTERY_LEVEL_EMPTY = 0;
50 private static final int BATTERY_LEVEL_1 = 12;
51 private static final int BATTERY_LEVEL_2 = 28;
52 private static final int BATTERY_LEVEL_3 = 63;
53 private static final int BATTERY_LEVEL_4 = 87;
54 private static final int BATTERY_LEVEL_FULL = 100;
55
56 private static final int INVALID_BATTERY_LEVEL = -1;
57
58 private final Context mContext;
59
60 private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
Anthony Chenda62fdcd52016-04-06 16:15:14 -070061 private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
Brad Stenning8d1a51c2018-11-20 17:34:16 -080062 private BluetoothHeadsetClient mBluetoothHeadsetClient;
63 private final ServiceListener mHfpServiceListener = new ServiceListener() {
64 @Override
65 public void onServiceConnected(int profile, BluetoothProfile proxy) {
66 if (profile == BluetoothProfile.HEADSET_CLIENT) {
67 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
68 }
69 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -070070
Brad Stenning8d1a51c2018-11-20 17:34:16 -080071 @Override
72 public void onServiceDisconnected(int profile) {
73 if (profile == BluetoothProfile.HEADSET_CLIENT) {
74 mBluetoothHeadsetClient = null;
75 }
76 }
77 };
Anthony Chenda62fdcd52016-04-06 16:15:14 -070078 private int mLevel;
Anthony Chenda62fdcd52016-04-06 16:15:14 -070079 private BatteryViewHandler mBatteryViewHandler;
80
81 public CarBatteryController(Context context) {
82 mContext = context;
83
Sam Hurstb3e73862016-05-31 10:35:17 -070084 if (mAdapter == null) {
Brad Stenning8d1a51c2018-11-20 17:34:16 -080085 return;
Sam Hurstb3e73862016-05-31 10:35:17 -070086 }
87
Anthony Chenda62fdcd52016-04-06 16:15:14 -070088 mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
89 BluetoothProfile.HEADSET_CLIENT);
90 }
91
92 @Override
93 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
94 pw.println("CarBatteryController state:");
95 pw.print(" mLevel=");
96 pw.println(mLevel);
97 }
98
99 @Override
100 public void setPowerSaveMode(boolean powerSave) {
101 // No-op. No power save mode for the car.
102 }
103
104 @Override
Jason Monk88529052016-11-04 13:29:58 -0400105 public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700106 mChangeCallbacks.add(cb);
107
108 // There is no way to know if the phone is plugged in or charging via bluetooth, so pass
109 // false for these values.
110 cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
111 cb.onPowerSaveChanged(false /* isPowerSave */);
112 }
113
114 @Override
Jason Monk88529052016-11-04 13:29:58 -0400115 public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700116 mChangeCallbacks.remove(cb);
117 }
118
119 public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) {
120 mBatteryViewHandler = batteryViewHandler;
121 }
122
123 public void startListening() {
124 IntentFilter filter = new IntentFilter();
125 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
126 filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
127 mContext.registerReceiver(this, filter);
128 }
129
130 public void stopListening() {
131 mContext.unregisterReceiver(this);
132 }
133
134 @Override
135 public void onReceive(Context context, Intent intent) {
136 String action = intent.getAction();
137
138 if (Log.isLoggable(TAG, Log.DEBUG)) {
139 Log.d(TAG, "onReceive(). action: " + action);
140 }
141
142 if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
143 if (Log.isLoggable(TAG, Log.DEBUG)) {
144 Log.d(TAG, "Received ACTION_AG_EVENT");
145 }
146
147 int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
148 INVALID_BATTERY_LEVEL);
149
150 updateBatteryLevel(batteryLevel);
151
152 if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) {
153 mBatteryViewHandler.showBatteryView();
154 }
155 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
156 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
157
158 if (Log.isLoggable(TAG, Log.DEBUG)) {
159 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
160 Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
161 + oldState + " -> " + newState);
162
163 }
164 BluetoothDevice device =
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800165 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700166 updateBatteryIcon(device, newState);
167 }
168 }
169
170 /**
171 * Converts the battery level to a percentage that can be displayed on-screen and notifies
172 * any {@link BatteryStateChangeCallback}s of this.
173 */
174 private void updateBatteryLevel(int batteryLevel) {
175 if (batteryLevel == INVALID_BATTERY_LEVEL) {
176 if (Log.isLoggable(TAG, Log.DEBUG)) {
177 Log.d(TAG, "Battery level invalid. Ignoring.");
178 }
179 return;
180 }
181
182 // The battery level is a value between 0-5. Let the default battery level be 0.
183 switch (batteryLevel) {
184 case 5:
185 mLevel = BATTERY_LEVEL_FULL;
186 break;
187 case 4:
188 mLevel = BATTERY_LEVEL_4;
189 break;
190 case 3:
191 mLevel = BATTERY_LEVEL_3;
192 break;
193 case 2:
194 mLevel = BATTERY_LEVEL_2;
195 break;
196 case 1:
197 mLevel = BATTERY_LEVEL_1;
198 break;
199 case 0:
200 default:
201 mLevel = BATTERY_LEVEL_EMPTY;
202 }
203
204 if (Log.isLoggable(TAG, Log.DEBUG)) {
205 Log.d(TAG, "Battery level: " + batteryLevel + "; setting mLevel as: " + mLevel);
206 }
207
208 notifyBatteryLevelChanged();
209 }
210
211 /**
212 * Updates the display of the battery icon depending on the given connection state from the
213 * given {@link BluetoothDevice}.
214 */
215 private void updateBatteryIcon(BluetoothDevice device, int newState) {
216 if (newState == BluetoothProfile.STATE_CONNECTED) {
217 if (Log.isLoggable(TAG, Log.DEBUG)) {
218 Log.d(TAG, "Device connected");
219 }
220
221 if (mBatteryViewHandler != null) {
222 mBatteryViewHandler.showBatteryView();
223 }
224
225 if (mBluetoothHeadsetClient == null || device == null) {
226 return;
227 }
228
229 // Check if battery information is available and immediately update.
230 Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device);
231 if (featuresBundle == null) {
232 return;
233 }
234
235 int batteryLevel = featuresBundle.getInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
236 INVALID_BATTERY_LEVEL);
237 updateBatteryLevel(batteryLevel);
238 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
239 if (Log.isLoggable(TAG, Log.DEBUG)) {
240 Log.d(TAG, "Device disconnected");
241 }
242
243 if (mBatteryViewHandler != null) {
244 mBatteryViewHandler.hideBatteryView();
245 }
246 }
247 }
248
249 @Override
Jason Monk98d7c7a2016-04-12 13:08:31 -0400250 public void dispatchDemoCommand(String command, Bundle args) {
251 // TODO: Car demo mode.
252 }
253
254 @Override
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700255 public boolean isPowerSave() {
256 // Power save is not valid for the car, so always return false.
257 return false;
258 }
259
Lucas Dupin6edeb182019-09-25 13:39:21 -0700260 @Override
261 public boolean isAodPowerSave() {
262 return false;
263 }
264
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700265 private void notifyBatteryLevelChanged() {
266 for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
267 mChangeCallbacks.get(i)
268 .onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
269 }
270 }
271
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800272 /**
273 * An interface indicating the container of a View that will display what the information
274 * in the {@link CarBatteryController}.
275 */
276 public interface BatteryViewHandler {
277 void hideBatteryView();
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700278
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800279 void showBatteryView();
280 }
Anthony Chenda62fdcd52016-04-06 16:15:14 -0700281
282}