John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2008 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.statusbar.policy; |
| 18 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 19 | import static android.bluetooth.BluetoothAdapter.ERROR; |
| 20 | import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString; |
| 21 | import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString; |
| 22 | import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString; |
| 23 | import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString; |
| 24 | import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile; |
| 25 | import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString; |
| 26 | import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString; |
| 27 | |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 28 | import android.bluetooth.BluetoothAdapter; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 29 | import android.bluetooth.BluetoothDevice; |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 30 | import android.bluetooth.BluetoothManager; |
| 31 | import android.bluetooth.BluetoothProfile; |
| 32 | import android.bluetooth.BluetoothProfile.ServiceListener; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 33 | import android.content.BroadcastReceiver; |
| 34 | import android.content.Context; |
| 35 | import android.content.Intent; |
| 36 | import android.content.IntentFilter; |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 37 | import android.os.ParcelUuid; |
| 38 | import android.util.ArrayMap; |
| 39 | import android.util.ArraySet; |
| 40 | import android.util.Log; |
| 41 | import android.util.SparseBooleanArray; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 42 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 43 | import com.android.systemui.statusbar.policy.BluetoothUtil.Profile; |
| 44 | |
| 45 | import java.io.FileDescriptor; |
| 46 | import java.io.PrintWriter; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 47 | import java.util.ArrayList; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 48 | import java.util.Set; |
| 49 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 50 | public class BluetoothControllerImpl implements BluetoothController { |
| 51 | private static final String TAG = "BluetoothController"; |
| 52 | private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 53 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 54 | private final Context mContext; |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 55 | private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 56 | private final BluetoothAdapter mAdapter; |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 57 | private final Receiver mReceiver = new Receiver(); |
| 58 | private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>(); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 59 | |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 60 | private boolean mEnabled; |
| 61 | private boolean mConnecting; |
| 62 | private BluetoothDevice mLastDevice; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 63 | |
| 64 | public BluetoothControllerImpl(Context context) { |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 65 | mContext = context; |
| 66 | final BluetoothManager bluetoothManager = |
| 67 | (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); |
| 68 | mAdapter = bluetoothManager.getAdapter(); |
| 69 | if (mAdapter == null) { |
| 70 | Log.w(TAG, "Default BT adapter not found"); |
| 71 | return; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 72 | } |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 73 | |
| 74 | mReceiver.register(); |
| 75 | setAdapterState(mAdapter.getState()); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 76 | updateBondedBluetoothDevices(); |
| 77 | } |
| 78 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 79 | public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| 80 | pw.println("BluetoothController state:"); |
| 81 | pw.print(" mAdapter="); pw.println(mAdapter); |
| 82 | pw.print(" mEnabled="); pw.println(mEnabled); |
| 83 | pw.print(" mConnecting="); pw.println(mConnecting); |
| 84 | pw.print(" mLastDevice="); pw.println(mLastDevice); |
| 85 | pw.print(" mCallbacks.size="); pw.println(mCallbacks.size()); |
| 86 | pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size()); |
| 87 | for (int i = 0; i < mDeviceInfo.size(); i++) { |
| 88 | final BluetoothDevice device = mDeviceInfo.keyAt(i); |
| 89 | final DeviceInfo info = mDeviceInfo.valueAt(i); |
| 90 | pw.print(" "); pw.print(deviceToString(device)); |
| 91 | pw.print('('); pw.print(uuidsToString(device)); pw.print(')'); |
| 92 | pw.print(" "); pw.println(infoToString(info)); |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | private static String infoToString(DeviceInfo info) { |
| 97 | return info == null ? null : ("connectionState=" + |
| 98 | connectionStateToString(info.connectionState) + ",bonded=" + info.bonded); |
| 99 | } |
| 100 | |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 101 | public void addStateChangedCallback(Callback cb) { |
| 102 | mCallbacks.add(cb); |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 103 | fireStateChange(cb); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | @Override |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 107 | public void removeStateChangedCallback(Callback cb) { |
| 108 | mCallbacks.remove(cb); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 109 | } |
| 110 | |
| 111 | @Override |
| 112 | public boolean isBluetoothEnabled() { |
| 113 | return mAdapter != null && mAdapter.isEnabled(); |
| 114 | } |
| 115 | |
| 116 | @Override |
| 117 | public boolean isBluetoothConnected() { |
| 118 | return mAdapter != null |
| 119 | && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED; |
| 120 | } |
| 121 | |
| 122 | @Override |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 123 | public boolean isBluetoothConnecting() { |
| 124 | return mAdapter != null |
| 125 | && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; |
| 126 | } |
| 127 | |
| 128 | @Override |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 129 | public void setBluetoothEnabled(boolean enabled) { |
| 130 | if (mAdapter != null) { |
| 131 | if (enabled) { |
| 132 | mAdapter.enable(); |
| 133 | } else { |
| 134 | mAdapter.disable(); |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | @Override |
| 140 | public boolean isBluetoothSupported() { |
| 141 | return mAdapter != null; |
| 142 | } |
| 143 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 144 | @Override |
| 145 | public ArraySet<PairedDevice> getPairedDevices() { |
| 146 | final ArraySet<PairedDevice> rt = new ArraySet<>(); |
| 147 | for (int i = 0; i < mDeviceInfo.size(); i++) { |
| 148 | final BluetoothDevice device = mDeviceInfo.keyAt(i); |
| 149 | final DeviceInfo info = mDeviceInfo.valueAt(i); |
| 150 | if (!info.bonded) continue; |
| 151 | final PairedDevice paired = new PairedDevice(); |
| 152 | paired.id = device.getAddress(); |
| 153 | paired.tag = device; |
| 154 | paired.name = device.getAliasName(); |
| 155 | paired.state = connectionStateToPairedDeviceState(info.connectionState); |
| 156 | rt.add(paired); |
| 157 | } |
| 158 | return rt; |
| 159 | } |
| 160 | |
| 161 | private static int connectionStateToPairedDeviceState(int state) { |
| 162 | if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED; |
| 163 | if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING; |
| 164 | if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING; |
| 165 | return PairedDevice.STATE_DISCONNECTED; |
| 166 | } |
| 167 | |
| 168 | @Override |
| 169 | public void connect(final PairedDevice pd) { |
| 170 | connect(pd, true); |
| 171 | } |
| 172 | |
| 173 | @Override |
| 174 | public void disconnect(PairedDevice pd) { |
| 175 | connect(pd, false); |
| 176 | } |
| 177 | |
| 178 | private void connect(PairedDevice pd, final boolean connect) { |
| 179 | if (mAdapter == null || pd == null || pd.tag == null) return; |
| 180 | final BluetoothDevice device = (BluetoothDevice) pd.tag; |
| 181 | final String action = connect ? "connect" : "disconnect"; |
| 182 | if (DEBUG) Log.d(TAG, action + " " + deviceToString(device)); |
| 183 | final SparseBooleanArray profiles = new SparseBooleanArray(); |
| 184 | for (ParcelUuid uuid : device.getUuids()) { |
| 185 | final int profile = uuidToProfile(uuid); |
| 186 | if (profile == 0) { |
| 187 | Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: " |
| 188 | + uuidToString(uuid)); |
| 189 | continue; |
| 190 | } |
| 191 | final int profileState = mAdapter.getProfileConnectionState(profile); |
| 192 | if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile) |
| 193 | + " state = " + profileStateToString(profileState)); |
| 194 | final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED; |
| 195 | if (connect != connected) { |
| 196 | profiles.put(profile, true); |
| 197 | } |
| 198 | } |
| 199 | for (int i = 0; i < profiles.size(); i++) { |
| 200 | final int profile = profiles.keyAt(i); |
| 201 | mAdapter.getProfileProxy(mContext, new ServiceListener() { |
| 202 | @Override |
| 203 | public void onServiceConnected(int profile, BluetoothProfile proxy) { |
| 204 | if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile)); |
| 205 | final Profile p = BluetoothUtil.getProfile(proxy); |
| 206 | if (p == null) { |
| 207 | Log.w(TAG, "Unable get get Profile for " + profileToString(profile)); |
| 208 | } else { |
| 209 | final boolean ok = connect ? p.connect(device) : p.disconnect(device); |
| 210 | if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " " |
| 211 | + (ok ? "succeeded" : "failed")); |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | @Override |
| 216 | public void onServiceDisconnected(int profile) { |
| 217 | if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile)); |
| 218 | } |
| 219 | }, profile); |
| 220 | } |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 221 | } |
| 222 | |
| 223 | @Override |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 224 | public String getLastDeviceName() { |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 225 | return mLastDevice != null ? mLastDevice.getAliasName() : null; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | private void updateBondedBluetoothDevices() { |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 229 | if (mAdapter == null) return; |
| 230 | final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); |
| 231 | for (DeviceInfo info : mDeviceInfo.values()) { |
| 232 | info.bonded = false; |
| 233 | } |
| 234 | int bondedCount = 0; |
| 235 | BluetoothDevice lastBonded = null; |
| 236 | if (bondedDevices != null) { |
| 237 | for (BluetoothDevice bondedDevice : bondedDevices) { |
| 238 | final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE; |
| 239 | updateInfo(bondedDevice).bonded = bonded; |
| 240 | if (bonded) { |
| 241 | bondedCount++; |
| 242 | lastBonded = bondedDevice; |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 243 | } |
| 244 | } |
| 245 | } |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 246 | if (mLastDevice == null && bondedCount == 1) { |
| 247 | mLastDevice = lastBonded; |
| 248 | } |
| 249 | firePairedDevicesChanged(); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 250 | } |
| 251 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 252 | private void firePairedDevicesChanged() { |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 253 | for (Callback cb : mCallbacks) { |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 254 | cb.onBluetoothPairedDevicesChanged(); |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 255 | } |
| 256 | } |
John Spurlock | ccb6b9a | 2014-05-17 15:54:40 -0400 | [diff] [blame] | 257 | |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 258 | private void setAdapterState(int adapterState) { |
| 259 | final boolean enabled = adapterState == BluetoothAdapter.STATE_ON; |
| 260 | if (mEnabled == enabled) return; |
| 261 | mEnabled = enabled; |
| 262 | fireStateChange(); |
| 263 | } |
| 264 | |
| 265 | private void setConnecting(boolean connecting) { |
| 266 | if (mConnecting == connecting) return; |
| 267 | mConnecting = connecting; |
| 268 | fireStateChange(); |
| 269 | } |
| 270 | |
| 271 | private void fireStateChange() { |
| 272 | for (Callback cb : mCallbacks) { |
| 273 | fireStateChange(cb); |
| 274 | } |
| 275 | } |
| 276 | |
| 277 | private void fireStateChange(Callback cb) { |
John Spurlock | d1c86e2 | 2014-06-01 00:04:53 -0400 | [diff] [blame] | 278 | cb.onBluetoothStateChange(mEnabled, mConnecting); |
John Spurlock | ccb6b9a | 2014-05-17 15:54:40 -0400 | [diff] [blame] | 279 | } |
John Spurlock | 486b78e | 2014-07-07 08:37:56 -0400 | [diff] [blame^] | 280 | |
| 281 | private final class Receiver extends BroadcastReceiver { |
| 282 | public void register() { |
| 283 | final IntentFilter filter = new IntentFilter(); |
| 284 | filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); |
| 285 | filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); |
| 286 | filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); |
| 287 | filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED); |
| 288 | mContext.registerReceiver(this, filter); |
| 289 | } |
| 290 | |
| 291 | @Override |
| 292 | public void onReceive(Context context, Intent intent) { |
| 293 | final String action = intent.getAction(); |
| 294 | final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); |
| 295 | if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { |
| 296 | setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)); |
| 297 | if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled); |
| 298 | } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { |
| 299 | final DeviceInfo info = updateInfo(device); |
| 300 | final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, |
| 301 | ERROR); |
| 302 | if (state != ERROR) { |
| 303 | info.connectionState = state; |
| 304 | } |
| 305 | mLastDevice = device; |
| 306 | if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED " |
| 307 | + connectionStateToString(state) + " " + deviceToString(device)); |
| 308 | setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING); |
| 309 | } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) { |
| 310 | updateInfo(device); |
| 311 | mLastDevice = device; |
| 312 | } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { |
| 313 | if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device); |
| 314 | // we'll update all bonded devices below |
| 315 | } |
| 316 | updateBondedBluetoothDevices(); |
| 317 | } |
| 318 | } |
| 319 | |
| 320 | private DeviceInfo updateInfo(BluetoothDevice device) { |
| 321 | DeviceInfo info = mDeviceInfo.get(device); |
| 322 | info = info != null ? info : new DeviceInfo(); |
| 323 | mDeviceInfo.put(device, info); |
| 324 | return info; |
| 325 | } |
| 326 | |
| 327 | private static class DeviceInfo { |
| 328 | int connectionState = BluetoothAdapter.STATE_DISCONNECTED; |
| 329 | boolean bonded; // per getBondedDevices |
| 330 | } |
John Spurlock | af8d6c4 | 2014-05-07 17:49:08 -0400 | [diff] [blame] | 331 | } |