blob: 4642868a225e5275768e925bb21abe8d95b8a332 [file] [log] [blame]
/*
* Copyright (C) 2020 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.car.bluetooth;
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
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.telephony.SignalStrength;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import com.android.settingslib.graph.SignalDrawable;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.ScalingDrawableWrapper;
import com.android.systemui.statusbar.policy.BluetoothController;
/**
* Controller that monitors signal strength for a device that is connected via bluetooth.
*/
public class ConnectedDeviceSignalController extends BroadcastReceiver implements
BluetoothController.Callback {
private static final String TAG = "DeviceSignalCtlr";
/**
* The value that indicates if a network is unavailable. This value is according ot the
* Bluetooth HFP 1.5 spec, which indicates this value is one of two: 0 or 1. These stand
* for network unavailable and available respectively.
*/
private static final int NETWORK_UNAVAILABLE = 0;
private static final int NETWORK_UNAVAILABLE_ICON_ID = R.drawable.stat_sys_signal_null;
/**
* All possible signal strength icons. According to the Bluetooth HFP 1.5 specification,
* signal strength is indicated by a value from 1-5, where these values represent the following:
*
* <p>0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5
*
* <p>As a result, these are treated as an index into this array for the corresponding icon.
* Note that the icon is the same for 0 and 1.
*/
private static final int[] SIGNAL_STRENGTH_ICONS = {
0,
0,
1,
2,
3,
4,
};
private static final int INVALID_SIGNAL = -1;
private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
private final Context mContext;
private final BluetoothController mController;
private final View mSignalsView;
private final ImageView mNetworkSignalView;
private final float mIconScaleFactor;
private final SignalDrawable mSignalDrawable;
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;
}
}
};
public ConnectedDeviceSignalController(Context context, View signalsView) {
mContext = context;
mController = Dependency.get(BluetoothController.class);
mSignalsView = signalsView;
mNetworkSignalView = (ImageView)
mSignalsView.findViewById(R.id.connected_device_network_signal);
TypedValue typedValue = new TypedValue();
context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
mIconScaleFactor = typedValue.getFloat();
mSignalDrawable = new SignalDrawable(mNetworkSignalView.getContext());
mNetworkSignalView.setImageDrawable(
new ScalingDrawableWrapper(mSignalDrawable, mIconScaleFactor));
if (mAdapter == null) {
return;
}
mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
BluetoothProfile.HEADSET_CLIENT);
}
/** Starts listening for bluetooth broadcast messages. */
public void startListening() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
mContext.registerReceiver(this, filter);
mController.addCallback(this);
}
/** Stops listening for bluetooth broadcast messages. */
public void stopListening() {
mContext.unregisterReceiver(this);
mController.removeCallback(this);
}
@Override
public void onBluetoothDevicesChanged() {
// Nothing to do here because this Controller is not displaying a list of possible
// bluetooth devices.
}
@Override
public void onBluetoothStateChange(boolean enabled) {
if (DEBUG) {
Log.d(TAG, "onBluetoothStateChange(). enabled: " + enabled);
}
// Only need to handle the case if bluetooth has been disabled, in which case the
// signal indicators are hidden. If bluetooth has been enabled, then this class should
// receive updates to the connection state via onReceive().
if (!enabled) {
mNetworkSignalView.setVisibility(View.GONE);
mSignalsView.setVisibility(View.GONE);
}
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DEBUG) {
Log.d(TAG, "onReceive(). action: " + action);
}
if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
if (DEBUG) {
Log.d(TAG, "Received ACTION_AG_EVENT");
}
processActionAgEvent(intent);
} else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
if (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);
updateViewVisibility(device, newState);
}
}
/**
* Processes an {@link Intent} that had an action of
* {@link BluetoothHeadsetClient#ACTION_AG_EVENT}.
*/
private void processActionAgEvent(Intent intent) {
int networkStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
INVALID_SIGNAL);
if (networkStatus != INVALID_SIGNAL) {
if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_STATUS: " + " " + networkStatus);
}
if (networkStatus == NETWORK_UNAVAILABLE) {
setNetworkSignalIcon(NETWORK_UNAVAILABLE_ICON_ID);
}
}
int signalStrength = intent.getIntExtra(
BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
if (signalStrength != INVALID_SIGNAL) {
if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
}
setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
}
int roamingStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
INVALID_SIGNAL);
if (roamingStatus != INVALID_SIGNAL) {
if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_ROAMING: " + roamingStatus);
}
}
}
private void setNetworkSignalIcon(int level) {
// Setting the icon on a child view of mSignalView, so toggle this container visible.
mSignalsView.setVisibility(View.VISIBLE);
mSignalDrawable.setLevel(SignalDrawable.getState(level,
SignalStrength.NUM_SIGNAL_STRENGTH_BINS, false));
mNetworkSignalView.setVisibility(View.VISIBLE);
}
private void updateViewVisibility(BluetoothDevice device, int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
if (DEBUG) {
Log.d(TAG, "Device connected");
}
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 signalStrength = featuresBundle.getInt(
BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
if (signalStrength != INVALID_SIGNAL) {
if (DEBUG) {
Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
}
setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
if (DEBUG) {
Log.d(TAG, "Device disconnected");
}
mNetworkSignalView.setVisibility(View.GONE);
mSignalsView.setVisibility(View.GONE);
}
}
}