blob: 58f80a4ed968040386f022e891aa5a28af78df4b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.statusbar.car;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProfile.ServiceListener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.util.Log;
import com.android.systemui.statusbar.policy.BatteryController;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
/**
* A {@link BatteryController} that is specific to the Auto use-case. For Auto, the battery icon
* displays the battery status of a device that is connected via bluetooth and not the system's
* battery.
*/
public class CarBatteryController extends BroadcastReceiver implements BatteryController {
private static final String TAG = "CarBatteryController";
// According to the Bluetooth HFP 1.5 specification, battery levels are indicated by a
// value from 1-5, where these values represent the following:
// 0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
// As a result, set the level as the average within that range.
private static final int BATTERY_LEVEL_EMPTY = 0;
private static final int BATTERY_LEVEL_1 = 12;
private static final int BATTERY_LEVEL_2 = 28;
private static final int BATTERY_LEVEL_3 = 63;
private static final int BATTERY_LEVEL_4 = 87;
private static final int BATTERY_LEVEL_FULL = 100;
private static final int INVALID_BATTERY_LEVEL = -1;
private final Context mContext;
private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
private final ArrayList<BatteryStateChangeCallback> mChangeCallbacks = new ArrayList<>();
private BluetoothHeadsetClient mBluetoothHeadsetClient;
private final ServiceListener mHfpServiceListener = new ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET_CLIENT) {
mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
}
}
@Override
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET_CLIENT) {
mBluetoothHeadsetClient = null;
}
}
};
private int mLevel;
private BatteryViewHandler mBatteryViewHandler;
public CarBatteryController(Context context) {
mContext = context;
if (mAdapter == null) {
return;
}
mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
BluetoothProfile.HEADSET_CLIENT);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("CarBatteryController state:");
pw.print(" mLevel=");
pw.println(mLevel);
}
@Override
public void setPowerSaveMode(boolean powerSave) {
// No-op. No power save mode for the car.
}
@Override
public void addCallback(BatteryController.BatteryStateChangeCallback cb) {
mChangeCallbacks.add(cb);
// There is no way to know if the phone is plugged in or charging via bluetooth, so pass
// false for these values.
cb.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
cb.onPowerSaveChanged(false /* isPowerSave */);
}
@Override
public void removeCallback(BatteryController.BatteryStateChangeCallback cb) {
mChangeCallbacks.remove(cb);
}
public void addBatteryViewHandler(BatteryViewHandler batteryViewHandler) {
mBatteryViewHandler = batteryViewHandler;
}
public void startListening() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
mContext.registerReceiver(this, filter);
}
public void stopListening() {
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onReceive(). action: " + action);
}
if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Received ACTION_AG_EVENT");
}
int batteryLevel = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
INVALID_BATTERY_LEVEL);
updateBatteryLevel(batteryLevel);
if (batteryLevel != INVALID_BATTERY_LEVEL && mBatteryViewHandler != null) {
mBatteryViewHandler.showBatteryView();
}
} else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
if (Log.isLoggable(TAG, Log.DEBUG)) {
int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: "
+ oldState + " -> " + newState);
}
BluetoothDevice device =
(BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
updateBatteryIcon(device, newState);
}
}
/**
* Converts the battery level to a percentage that can be displayed on-screen and notifies
* any {@link BatteryStateChangeCallback}s of this.
*/
private void updateBatteryLevel(int batteryLevel) {
if (batteryLevel == INVALID_BATTERY_LEVEL) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Battery level invalid. Ignoring.");
}
return;
}
// The battery level is a value between 0-5. Let the default battery level be 0.
switch (batteryLevel) {
case 5:
mLevel = BATTERY_LEVEL_FULL;
break;
case 4:
mLevel = BATTERY_LEVEL_4;
break;
case 3:
mLevel = BATTERY_LEVEL_3;
break;
case 2:
mLevel = BATTERY_LEVEL_2;
break;
case 1:
mLevel = BATTERY_LEVEL_1;
break;
case 0:
default:
mLevel = BATTERY_LEVEL_EMPTY;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Battery level: " + batteryLevel + "; setting mLevel as: " + mLevel);
}
notifyBatteryLevelChanged();
}
/**
* Updates the display of the battery icon depending on the given connection state from the
* given {@link BluetoothDevice}.
*/
private void updateBatteryIcon(BluetoothDevice device, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Device connected");
}
if (mBatteryViewHandler != null) {
mBatteryViewHandler.showBatteryView();
}
if (mBluetoothHeadsetClient == null || device == null) {
return;
}
// Check if battery information is available and immediately update.
Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device);
if (featuresBundle == null) {
return;
}
int batteryLevel = featuresBundle.getInt(BluetoothHeadsetClient.EXTRA_BATTERY_LEVEL,
INVALID_BATTERY_LEVEL);
updateBatteryLevel(batteryLevel);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Device disconnected");
}
if (mBatteryViewHandler != null) {
mBatteryViewHandler.hideBatteryView();
}
}
}
@Override
public void dispatchDemoCommand(String command, Bundle args) {
// TODO: Car demo mode.
}
@Override
public boolean isPowerSave() {
// Power save is not valid for the car, so always return false.
return false;
}
private void notifyBatteryLevelChanged() {
for (int i = 0, size = mChangeCallbacks.size(); i < size; i++) {
mChangeCallbacks.get(i)
.onBatteryLevelChanged(mLevel, false /* pluggedIn */, false /* charging */);
}
}
/**
* An interface indicating the container of a View that will display what the information
* in the {@link CarBatteryController}.
*/
public interface BatteryViewHandler {
void hideBatteryView();
void showBatteryView();
}
}