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 static com.android.systemui.statusbar.phone.StatusBar.DEBUG; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadsetClient; 24 import android.bluetooth.BluetoothProfile; 25 import android.bluetooth.BluetoothProfile.ServiceListener; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.os.Bundle; 31 import android.telephony.SignalStrength; 32 import android.util.Log; 33 import android.util.TypedValue; 34 import android.view.View; 35 import android.widget.ImageView; 36 37 import com.android.settingslib.graph.SignalDrawable; 38 import com.android.systemui.Dependency; 39 import com.android.systemui.R; 40 import com.android.systemui.statusbar.ScalingDrawableWrapper; 41 import com.android.systemui.statusbar.policy.BluetoothController; 42 43 /** 44 * Controller that monitors signal strength for a device that is connected via bluetooth. 45 */ 46 public class ConnectedDeviceSignalController extends BroadcastReceiver implements 47 BluetoothController.Callback { 48 private static final String TAG = "DeviceSignalCtlr"; 49 50 /** 51 * The value that indicates if a network is unavailable. This value is according ot the 52 * Bluetooth HFP 1.5 spec, which indicates this value is one of two: 0 or 1. These stand 53 * for network unavailable and available respectively. 54 */ 55 private static final int NETWORK_UNAVAILABLE = 0; 56 private static final int NETWORK_UNAVAILABLE_ICON_ID = R.drawable.stat_sys_signal_null; 57 58 /** 59 * All possible signal strength icons. According to the Bluetooth HFP 1.5 specification, 60 * signal strength is indicated by a value from 1-5, where these values represent the following: 61 * 62 * <p>0%% - 0, 1-25%% - 1, 26-50%% - 2, 51-75%% - 3, 76-99%% - 4, 100%% - 5 63 * 64 * <p>As a result, these are treated as an index into this array for the corresponding icon. 65 * Note that the icon is the same for 0 and 1. 66 */ 67 private static final int[] SIGNAL_STRENGTH_ICONS = { 68 0, 69 0, 70 1, 71 2, 72 3, 73 4, 74 }; 75 76 private static final int INVALID_SIGNAL = -1; 77 78 private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); 79 private final Context mContext; 80 private final BluetoothController mController; 81 82 private final View mSignalsView; 83 private final ImageView mNetworkSignalView; 84 85 private final float mIconScaleFactor; 86 private final SignalDrawable mSignalDrawable; 87 88 private BluetoothHeadsetClient mBluetoothHeadsetClient; 89 private final ServiceListener mHfpServiceListener = new ServiceListener() { 90 @Override 91 public void onServiceConnected(int profile, BluetoothProfile proxy) { 92 if (profile == BluetoothProfile.HEADSET_CLIENT) { 93 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy; 94 } 95 } 96 97 @Override 98 public void onServiceDisconnected(int profile) { 99 if (profile == BluetoothProfile.HEADSET_CLIENT) { 100 mBluetoothHeadsetClient = null; 101 } 102 } 103 }; 104 ConnectedDeviceSignalController(Context context, View signalsView)105 public ConnectedDeviceSignalController(Context context, View signalsView) { 106 mContext = context; 107 mController = Dependency.get(BluetoothController.class); 108 109 mSignalsView = signalsView; 110 mNetworkSignalView = (ImageView) 111 mSignalsView.findViewById(R.id.connected_device_network_signal); 112 113 TypedValue typedValue = new TypedValue(); 114 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 115 mIconScaleFactor = typedValue.getFloat(); 116 mSignalDrawable = new SignalDrawable(mNetworkSignalView.getContext()); 117 mNetworkSignalView.setImageDrawable( 118 new ScalingDrawableWrapper(mSignalDrawable, mIconScaleFactor)); 119 120 if (mAdapter == null) { 121 return; 122 } 123 124 mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener, 125 BluetoothProfile.HEADSET_CLIENT); 126 } 127 128 /** Starts listening for bluetooth broadcast messages. */ startListening()129 public void startListening() { 130 IntentFilter filter = new IntentFilter(); 131 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 132 filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT); 133 mContext.registerReceiver(this, filter); 134 135 mController.addCallback(this); 136 } 137 138 /** Stops listening for bluetooth broadcast messages. */ stopListening()139 public void stopListening() { 140 mContext.unregisterReceiver(this); 141 mController.removeCallback(this); 142 } 143 144 @Override onBluetoothDevicesChanged()145 public void onBluetoothDevicesChanged() { 146 // Nothing to do here because this Controller is not displaying a list of possible 147 // bluetooth devices. 148 } 149 150 @Override onBluetoothStateChange(boolean enabled)151 public void onBluetoothStateChange(boolean enabled) { 152 if (DEBUG) { 153 Log.d(TAG, "onBluetoothStateChange(). enabled: " + enabled); 154 } 155 156 // Only need to handle the case if bluetooth has been disabled, in which case the 157 // signal indicators are hidden. If bluetooth has been enabled, then this class should 158 // receive updates to the connection state via onReceive(). 159 if (!enabled) { 160 mNetworkSignalView.setVisibility(View.GONE); 161 mSignalsView.setVisibility(View.GONE); 162 } 163 } 164 165 @Override onReceive(Context context, Intent intent)166 public void onReceive(Context context, Intent intent) { 167 String action = intent.getAction(); 168 169 if (DEBUG) { 170 Log.d(TAG, "onReceive(). action: " + action); 171 } 172 173 if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) { 174 if (DEBUG) { 175 Log.d(TAG, "Received ACTION_AG_EVENT"); 176 } 177 178 processActionAgEvent(intent); 179 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 180 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 181 182 if (DEBUG) { 183 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 184 Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: " 185 + oldState + " -> " + newState); 186 } 187 BluetoothDevice device = 188 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE); 189 updateViewVisibility(device, newState); 190 } 191 } 192 193 /** 194 * Processes an {@link Intent} that had an action of 195 * {@link BluetoothHeadsetClient#ACTION_AG_EVENT}. 196 */ processActionAgEvent(Intent intent)197 private void processActionAgEvent(Intent intent) { 198 int networkStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS, 199 INVALID_SIGNAL); 200 if (networkStatus != INVALID_SIGNAL) { 201 if (DEBUG) { 202 Log.d(TAG, "EXTRA_NETWORK_STATUS: " + " " + networkStatus); 203 } 204 205 if (networkStatus == NETWORK_UNAVAILABLE) { 206 setNetworkSignalIcon(NETWORK_UNAVAILABLE_ICON_ID); 207 } 208 } 209 210 int signalStrength = intent.getIntExtra( 211 BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL); 212 if (signalStrength != INVALID_SIGNAL) { 213 if (DEBUG) { 214 Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength); 215 } 216 217 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]); 218 } 219 220 int roamingStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING, 221 INVALID_SIGNAL); 222 if (roamingStatus != INVALID_SIGNAL) { 223 if (DEBUG) { 224 Log.d(TAG, "EXTRA_NETWORK_ROAMING: " + roamingStatus); 225 } 226 } 227 } 228 setNetworkSignalIcon(int level)229 private void setNetworkSignalIcon(int level) { 230 // Setting the icon on a child view of mSignalView, so toggle this container visible. 231 mSignalsView.setVisibility(View.VISIBLE); 232 233 mSignalDrawable.setLevel(SignalDrawable.getState(level, 234 SignalStrength.NUM_SIGNAL_STRENGTH_BINS, false)); 235 mNetworkSignalView.setVisibility(View.VISIBLE); 236 } 237 updateViewVisibility(BluetoothDevice device, int newState)238 private void updateViewVisibility(BluetoothDevice device, int newState) { 239 if (newState == BluetoothProfile.STATE_CONNECTED) { 240 if (DEBUG) { 241 Log.d(TAG, "Device connected"); 242 } 243 244 if (mBluetoothHeadsetClient == null || device == null) { 245 return; 246 } 247 248 // Check if battery information is available and immediately update. 249 Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device); 250 if (featuresBundle == null) { 251 return; 252 } 253 254 int signalStrength = featuresBundle.getInt( 255 BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL); 256 if (signalStrength != INVALID_SIGNAL) { 257 if (DEBUG) { 258 Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength); 259 } 260 261 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]); 262 } 263 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 264 if (DEBUG) { 265 Log.d(TAG, "Device disconnected"); 266 } 267 268 mNetworkSignalView.setVisibility(View.GONE); 269 mSignalsView.setVisibility(View.GONE); 270 } 271 } 272 } 273