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 17package com.android.systemui.statusbar.policy; 18 19import static android.bluetooth.BluetoothAdapter.ERROR; 20import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString; 21import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString; 22import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString; 23import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString; 24import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile; 25import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString; 26import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString; 27 28import android.bluetooth.BluetoothAdapter; 29import android.bluetooth.BluetoothDevice; 30import android.bluetooth.BluetoothManager; 31import android.bluetooth.BluetoothProfile; 32import android.bluetooth.BluetoothProfile.ServiceListener; 33import android.content.BroadcastReceiver; 34import android.content.Context; 35import android.content.Intent; 36import android.content.IntentFilter; 37import android.os.ParcelUuid; 38import android.util.ArrayMap; 39import android.util.ArraySet; 40import android.util.Log; 41import android.util.SparseBooleanArray; 42 43import com.android.systemui.statusbar.policy.BluetoothUtil.Profile; 44 45import java.io.FileDescriptor; 46import java.io.PrintWriter; 47import java.util.ArrayList; 48import java.util.Set; 49 50public class BluetoothControllerImpl implements BluetoothController { 51 private static final String TAG = "BluetoothController"; 52 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 53 54 private final Context mContext; 55 private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); 56 private final BluetoothAdapter mAdapter; 57 private final Receiver mReceiver = new Receiver(); 58 private final ArrayMap<BluetoothDevice, DeviceInfo> mDeviceInfo = new ArrayMap<>(); 59 60 private boolean mEnabled; 61 private boolean mConnecting; 62 private BluetoothDevice mLastDevice; 63 64 public BluetoothControllerImpl(Context context) { 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; 72 } 73 74 mReceiver.register(); 75 setAdapterState(mAdapter.getState()); 76 updateBondedBluetoothDevices(); 77 } 78 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 101 public void addStateChangedCallback(Callback cb) { 102 mCallbacks.add(cb); 103 fireStateChange(cb); 104 } 105 106 @Override 107 public void removeStateChangedCallback(Callback cb) { 108 mCallbacks.remove(cb); 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 123 public boolean isBluetoothConnecting() { 124 return mAdapter != null 125 && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; 126 } 127 128 @Override 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 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 ParcelUuid[] uuids = device.getUuids(); 184 if (uuids == null) { 185 Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device)); 186 return; 187 } 188 final SparseBooleanArray profiles = new SparseBooleanArray(); 189 for (ParcelUuid uuid : uuids) { 190 final int profile = uuidToProfile(uuid); 191 if (profile == 0) { 192 Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: " 193 + uuidToString(uuid)); 194 continue; 195 } 196 final int profileState = mAdapter.getProfileConnectionState(profile); 197 if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile) 198 + " state = " + profileStateToString(profileState)); 199 final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED; 200 if (connect != connected) { 201 profiles.put(profile, true); 202 } 203 } 204 for (int i = 0; i < profiles.size(); i++) { 205 final int profile = profiles.keyAt(i); 206 mAdapter.getProfileProxy(mContext, new ServiceListener() { 207 @Override 208 public void onServiceConnected(int profile, BluetoothProfile proxy) { 209 if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile)); 210 final Profile p = BluetoothUtil.getProfile(proxy); 211 if (p == null) { 212 Log.w(TAG, "Unable get get Profile for " + profileToString(profile)); 213 } else { 214 final boolean ok = connect ? p.connect(device) : p.disconnect(device); 215 if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " " 216 + (ok ? "succeeded" : "failed")); 217 } 218 } 219 220 @Override 221 public void onServiceDisconnected(int profile) { 222 if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile)); 223 } 224 }, profile); 225 } 226 } 227 228 @Override 229 public String getLastDeviceName() { 230 return mLastDevice != null ? mLastDevice.getAliasName() : null; 231 } 232 233 private void updateBondedBluetoothDevices() { 234 if (mAdapter == null) return; 235 final Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); 236 for (DeviceInfo info : mDeviceInfo.values()) { 237 info.bonded = false; 238 } 239 int bondedCount = 0; 240 BluetoothDevice lastBonded = null; 241 if (bondedDevices != null) { 242 for (BluetoothDevice bondedDevice : bondedDevices) { 243 final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE; 244 updateInfo(bondedDevice).bonded = bonded; 245 if (bonded) { 246 bondedCount++; 247 lastBonded = bondedDevice; 248 } 249 } 250 } 251 if (mLastDevice == null && bondedCount == 1) { 252 mLastDevice = lastBonded; 253 } 254 firePairedDevicesChanged(); 255 } 256 257 private void firePairedDevicesChanged() { 258 for (Callback cb : mCallbacks) { 259 cb.onBluetoothPairedDevicesChanged(); 260 } 261 } 262 263 private void setAdapterState(int adapterState) { 264 final boolean enabled = adapterState == BluetoothAdapter.STATE_ON; 265 if (mEnabled == enabled) return; 266 mEnabled = enabled; 267 fireStateChange(); 268 } 269 270 private void setConnecting(boolean connecting) { 271 if (mConnecting == connecting) return; 272 mConnecting = connecting; 273 fireStateChange(); 274 } 275 276 private void fireStateChange() { 277 for (Callback cb : mCallbacks) { 278 fireStateChange(cb); 279 } 280 } 281 282 private void fireStateChange(Callback cb) { 283 cb.onBluetoothStateChange(mEnabled, mConnecting); 284 } 285 286 private final class Receiver extends BroadcastReceiver { 287 public void register() { 288 final IntentFilter filter = new IntentFilter(); 289 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 290 filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); 291 filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); 292 filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED); 293 mContext.registerReceiver(this, filter); 294 } 295 296 @Override 297 public void onReceive(Context context, Intent intent) { 298 final String action = intent.getAction(); 299 final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 300 if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 301 setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)); 302 if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled); 303 } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { 304 final DeviceInfo info = updateInfo(device); 305 final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, 306 ERROR); 307 if (state != ERROR) { 308 info.connectionState = state; 309 } 310 mLastDevice = device; 311 if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED " 312 + connectionStateToString(state) + " " + deviceToString(device)); 313 setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING); 314 } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) { 315 updateInfo(device); 316 mLastDevice = device; 317 } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { 318 if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device); 319 // we'll update all bonded devices below 320 } 321 updateBondedBluetoothDevices(); 322 } 323 } 324 325 private DeviceInfo updateInfo(BluetoothDevice device) { 326 DeviceInfo info = mDeviceInfo.get(device); 327 info = info != null ? info : new DeviceInfo(); 328 mDeviceInfo.put(device, info); 329 return info; 330 } 331 332 private static class DeviceInfo { 333 int connectionState = BluetoothAdapter.STATE_DISCONNECTED; 334 boolean bonded; // per getBondedDevices 335 } 336} 337