• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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