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