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.CentralSurfaces.DEBUG; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothHeadsetClient; 24 import android.bluetooth.BluetoothHeadsetClient.NetworkServiceState; 25 import android.bluetooth.BluetoothProfile; 26 import android.bluetooth.BluetoothProfile.ServiceListener; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 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 final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); 77 private final Context mContext; 78 private final BluetoothController mController; 79 80 private final View mSignalsView; 81 private final ImageView mNetworkSignalView; 82 83 private final float mIconScaleFactor; 84 private final SignalDrawable mSignalDrawable; 85 86 private BluetoothHeadsetClient mBluetoothHeadsetClient; 87 private final ServiceListener mHfpServiceListener = new ServiceListener() { 88 @Override 89 public void onServiceConnected(int profile, BluetoothProfile proxy) { 90 if (profile == BluetoothProfile.HEADSET_CLIENT) { 91 mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy; 92 } 93 } 94 95 @Override 96 public void onServiceDisconnected(int profile) { 97 if (profile == BluetoothProfile.HEADSET_CLIENT) { 98 mBluetoothHeadsetClient = null; 99 } 100 } 101 }; 102 ConnectedDeviceSignalController(Context context, View signalsView)103 public ConnectedDeviceSignalController(Context context, View signalsView) { 104 mContext = context; 105 mController = Dependency.get(BluetoothController.class); 106 107 mSignalsView = signalsView; 108 mNetworkSignalView = (ImageView) 109 mSignalsView.findViewById(R.id.connected_device_network_signal); 110 111 TypedValue typedValue = new TypedValue(); 112 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true); 113 mIconScaleFactor = typedValue.getFloat(); 114 mSignalDrawable = new SignalDrawable(mNetworkSignalView.getContext()); 115 mNetworkSignalView.setImageDrawable( 116 new ScalingDrawableWrapper(mSignalDrawable, mIconScaleFactor)); 117 118 if (mAdapter == null) { 119 return; 120 } 121 122 mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener, 123 BluetoothProfile.HEADSET_CLIENT); 124 } 125 126 /** Starts listening for bluetooth broadcast messages. */ startListening()127 public void startListening() { 128 IntentFilter filter = new IntentFilter(); 129 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED); 130 filter.addAction(BluetoothHeadsetClient.ACTION_NETWORK_SERVICE_STATE_CHANGED); 131 mContext.registerReceiver(this, filter); 132 133 mController.addCallback(this); 134 } 135 136 /** Stops listening for bluetooth broadcast messages. */ stopListening()137 public void stopListening() { 138 mContext.unregisterReceiver(this); 139 mController.removeCallback(this); 140 } 141 142 @Override onBluetoothDevicesChanged()143 public void onBluetoothDevicesChanged() { 144 // Nothing to do here because this Controller is not displaying a list of possible 145 // bluetooth devices. 146 } 147 148 @Override onBluetoothStateChange(boolean enabled)149 public void onBluetoothStateChange(boolean enabled) { 150 if (DEBUG) { 151 Log.d(TAG, "onBluetoothStateChange(). enabled: " + enabled); 152 } 153 154 // Only need to handle the case if bluetooth has been disabled, in which case the 155 // signal indicators are hidden. If bluetooth has been enabled, then this class should 156 // receive updates to the connection state via onReceive(). 157 if (!enabled) { 158 mNetworkSignalView.setVisibility(View.GONE); 159 mSignalsView.setVisibility(View.GONE); 160 } 161 } 162 163 @Override onReceive(Context context, Intent intent)164 public void onReceive(Context context, Intent intent) { 165 String action = intent.getAction(); 166 167 if (DEBUG) { 168 Log.d(TAG, "onReceive(). action: " + action); 169 } 170 171 if (BluetoothHeadsetClient.ACTION_NETWORK_SERVICE_STATE_CHANGED.equals(action)) { 172 if (DEBUG) { 173 Log.d(TAG, "Received ACTION_NETWORK_SERVICE_STATE_CHANGED"); 174 } 175 176 processActionNetworkServiceStateEvent(intent); 177 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) { 178 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 179 180 if (DEBUG) { 181 int oldState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 182 Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED event: " 183 + oldState + " -> " + newState); 184 } 185 BluetoothDevice device = 186 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE); 187 updateViewVisibility(device, newState); 188 } 189 } 190 191 /** 192 * Processes an {@link Intent} that had an action of 193 * {@link BluetoothHeadsetClient#ACTION_NETWORK_SERVICE_STATE_CHANGED}. 194 */ processActionNetworkServiceStateEvent(Intent intent)195 private void processActionNetworkServiceStateEvent(Intent intent) { 196 NetworkServiceState state = (NetworkServiceState) intent.getExtra( 197 BluetoothHeadsetClient.EXTRA_NETWORK_SERVICE_STATE, null); 198 199 if (!state.isServiceAvailable()) { 200 setNetworkSignalIcon(NETWORK_UNAVAILABLE_ICON_ID); 201 } 202 203 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[state.getSignalStrength()]); 204 } 205 setNetworkSignalIcon(int level)206 private void setNetworkSignalIcon(int level) { 207 // Setting the icon on a child view of mSignalView, so toggle this container visible. 208 mSignalsView.setVisibility(View.VISIBLE); 209 210 mSignalDrawable.setLevel(SignalDrawable.getState(level, 211 SignalStrength.NUM_SIGNAL_STRENGTH_BINS, false)); 212 mNetworkSignalView.setVisibility(View.VISIBLE); 213 } 214 updateViewVisibility(BluetoothDevice device, int newState)215 private void updateViewVisibility(BluetoothDevice device, int newState) { 216 if (newState == BluetoothProfile.STATE_CONNECTED) { 217 if (DEBUG) { 218 Log.d(TAG, "Device connected"); 219 } 220 221 if (mBluetoothHeadsetClient == null || device == null) { 222 return; 223 } 224 225 // Check if network signal strength information is available and immediately update. 226 NetworkServiceState state = mBluetoothHeadsetClient.getNetworkServiceState(device); 227 if (state == null) { 228 return; 229 } 230 231 int signalStrength = state.getSignalStrength(); 232 if (DEBUG) { 233 Log.d(TAG, "NetworkServiceState getSignalStrength(): " + signalStrength); 234 } 235 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]); 236 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 237 if (DEBUG) { 238 Log.d(TAG, "Device disconnected"); 239 } 240 241 mNetworkSignalView.setVisibility(View.GONE); 242 mSignalsView.setVisibility(View.GONE); 243 } 244 } 245 } 246