1 /* 2 * Copyright (C) 2020 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.systemui.car.bluetooth; 18 19 import android.bluetooth.BluetoothAdapter; 20 import android.bluetooth.BluetoothDevice; 21 import android.bluetooth.BluetoothHeadsetClient; 22 import android.bluetooth.BluetoothProfile; 23 import android.content.BroadcastReceiver; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.content.IntentFilter; 27 import android.os.Bundle; 28 import android.util.Log; 29 30 import com.android.systemui.animation.Expandable; 31 import com.android.systemui.statusbar.policy.BatteryController; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 36 /** 37 * A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon 38 * displays the battery status of a device that is connected via bluetooth and not the system's 39 * battery. 40 */ 41 public class CarBatteryController extends BroadcastReceiver implements BatteryController { 42 private static final String TAG = "CarBatteryController"; 43 44 private static final int INVALID_BATTERY_LEVEL = -1; 45 46 private final Context mContext; 47 48 private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); 49 private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>(); 50 private int mLevel; 51 private BatteryViewHandler mBatteryViewHandler; 52 CarBatteryController(Context context)53 public CarBatteryController(Context context) { 54 mContext = context; 55 56 if (mAdapter == null) { 57 return; 58 } 59 } 60 61 @Override dump(PrintWriter pw, String[] args)62 public void dump(PrintWriter pw, String[] args) { 63 pw.println("CarBatteryController state:"); 64 pw.print(" mLevel="); 65 pw.println(mLevel); 66 } 67 68 @Override setPowerSaveMode(boolean powerSave)69 public void setPowerSaveMode(boolean powerSave) { 70 // No-op. No power save mode for the car. 71 } 72 73 @Override setPowerSaveMode(boolean powerSave, Expandable expandable)74 public void setPowerSaveMode(boolean powerSave, Expandable expandable) { 75 // No-op. No power save mode for the car. 76 } 77 78 @Override addCallback(BatteryController.BatteryStateChangeCallback cb)79 public void addCallback(BatteryController.BatteryStateChangeCallback cb) { 80 mChangeCallbacks.add(cb); 81 82 // There is no way to know if the phone is plugged in or charging via bluetooth, so pass 83 // false for these values. 84 cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */); 85 cb.onPowerSaveChanged(false /* isPowerSave */); 86 } 87 88 @Override removeCallback(BatteryController.BatteryStateChangeCallback cb)89 public void removeCallback(BatteryController.BatteryStateChangeCallback cb) { 90 mChangeCallbacks.remove(cb); 91 } 92 93 /** Sets {@link BatteryViewHandler}. */ addBatteryViewHandler(BatteryViewHandler batteryViewHandler)94 public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) { 95 mBatteryViewHandler = batteryViewHandler; 96 } 97 98 /** Starts listening for bluetooth broadcast messages. */ startListening()99 public void startListening() { 100 IntentFilter filter = new IntentFilter(); 101 filter.addAction(BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED); 102 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 103 mContext.registerReceiver(this, filter); 104 } 105 106 /** Stops listening for bluetooth broadcast messages. */ stopListening()107 public void stopListening() { 108 mContext.unregisterReceiver(this); 109 } 110 111 @Override onReceive(Context context, Intent intent)112 public void onReceive(Context context, Intent intent) { 113 String action = intent.getAction(); 114 115 if (Log.isLoggable(TAG, Log.DEBUG)) { 116 Log.d(TAG, "onReceive(). action: " + action); 117 } 118 119 if (BluetoothDevice.ACTION_BATTERY_LEVEL_CHANGED.equals(action)) { 120 BluetoothDevice device = 121 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE); 122 int batteryLevel = intent.getIntExtra(BluetoothDevice.EXTRA_BATTERY_LEVEL, 123 INVALID_BATTERY_LEVEL); 124 125 if (Log.isLoggable(TAG, Log.DEBUG)) { 126 Log.d(TAG, "Received ACTION_BATTERY_LEVEL_CHANGED event: device=" + device 127 + ", level=" + batteryLevel); 128 } 129 130 updateBatteryLevel(batteryLevel); 131 132 if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) { 133 mBatteryViewHandler.showBatteryView(); 134 } 135 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 136 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 137 138 if (Log.isLoggable(TAG, Log.DEBUG)) { 139 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 140 Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: " 141 + oldState + " -> " + newState); 142 143 } 144 BluetoothDevice device = 145 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE); 146 updateBatteryIcon(device, newState); 147 } 148 } 149 150 /** 151 * Verifies battery level is a valid percentage and notifies 152 * any {@link BatteryStateChangeCallback}s of this. 153 */ updateBatteryLevel(int batteryLevel)154 private void updateBatteryLevel(int batteryLevel) { 155 // Valid battery level is from 0 to 100, inclusive. 156 if (batteryLevel < 0 || batteryLevel > 100) { 157 if (Log.isLoggable(TAG, Log.DEBUG)) { 158 Log.d(TAG, "Battery level invalid. Ignoring."); 159 } 160 return; 161 } 162 163 mLevel = batteryLevel; 164 165 if (Log.isLoggable(TAG, Log.DEBUG)) { 166 Log.d(TAG, "Battery level: " + mLevel); 167 } 168 169 notifyBatteryLevelChanged(); 170 } 171 172 /** 173 * Updates the display of the battery icon depending on the given connection state from the 174 * given {@link BluetoothDevice}. 175 */ updateBatteryIcon(BluetoothDevice device, int newState)176 private void updateBatteryIcon(BluetoothDevice device, int newState) { 177 if (newState == BluetoothProfile.STATE_CONNECTED) { 178 if (Log.isLoggable(TAG, Log.DEBUG)) { 179 Log.d(TAG, "Device connected"); 180 } 181 182 if (mBatteryViewHandler != null) { 183 mBatteryViewHandler.showBatteryView(); 184 } 185 186 if (device == null) { 187 return; 188 } 189 190 int batteryLevel = device.getBatteryLevel(); 191 updateBatteryLevel(batteryLevel); 192 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 193 if (Log.isLoggable(TAG, Log.DEBUG)) { 194 Log.d(TAG, "Device disconnected"); 195 } 196 197 if (mBatteryViewHandler != null) { 198 mBatteryViewHandler.hideBatteryView(); 199 } 200 } 201 } 202 203 @Override dispatchDemoCommand(String command, Bundle args)204 public void dispatchDemoCommand(String command, Bundle args) { 205 // TODO: Car demo mode. 206 } 207 208 @Override isPluggedIn()209 public boolean isPluggedIn() { 210 return true; 211 } 212 213 @Override isPowerSave()214 public boolean isPowerSave() { 215 // Power save is not valid for the car, so always return false. 216 return false; 217 } 218 219 @Override isAodPowerSave()220 public boolean isAodPowerSave() { 221 return false; 222 } 223 notifyBatteryLevelChanged()224 private void notifyBatteryLevelChanged() { 225 for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) { 226 mChangeCallbacks.get(i) 227 .onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */); 228 } 229 } 230 231 /** 232 * An interface indicating the container of a View that will display what the information 233 * in the {@link CarBatteryController}. 234 */ 235 public interface BatteryViewHandler { 236 /** Hides the battery view. */ hideBatteryView()237 void hideBatteryView(); 238 239 /** Shows the battery view. */ showBatteryView()240 void showBatteryView(); 241 } 242 243 } 244