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