/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.statusbar.policy; import static android.bluetooth.BluetoothAdapter.ERROR; import static com.android.systemui.statusbar.policy.BluetoothUtil.connectionStateToString; import static com.android.systemui.statusbar.policy.BluetoothUtil.deviceToString; import static com.android.systemui.statusbar.policy.BluetoothUtil.profileStateToString; import static com.android.systemui.statusbar.policy.BluetoothUtil.profileToString; import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToProfile; import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidToString; import static com.android.systemui.statusbar.policy.BluetoothUtil.uuidsToString; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.ParcelUuid; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseBooleanArray; import com.android.systemui.statusbar.policy.BluetoothUtil.Profile; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Set; public class BluetoothControllerImpl implements BluetoothController { private static final String TAG = "BluetoothController"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final ArrayList mCallbacks = new ArrayList(); private final BluetoothAdapter mAdapter; private final Receiver mReceiver = new Receiver(); private final ArrayMap mDeviceInfo = new ArrayMap<>(); private boolean mEnabled; private boolean mConnecting; private BluetoothDevice mLastDevice; public BluetoothControllerImpl(Context context) { mContext = context; final BluetoothManager bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); mAdapter = bluetoothManager.getAdapter(); if (mAdapter == null) { Log.w(TAG, "Default BT adapter not found"); return; } mReceiver.register(); setAdapterState(mAdapter.getState()); updateBondedBluetoothDevices(); } public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("BluetoothController state:"); pw.print(" mAdapter="); pw.println(mAdapter); pw.print(" mEnabled="); pw.println(mEnabled); pw.print(" mConnecting="); pw.println(mConnecting); pw.print(" mLastDevice="); pw.println(mLastDevice); pw.print(" mCallbacks.size="); pw.println(mCallbacks.size()); pw.print(" mDeviceInfo.size="); pw.println(mDeviceInfo.size()); for (int i = 0; i < mDeviceInfo.size(); i++) { final BluetoothDevice device = mDeviceInfo.keyAt(i); final DeviceInfo info = mDeviceInfo.valueAt(i); pw.print(" "); pw.print(deviceToString(device)); pw.print('('); pw.print(uuidsToString(device)); pw.print(')'); pw.print(" "); pw.println(infoToString(info)); } } private static String infoToString(DeviceInfo info) { return info == null ? null : ("connectionState=" + connectionStateToString(info.connectionState) + ",bonded=" + info.bonded); } public void addStateChangedCallback(Callback cb) { mCallbacks.add(cb); fireStateChange(cb); } @Override public void removeStateChangedCallback(Callback cb) { mCallbacks.remove(cb); } @Override public boolean isBluetoothEnabled() { return mAdapter != null && mAdapter.isEnabled(); } @Override public boolean isBluetoothConnected() { return mAdapter != null && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTED; } @Override public boolean isBluetoothConnecting() { return mAdapter != null && mAdapter.getConnectionState() == BluetoothAdapter.STATE_CONNECTING; } @Override public void setBluetoothEnabled(boolean enabled) { if (mAdapter != null) { if (enabled) { mAdapter.enable(); } else { mAdapter.disable(); } } } @Override public boolean isBluetoothSupported() { return mAdapter != null; } @Override public ArraySet getPairedDevices() { final ArraySet rt = new ArraySet<>(); for (int i = 0; i < mDeviceInfo.size(); i++) { final BluetoothDevice device = mDeviceInfo.keyAt(i); final DeviceInfo info = mDeviceInfo.valueAt(i); if (!info.bonded) continue; final PairedDevice paired = new PairedDevice(); paired.id = device.getAddress(); paired.tag = device; paired.name = device.getAliasName(); paired.state = connectionStateToPairedDeviceState(info.connectionState); rt.add(paired); } return rt; } private static int connectionStateToPairedDeviceState(int state) { if (state == BluetoothAdapter.STATE_CONNECTED) return PairedDevice.STATE_CONNECTED; if (state == BluetoothAdapter.STATE_CONNECTING) return PairedDevice.STATE_CONNECTING; if (state == BluetoothAdapter.STATE_DISCONNECTING) return PairedDevice.STATE_DISCONNECTING; return PairedDevice.STATE_DISCONNECTED; } @Override public void connect(final PairedDevice pd) { connect(pd, true); } @Override public void disconnect(PairedDevice pd) { connect(pd, false); } private void connect(PairedDevice pd, final boolean connect) { if (mAdapter == null || pd == null || pd.tag == null) return; final BluetoothDevice device = (BluetoothDevice) pd.tag; final String action = connect ? "connect" : "disconnect"; if (DEBUG) Log.d(TAG, action + " " + deviceToString(device)); final ParcelUuid[] uuids = device.getUuids(); if (uuids == null) { Log.w(TAG, "No uuids returned, aborting " + action + " for " + deviceToString(device)); return; } final SparseBooleanArray profiles = new SparseBooleanArray(); for (ParcelUuid uuid : uuids) { final int profile = uuidToProfile(uuid); if (profile == 0) { Log.w(TAG, "Device " + deviceToString(device) + " has an unsupported uuid: " + uuidToString(uuid)); continue; } final int profileState = mAdapter.getProfileConnectionState(profile); if (DEBUG && !profiles.get(profile)) Log.d(TAG, "Profile " + profileToString(profile) + " state = " + profileStateToString(profileState)); final boolean connected = profileState == BluetoothProfile.STATE_CONNECTED; if (connect != connected) { profiles.put(profile, true); } } for (int i = 0; i < profiles.size(); i++) { final int profile = profiles.keyAt(i); mAdapter.getProfileProxy(mContext, new ServiceListener() { @Override public void onServiceConnected(int profile, BluetoothProfile proxy) { if (DEBUG) Log.d(TAG, "onServiceConnected " + profileToString(profile)); final Profile p = BluetoothUtil.getProfile(proxy); if (p == null) { Log.w(TAG, "Unable get get Profile for " + profileToString(profile)); } else { final boolean ok = connect ? p.connect(device) : p.disconnect(device); if (DEBUG) Log.d(TAG, action + " " + profileToString(profile) + " " + (ok ? "succeeded" : "failed")); } } @Override public void onServiceDisconnected(int profile) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + profileToString(profile)); } }, profile); } } @Override public String getLastDeviceName() { return mLastDevice != null ? mLastDevice.getAliasName() : null; } private void updateBondedBluetoothDevices() { if (mAdapter == null) return; final Set bondedDevices = mAdapter.getBondedDevices(); for (DeviceInfo info : mDeviceInfo.values()) { info.bonded = false; } int bondedCount = 0; BluetoothDevice lastBonded = null; if (bondedDevices != null) { for (BluetoothDevice bondedDevice : bondedDevices) { final boolean bonded = bondedDevice.getBondState() != BluetoothDevice.BOND_NONE; updateInfo(bondedDevice).bonded = bonded; if (bonded) { bondedCount++; lastBonded = bondedDevice; } } } if (mLastDevice == null && bondedCount == 1) { mLastDevice = lastBonded; } firePairedDevicesChanged(); } private void firePairedDevicesChanged() { for (Callback cb : mCallbacks) { cb.onBluetoothPairedDevicesChanged(); } } private void setAdapterState(int adapterState) { final boolean enabled = adapterState == BluetoothAdapter.STATE_ON; if (mEnabled == enabled) return; mEnabled = enabled; fireStateChange(); } private void setConnecting(boolean connecting) { if (mConnecting == connecting) return; mConnecting = connecting; fireStateChange(); } private void fireStateChange() { for (Callback cb : mCallbacks) { fireStateChange(cb); } } private void fireStateChange(Callback cb) { cb.onBluetoothStateChange(mEnabled, mConnecting); } private final class Receiver extends BroadcastReceiver { public void register() { final IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); filter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); filter.addAction(BluetoothDevice.ACTION_ALIAS_CHANGED); mContext.registerReceiver(this, filter); } @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { setAdapterState(intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, ERROR)); if (DEBUG) Log.d(TAG, "ACTION_STATE_CHANGED " + mEnabled); } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { final DeviceInfo info = updateInfo(device); final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, ERROR); if (state != ERROR) { info.connectionState = state; } mLastDevice = device; if (DEBUG) Log.d(TAG, "ACTION_CONNECTION_STATE_CHANGED " + connectionStateToString(state) + " " + deviceToString(device)); setConnecting(info.connectionState == BluetoothAdapter.STATE_CONNECTING); } else if (action.equals(BluetoothDevice.ACTION_ALIAS_CHANGED)) { updateInfo(device); mLastDevice = device; } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { if (DEBUG) Log.d(TAG, "ACTION_BOND_STATE_CHANGED " + device); // we'll update all bonded devices below } updateBondedBluetoothDevices(); } } private DeviceInfo updateInfo(BluetoothDevice device) { DeviceInfo info = mDeviceInfo.get(device); info = info != null ? info : new DeviceInfo(); mDeviceInfo.put(device, info); return info; } private static class DeviceInfo { int connectionState = BluetoothAdapter.STATE_DISCONNECTED; boolean bonded; // per getBondedDevices } }