/* * 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: * *
0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5 * *
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); } } }