blob: 3288927bf73084aacdf99f39f1bab52792d300db [file] [log] [blame]
Brad Stenning8d1a51c2018-11-20 17:34:16 -08001/*
2 * Copyright (C) 2018 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
Anthony Chen1c59e9f2016-04-11 11:05:48 -070017package com.android.systemui.statusbar.car;
18
Gus Prevasab336792018-11-14 13:52:20 -050019import static com.android.systemui.statusbar.phone.StatusBar.DEBUG;
20
Anthony Chen1c59e9f2016-04-11 11:05:48 -070021import android.bluetooth.BluetoothAdapter;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.BluetoothHeadsetClient;
24import android.bluetooth.BluetoothProfile;
25import android.bluetooth.BluetoothProfile.ServiceListener;
26import android.content.BroadcastReceiver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070030import android.os.Bundle;
Jason Monk48edc0c2017-04-10 15:01:27 -040031import android.telephony.SignalStrength;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070032import android.util.Log;
33import android.util.TypedValue;
34import android.view.View;
35import android.widget.ImageView;
Gus Prevasab336792018-11-14 13:52:20 -050036
Evan Laird06e9fd82018-02-10 09:36:55 -080037import com.android.settingslib.graph.SignalDrawable;
Jason Monk9c7844c2017-01-18 15:21:53 -050038import com.android.systemui.Dependency;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070039import com.android.systemui.R;
40import com.android.systemui.statusbar.ScalingDrawableWrapper;
Anthony Chen13e1b672016-09-26 11:01:05 -070041import com.android.systemui.statusbar.policy.BluetoothController;
42
Anthony Chen1c59e9f2016-04-11 11:05:48 -070043/**
44 * Controller that monitors signal strength for a device that is connected via bluetooth.
45 */
Anthony Chen13e1b672016-09-26 11:01:05 -070046public class ConnectedDeviceSignalController extends BroadcastReceiver implements
47 BluetoothController.Callback {
Brad Stenning8d1a51c2018-11-20 17:34:16 -080048 private static final String TAG = "DeviceSignalCtlr";
Anthony Chen1c59e9f2016-04-11 11:05:48 -070049
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 = {
Jason Monk48edc0c2017-04-10 15:01:27 -040068 0,
69 0,
70 1,
71 2,
72 3,
73 4,
Anthony Chen1c59e9f2016-04-11 11:05:48 -070074 };
75
76 private static final int INVALID_SIGNAL = -1;
77
78 private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
79 private final Context mContext;
Anthony Chen13e1b672016-09-26 11:01:05 -070080 private final BluetoothController mController;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070081
Anthony Chen13e1b672016-09-26 11:01:05 -070082 private final View mSignalsView;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070083 private final ImageView mNetworkSignalView;
84
85 private final float mIconScaleFactor;
Jason Monk48edc0c2017-04-10 15:01:27 -040086 private final SignalDrawable mSignalDrawable;
Anthony Chen1c59e9f2016-04-11 11:05:48 -070087
88 private BluetoothHeadsetClient mBluetoothHeadsetClient;
Brad Stenning8d1a51c2018-11-20 17:34:16 -080089 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 };
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700104
Jason Monk9c7844c2017-01-18 15:21:53 -0500105 public ConnectedDeviceSignalController(Context context, View signalsView) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700106 mContext = context;
Jason Monk9c7844c2017-01-18 15:21:53 -0500107 mController = Dependency.get(BluetoothController.class);
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700108
Anthony Chen13e1b672016-09-26 11:01:05 -0700109 mSignalsView = signalsView;
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700110 mNetworkSignalView = (ImageView)
111 mSignalsView.findViewById(R.id.connected_device_network_signal);
112
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700113 TypedValue typedValue = new TypedValue();
114 context.getResources().getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
115 mIconScaleFactor = typedValue.getFloat();
Jason Monk48edc0c2017-04-10 15:01:27 -0400116 mSignalDrawable = new SignalDrawable(mNetworkSignalView.getContext());
117 mNetworkSignalView.setImageDrawable(
118 new ScalingDrawableWrapper(mSignalDrawable, mIconScaleFactor));
Sam Hurstb3e73862016-05-31 10:35:17 -0700119
120 if (mAdapter == null) {
Brad Stenning8d1a51c2018-11-20 17:34:16 -0800121 return;
Sam Hurstb3e73862016-05-31 10:35:17 -0700122 }
123
124 mAdapter.getProfileProxy(context.getApplicationContext(), mHfpServiceListener,
125 BluetoothProfile.HEADSET_CLIENT);
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700126 }
127
128 public void startListening() {
129 IntentFilter filter = new IntentFilter();
130 filter.addAction(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
131 filter.addAction(BluetoothHeadsetClient.ACTION_AG_EVENT);
132 mContext.registerReceiver(this, filter);
Anthony Chen13e1b672016-09-26 11:01:05 -0700133
Jason Monk88529052016-11-04 13:29:58 -0400134 mController.addCallback(this);
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700135 }
136
137 public void stopListening() {
138 mContext.unregisterReceiver(this);
Jason Monk88529052016-11-04 13:29:58 -0400139 mController.removeCallback(this);
Anthony Chen13e1b672016-09-26 11:01:05 -0700140 }
141
142 @Override
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
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 }
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700161 }
162
163 @Override
164 public void onReceive(Context context, Intent intent) {
165 String action = intent.getAction();
166
Anthony Chen13e1b672016-09-26 11:01:05 -0700167 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700168 Log.d(TAG, "onReceive(). action: " + action);
169 }
170
171 if (BluetoothHeadsetClient.ACTION_AG_EVENT.equals(action)) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700172 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700173 Log.d(TAG, "Received ACTION_AG_EVENT");
174 }
175
176 processActionAgEvent(intent);
177 } else if (BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
178 int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
179
Anthony Chen13e1b672016-09-26 11:01:05 -0700180 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700181 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 =
Anthony Chen13e1b672016-09-26 11:01:05 -0700186 (BluetoothDevice) intent.getExtra(BluetoothDevice.EXTRA_DEVICE);
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700187 updateViewVisibility(device, newState);
188 }
189 }
190
191 /**
192 * Processes an {@link Intent} that had an action of
193 * {@link BluetoothHeadsetClient#ACTION_AG_EVENT}.
194 */
195 private void processActionAgEvent(Intent intent) {
196 int networkStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_STATUS,
197 INVALID_SIGNAL);
198 if (networkStatus != INVALID_SIGNAL) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700199 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700200 Log.d(TAG, "EXTRA_NETWORK_STATUS: " + " " + networkStatus);
201 }
202
203 if (networkStatus == NETWORK_UNAVAILABLE) {
204 setNetworkSignalIcon(NETWORK_UNAVAILABLE_ICON_ID);
205 }
206 }
207
208 int signalStrength = intent.getIntExtra(
209 BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
210 if (signalStrength != INVALID_SIGNAL) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700211 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700212 Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
213 }
214
215 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
216 }
217
218 int roamingStatus = intent.getIntExtra(BluetoothHeadsetClient.EXTRA_NETWORK_ROAMING,
219 INVALID_SIGNAL);
220 if (roamingStatus != INVALID_SIGNAL) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700221 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700222 Log.d(TAG, "EXTRA_NETWORK_ROAMING: " + roamingStatus);
223 }
224 }
225 }
226
Jason Monk48edc0c2017-04-10 15:01:27 -0400227 private void setNetworkSignalIcon(int level) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700228 // Setting the icon on a child view of mSignalView, so toggle this container visible.
229 mSignalsView.setVisibility(View.VISIBLE);
230
Jason Monk48edc0c2017-04-10 15:01:27 -0400231 mSignalDrawable.setLevel(SignalDrawable.getState(level,
232 SignalStrength.NUM_SIGNAL_STRENGTH_BINS, false));
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700233 mNetworkSignalView.setVisibility(View.VISIBLE);
234 }
235
236 private void updateViewVisibility(BluetoothDevice device, int newState) {
237 if (newState == BluetoothProfile.STATE_CONNECTED) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700238 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700239 Log.d(TAG, "Device connected");
240 }
241
242 if (mBluetoothHeadsetClient == null || device == null) {
243 return;
244 }
245
246 // Check if battery information is available and immediately update.
247 Bundle featuresBundle = mBluetoothHeadsetClient.getCurrentAgEvents(device);
248 if (featuresBundle == null) {
249 return;
250 }
251
252 int signalStrength = featuresBundle.getInt(
253 BluetoothHeadsetClient.EXTRA_NETWORK_SIGNAL_STRENGTH, INVALID_SIGNAL);
254 if (signalStrength != INVALID_SIGNAL) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700255 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700256 Log.d(TAG, "EXTRA_NETWORK_SIGNAL_STRENGTH: " + signalStrength);
257 }
258
259 setNetworkSignalIcon(SIGNAL_STRENGTH_ICONS[signalStrength]);
260 }
261 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Anthony Chen13e1b672016-09-26 11:01:05 -0700262 if (DEBUG) {
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700263 Log.d(TAG, "Device disconnected");
264 }
265
266 mNetworkSignalView.setVisibility(View.GONE);
267 mSignalsView.setVisibility(View.GONE);
268 }
269 }
Anthony Chen1c59e9f2016-04-11 11:05:48 -0700270}