blob: 80fec5b0c232b419c6cc974a6292cbb7d9433daa [file] [log] [blame]
/*
* 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<Callback> mCallbacks = new ArrayList<Callback>();
private final BluetoothAdapter mAdapter;
private final Receiver mReceiver = new Receiver();
private final ArrayMap<BluetoothDevice, DeviceInfo> 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<PairedDevice> getPairedDevices() {
final ArraySet<PairedDevice> 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<BluetoothDevice> 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
}
}