3661 lines
140 KiB
Java
3661 lines
140 KiB
Java
|
/*
|
||
|
* Copyright (C) 2008 The Android Open Source Project
|
||
|
* Copyright (C) 2010, 2011 The Linux Foundation. All rights reserved.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* TODO: Move this to
|
||
|
* java/services/com/android/server/BluetoothService.java
|
||
|
* and make the contructor package private again.
|
||
|
*
|
||
|
* @hide
|
||
|
*/
|
||
|
|
||
|
package android.server;
|
||
|
|
||
|
import android.bluetooth.BluetoothA2dp;
|
||
|
import android.bluetooth.BluetoothAdapter;
|
||
|
import android.bluetooth.BluetoothClass;
|
||
|
import android.bluetooth.BluetoothDevice;
|
||
|
import android.bluetooth.BluetoothDeviceProfileState;
|
||
|
import android.bluetooth.BluetoothHeadset;
|
||
|
import android.bluetooth.BluetoothProfileState;
|
||
|
import android.bluetooth.BluetoothSocket;
|
||
|
import android.bluetooth.BluetoothUuid;
|
||
|
import android.bluetooth.BluetoothGattService;
|
||
|
import android.bluetooth.IBluetooth;
|
||
|
import android.bluetooth.IBluetoothCallback;
|
||
|
import android.bluetooth.IBluetoothGattService;
|
||
|
import android.content.BroadcastReceiver;
|
||
|
import android.content.ContentResolver;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.IntentFilter;
|
||
|
import android.content.SharedPreferences;
|
||
|
import android.os.Binder;
|
||
|
import android.os.Handler;
|
||
|
import android.os.IBinder;
|
||
|
import android.os.Message;
|
||
|
import android.os.ParcelUuid;
|
||
|
import android.os.RemoteException;
|
||
|
import android.os.ServiceManager;
|
||
|
import android.os.SystemService;
|
||
|
import android.os.SystemProperties;
|
||
|
import android.provider.Settings;
|
||
|
import android.util.Log;
|
||
|
import android.util.Pair;
|
||
|
|
||
|
import com.android.internal.app.IBatteryStats;
|
||
|
|
||
|
import java.io.BufferedInputStream;
|
||
|
import java.io.BufferedReader;
|
||
|
import java.io.BufferedWriter;
|
||
|
import java.io.DataInputStream;
|
||
|
import java.io.File;
|
||
|
import java.io.FileDescriptor;
|
||
|
import java.io.FileInputStream;
|
||
|
import java.io.FileNotFoundException;
|
||
|
import java.io.FileOutputStream;
|
||
|
import java.io.FileWriter;
|
||
|
import java.io.IOException;
|
||
|
import java.io.InputStreamReader;
|
||
|
import java.io.PrintWriter;
|
||
|
import java.io.RandomAccessFile;
|
||
|
import java.io.UnsupportedEncodingException;
|
||
|
import java.util.ArrayList;
|
||
|
import java.util.Arrays;
|
||
|
import java.util.HashMap;
|
||
|
import java.util.SortedMap;
|
||
|
import java.util.TreeMap;
|
||
|
import java.util.Iterator;
|
||
|
import java.util.LinkedList;
|
||
|
import java.util.ListIterator;
|
||
|
import java.util.Map;
|
||
|
import java.util.NoSuchElementException;
|
||
|
|
||
|
public class BluetoothService extends IBluetooth.Stub {
|
||
|
private static final String TAG = "BluetoothService";
|
||
|
private static final boolean DBG = true;
|
||
|
|
||
|
private int mNativeData;
|
||
|
private BluetoothEventLoop mEventLoop;
|
||
|
private boolean mIsAirplaneSensitive;
|
||
|
private boolean mIsAirplaneToggleable;
|
||
|
private int mBluetoothState;
|
||
|
private boolean mRestart = false; // need to call enable() after disable()
|
||
|
private boolean mIsDiscovering;
|
||
|
|
||
|
private BluetoothAdapter mAdapter; // constant after init()
|
||
|
private final BondState mBondState = new BondState(); // local cache of bondings
|
||
|
private final IBatteryStats mBatteryStats;
|
||
|
private final Context mContext;
|
||
|
|
||
|
private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
|
||
|
private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
|
||
|
|
||
|
private static final String DOCK_ADDRESS_PATH = "/sys/class/switch/dock/bt_addr";
|
||
|
private static final String DOCK_PIN_PATH = "/sys/class/switch/dock/bt_pin";
|
||
|
|
||
|
private static final String SHARED_PREFERENCE_DOCK_ADDRESS = "dock_bluetooth_address";
|
||
|
private static final String SHARED_PREFERENCES_NAME = "bluetooth_service_settings";
|
||
|
|
||
|
private static final int MESSAGE_REGISTER_SDP_RECORDS = 1;
|
||
|
private static final int MESSAGE_FINISH_DISABLE = 2;
|
||
|
private static final int MESSAGE_UUID_INTENT = 3;
|
||
|
private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4;
|
||
|
private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5;
|
||
|
private static final int MESSAGE_START_DUN_SERVER = 6;
|
||
|
private static final int MESSAGE_GATT_INTENT = 7;
|
||
|
private static final int MESSAGE_START_SAP_SERVER = 8;
|
||
|
private static final int MESSAGE_DISCOVERABLE_CHANGE_TIMEOUT = 10;
|
||
|
|
||
|
// The time (in millisecs) to delay the pairing attempt after the first
|
||
|
// auto pairing attempt fails. We use an exponential delay with
|
||
|
// INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
|
||
|
// MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
|
||
|
private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
|
||
|
private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
|
||
|
|
||
|
private static final int SDP_ATTR_PROTO_DESC_LIST = 0x0004;
|
||
|
private static final int SDP_ATTR_GOEP_L2CAP_PSM = 0x0200;
|
||
|
|
||
|
// The timeout used to sent the UUIDs Intent
|
||
|
// This timeout should be greater than the page timeout
|
||
|
// Some of devices takes more than 15 secons for SDP response,
|
||
|
// Therefore, it is better set 30 seconds timeout instead of 6 seconds.
|
||
|
private static final int UUID_INTENT_DELAY = 30000;
|
||
|
|
||
|
// The timeout used to sent the GATT services Intent
|
||
|
// This timeout should be greater than the page timeout
|
||
|
private static final int GATT_INTENT_DELAY = 30000;
|
||
|
|
||
|
public static final String SAP_AUTHORIZE_INTENT =
|
||
|
"com.android.bluetooth.sap.accessrequest";
|
||
|
public static final String SAP_STATECHANGE_INTENT =
|
||
|
"com.android.bluetooth.sap.statechanged";
|
||
|
public static final String SAP_ACCESS_ALLOWED_ACTION =
|
||
|
"com.android.bluetooth.sap.accessallowed";
|
||
|
public static final String SAP_EXTRA_ALWAYS_ALLOWED =
|
||
|
"com.android.bluetooth.sap.alwaysallowed";
|
||
|
public static final String SAP_ACCESS_DISALLOWED_ACTION =
|
||
|
"com.android.bluetooth.sap.accessdisallowed";
|
||
|
|
||
|
/** Always retrieve RFCOMM channel for these SDP UUIDs */
|
||
|
private static final ParcelUuid[] RFCOMM_UUIDS = {
|
||
|
BluetoothUuid.Handsfree,
|
||
|
BluetoothUuid.HSP,
|
||
|
BluetoothUuid.ObexObjectPush,
|
||
|
BluetoothUuid.MessageNotificationServer,
|
||
|
BluetoothUuid.DirectPrinting,
|
||
|
BluetoothUuid.ReferencePrinting,
|
||
|
BluetoothUuid.PrintingStatus };
|
||
|
|
||
|
// TODO(): Optimize all these string handling
|
||
|
private final Map<String, String> mAdapterProperties;
|
||
|
private final HashMap<String, Map<String, String>> mDeviceProperties;
|
||
|
|
||
|
private final HashMap<String, Map<String, String>> mGattProperties;
|
||
|
|
||
|
private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceServiceChannelCache;
|
||
|
private final HashMap<String, Map<ParcelUuid, Integer>> mDeviceL2capPsmCache;
|
||
|
private final HashMap<String, Map<String, String>> mDeviceFeatureCache;
|
||
|
|
||
|
private final ArrayList<String> mUuidIntentTracker;
|
||
|
private final HashMap<RemoteService, IBluetoothCallback> mUuidCallbackTracker;
|
||
|
|
||
|
private final HashMap<String, ArrayList<ParcelUuid>> mGattIntentTracker;
|
||
|
private final HashMap<String, IBluetoothGattService> mGattServiceTracker;
|
||
|
private final HashMap<String, IBluetoothGattService> mGattWatcherTracker;
|
||
|
|
||
|
private final SortedMap<String, Integer> mGattServices;
|
||
|
|
||
|
private final HashMap<Integer, Integer> mServiceRecordToPid;
|
||
|
|
||
|
private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState;
|
||
|
private final BluetoothProfileState mA2dpProfileState;
|
||
|
private final BluetoothProfileState mHfpProfileState;
|
||
|
|
||
|
private BluetoothA2dpService mA2dpService;
|
||
|
private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData;
|
||
|
|
||
|
private static String mDockAddress;
|
||
|
private String mDockPin;
|
||
|
private boolean mDUNenable = false;
|
||
|
private boolean mSAPenable = false;
|
||
|
private boolean mFTPenable = false;
|
||
|
|
||
|
private static final String INCOMING_CONNECTION_FILE =
|
||
|
"/data/misc/bluetooth/incoming_connection.conf";
|
||
|
private HashMap<String, Pair<Integer, String>> mIncomingConnections;
|
||
|
|
||
|
|
||
|
private static class RemoteService {
|
||
|
public String address;
|
||
|
public ParcelUuid uuid;
|
||
|
public RemoteService(String address, ParcelUuid uuid) {
|
||
|
this.address = address;
|
||
|
this.uuid = uuid;
|
||
|
}
|
||
|
@Override
|
||
|
public boolean equals(Object o) {
|
||
|
if (o instanceof RemoteService) {
|
||
|
RemoteService service = (RemoteService)o;
|
||
|
return address.equals(service.address) && uuid.equals(service.uuid);
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public int hashCode() {
|
||
|
int hash = 1;
|
||
|
hash = hash * 31 + (address == null ? 0 : address.hashCode());
|
||
|
hash = hash * 31 + (uuid == null ? 0 : uuid.hashCode());
|
||
|
return hash;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static {
|
||
|
classInitNative();
|
||
|
}
|
||
|
|
||
|
public BluetoothService(Context context) {
|
||
|
mContext = context;
|
||
|
|
||
|
// Need to do this in place of:
|
||
|
// mBatteryStats = BatteryStatsService.getService();
|
||
|
// Since we can not import BatteryStatsService from here. This class really needs to be
|
||
|
// moved to java/services/com/android/server/
|
||
|
mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo"));
|
||
|
|
||
|
initializeNativeDataNative();
|
||
|
|
||
|
if (isEnabledNative() == 1) {
|
||
|
Log.w(TAG, "Bluetooth daemons already running - runtime restart? ");
|
||
|
disableNative();
|
||
|
}
|
||
|
|
||
|
mBluetoothState = BluetoothAdapter.STATE_OFF;
|
||
|
mIsDiscovering = false;
|
||
|
mAdapterProperties = new HashMap<String, String>();
|
||
|
mDeviceProperties = new HashMap<String, Map<String,String>>();
|
||
|
|
||
|
mGattProperties = new HashMap<String, Map<String,String>>();
|
||
|
|
||
|
mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>();
|
||
|
mDeviceFeatureCache = new HashMap<String, Map<String, String>>();
|
||
|
mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>();
|
||
|
mDeviceL2capPsmCache = new HashMap<String, Map<ParcelUuid, Integer>>();
|
||
|
mUuidIntentTracker = new ArrayList<String>();
|
||
|
mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>();
|
||
|
mGattIntentTracker = new HashMap<String, ArrayList<ParcelUuid>>();
|
||
|
mGattServiceTracker = new HashMap<String, IBluetoothGattService>();
|
||
|
mGattWatcherTracker = new HashMap<String, IBluetoothGattService>();
|
||
|
mGattServices = new TreeMap<String, Integer>();
|
||
|
mServiceRecordToPid = new HashMap<Integer, Integer>();
|
||
|
mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>();
|
||
|
mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP);
|
||
|
mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP);
|
||
|
|
||
|
mHfpProfileState.start();
|
||
|
mA2dpProfileState.start();
|
||
|
|
||
|
mConnectionManager = new ConnectionManager();
|
||
|
|
||
|
IntentFilter filter = new IntentFilter();
|
||
|
registerForAirplaneMode(filter);
|
||
|
|
||
|
// Register for connection-oriented notifications
|
||
|
filter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
|
||
|
filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED);
|
||
|
|
||
|
filter.addAction(Intent.ACTION_DOCK_EVENT);
|
||
|
filter.addAction(SAP_ACCESS_ALLOWED_ACTION);
|
||
|
filter.addAction(SAP_ACCESS_DISALLOWED_ACTION);
|
||
|
mContext.registerReceiver(mReceiver, filter);
|
||
|
mIncomingConnections = new HashMap<String, Pair<Integer, String>>();
|
||
|
}
|
||
|
|
||
|
public static synchronized String readDockBluetoothAddress() {
|
||
|
if (mDockAddress != null) return mDockAddress;
|
||
|
|
||
|
BufferedInputStream file = null;
|
||
|
String dockAddress;
|
||
|
try {
|
||
|
file = new BufferedInputStream(new FileInputStream(DOCK_ADDRESS_PATH));
|
||
|
byte[] address = new byte[17];
|
||
|
file.read(address);
|
||
|
dockAddress = new String(address);
|
||
|
dockAddress = dockAddress.toUpperCase();
|
||
|
if (BluetoothAdapter.checkBluetoothAddress(dockAddress)) {
|
||
|
mDockAddress = dockAddress;
|
||
|
return mDockAddress;
|
||
|
} else {
|
||
|
log("CheckBluetoothAddress failed for car dock address:" + dockAddress);
|
||
|
}
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException while trying to read dock address");
|
||
|
} catch (IOException e) {
|
||
|
log("IOException while trying to read dock address");
|
||
|
} finally {
|
||
|
if (file != null) {
|
||
|
try {
|
||
|
file.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
mDockAddress = null;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
private synchronized boolean writeDockPin() {
|
||
|
BufferedWriter out = null;
|
||
|
try {
|
||
|
out = new BufferedWriter(new FileWriter(DOCK_PIN_PATH));
|
||
|
|
||
|
// Generate a random 4 digit pin between 0000 and 9999
|
||
|
// This is not truly random but good enough for our purposes.
|
||
|
int pin = (int) Math.floor(Math.random() * 10000);
|
||
|
|
||
|
mDockPin = String.format("%04d", pin);
|
||
|
out.write(mDockPin);
|
||
|
return true;
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException while trying to write dock pairing pin");
|
||
|
} catch (IOException e) {
|
||
|
log("IOException while while trying to write dock pairing pin");
|
||
|
} finally {
|
||
|
if (out != null) {
|
||
|
try {
|
||
|
out.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
mDockPin = null;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String getDockPin() {
|
||
|
return mDockPin;
|
||
|
}
|
||
|
|
||
|
public synchronized void initAfterRegistration() {
|
||
|
mAdapter = BluetoothAdapter.getDefaultAdapter();
|
||
|
mEventLoop = new BluetoothEventLoop(mContext, mAdapter, this);
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() throws Throwable {
|
||
|
mContext.unregisterReceiver(mReceiver);
|
||
|
try {
|
||
|
cleanupNativeDataNative();
|
||
|
} finally {
|
||
|
super.finalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean isEnabled() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return isEnabledInternal();
|
||
|
}
|
||
|
|
||
|
private boolean isEnabledInternal() {
|
||
|
return mBluetoothState == BluetoothAdapter.STATE_ON;
|
||
|
}
|
||
|
|
||
|
public int getBluetoothState() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return mBluetoothState;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Bring down bluetooth and disable BT in settings. Returns true on success.
|
||
|
*/
|
||
|
public boolean disable() {
|
||
|
return disable(true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Bring down bluetooth. Returns true on success.
|
||
|
*
|
||
|
* @param saveSetting If true, persist the new setting
|
||
|
*/
|
||
|
public synchronized boolean disable(boolean saveSetting) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission");
|
||
|
int disconnDelay = 3000;
|
||
|
int conn = 0;
|
||
|
|
||
|
switch (mBluetoothState) {
|
||
|
case BluetoothAdapter.STATE_OFF:
|
||
|
return true;
|
||
|
case BluetoothAdapter.STATE_ON:
|
||
|
break;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
if (mEnableThread != null && mEnableThread.isAlive()) {
|
||
|
return false;
|
||
|
}
|
||
|
setBluetoothState(BluetoothAdapter.STATE_TURNING_OFF);
|
||
|
mHandler.removeMessages(MESSAGE_REGISTER_SDP_RECORDS);
|
||
|
mHandler.removeMessages(MESSAGE_START_DUN_SERVER);
|
||
|
mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
|
||
|
|
||
|
// Allow 3 second for profiles to gracefully disconnect
|
||
|
// TODO: Introduce a callback mechanism so that each profile can notify
|
||
|
// BluetoothService when it is done shutting down
|
||
|
|
||
|
conn = listConnectionNative();
|
||
|
Log.d(TAG,"conn = "+conn);
|
||
|
if(conn == 0)
|
||
|
disconnDelay = 2000;
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_FINISH_DISABLE, saveSetting ? 1 : 0, 0),disconnDelay);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
private synchronized void finishDisable(boolean saveSetting) {
|
||
|
if (mBluetoothState != BluetoothAdapter.STATE_TURNING_OFF) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (mDUNenable == true) {
|
||
|
Log.d(TAG, "Stopping BT-DUN server");
|
||
|
SystemService.stop("bt-dun");
|
||
|
}
|
||
|
|
||
|
if (mSAPenable == true) {
|
||
|
Log.d(TAG, "Stopping BT SAP server");
|
||
|
SystemService.stop("bt-sap");
|
||
|
}
|
||
|
mEventLoop.stop();
|
||
|
tearDownNativeDataNative();
|
||
|
disableNative();
|
||
|
|
||
|
// mark in progress bondings as cancelled
|
||
|
for (String address : mBondState.listInState(BluetoothDevice.BOND_BONDING)) {
|
||
|
mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
|
||
|
BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
|
||
|
}
|
||
|
|
||
|
// update mode
|
||
|
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
|
||
|
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, BluetoothAdapter.SCAN_MODE_NONE);
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
|
||
|
|
||
|
mIsDiscovering = false;
|
||
|
mAdapterProperties.clear();
|
||
|
mServiceRecordToPid.clear();
|
||
|
|
||
|
if (saveSetting) {
|
||
|
persistBluetoothOnSetting(false);
|
||
|
}
|
||
|
|
||
|
setBluetoothState(BluetoothAdapter.STATE_OFF);
|
||
|
|
||
|
// Log bluetooth off to battery stats.
|
||
|
long ident = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mBatteryStats.noteBluetoothOff();
|
||
|
} catch (RemoteException e) {
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(ident);
|
||
|
}
|
||
|
|
||
|
if (mRestart) {
|
||
|
mRestart = false;
|
||
|
enable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Bring up BT and persist BT on in settings */
|
||
|
public boolean enable() {
|
||
|
return enable(true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Enable this Bluetooth device, asynchronously.
|
||
|
* This turns on/off the underlying hardware.
|
||
|
*
|
||
|
* @param saveSetting If true, persist the new state of BT in settings
|
||
|
* @return True on success (so far)
|
||
|
*/
|
||
|
public synchronized boolean enable(boolean saveSetting) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
|
||
|
// Airplane mode can prevent Bluetooth radio from being turned on.
|
||
|
if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
|
||
|
return false;
|
||
|
}
|
||
|
if (mBluetoothState != BluetoothAdapter.STATE_OFF) {
|
||
|
return false;
|
||
|
}
|
||
|
if (mEnableThread != null && mEnableThread.isAlive()) {
|
||
|
return false;
|
||
|
}
|
||
|
if (SystemProperties.getBoolean("ro.qualcomm.bluetooth.dun", false)) {
|
||
|
mDUNenable = true;
|
||
|
}
|
||
|
if (SystemProperties.getBoolean("ro.qualcomm.bluetooth.ftp", false)) {
|
||
|
mFTPenable = true;
|
||
|
}
|
||
|
if (SystemProperties.getBoolean("ro.qualcomm.bluetooth.sap", false)) {
|
||
|
mSAPenable = true;
|
||
|
}
|
||
|
setBluetoothState(BluetoothAdapter.STATE_TURNING_ON);
|
||
|
mEnableThread = new EnableThread(saveSetting);
|
||
|
mEnableThread.start();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/** Forcibly restart Bluetooth if it is on */
|
||
|
/* package */ synchronized void restart() {
|
||
|
if (mBluetoothState != BluetoothAdapter.STATE_ON) {
|
||
|
return;
|
||
|
}
|
||
|
mRestart = true;
|
||
|
if (!disable(false)) {
|
||
|
mRestart = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private synchronized void setBluetoothState(int state) {
|
||
|
if (state == mBluetoothState) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (DBG) log("Bluetooth state " + mBluetoothState + " -> " + state);
|
||
|
|
||
|
Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED);
|
||
|
intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, mBluetoothState);
|
||
|
intent.putExtra(BluetoothAdapter.EXTRA_STATE, state);
|
||
|
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
||
|
|
||
|
mBluetoothState = state;
|
||
|
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
|
||
|
}
|
||
|
|
||
|
private final Handler mHandler = new Handler() {
|
||
|
@Override
|
||
|
public void handleMessage(Message msg) {
|
||
|
switch (msg.what) {
|
||
|
case MESSAGE_REGISTER_SDP_RECORDS:
|
||
|
if (!isEnabledInternal()) {
|
||
|
return;
|
||
|
}
|
||
|
// SystemService.start() forks sdptool to register service
|
||
|
// records. It can fail to register some records if it is
|
||
|
// forked multiple times in a row, probably because there is
|
||
|
// some race in sdptool or bluez when operated in parallel.
|
||
|
// As a workaround, delay 500ms between each fork of sdptool.
|
||
|
// TODO: Don't fork sdptool in order to register service
|
||
|
// records, use a DBUS call instead.
|
||
|
switch (msg.arg1) {
|
||
|
case 1:
|
||
|
Log.d(TAG, "Registering hfag record");
|
||
|
SystemService.start("hfag");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 2, -1), 500);
|
||
|
break;
|
||
|
case 2:
|
||
|
Log.d(TAG, "Registering hsag record");
|
||
|
SystemService.start("hsag");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 3, -1), 500);
|
||
|
break;
|
||
|
case 3:
|
||
|
Log.d(TAG, "Registering opush record");
|
||
|
SystemService.start("opush");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 4, -1), 500);
|
||
|
break;
|
||
|
case 4:
|
||
|
Log.d(TAG, "Registering pbap record");
|
||
|
SystemService.start("pbap");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 5, -1), 500);
|
||
|
break;
|
||
|
case 5:
|
||
|
Log.d(TAG, "Registering map record instance id 0");
|
||
|
SystemService.start("map0");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 6, -1), 500);
|
||
|
break;
|
||
|
case 6:
|
||
|
if (mDUNenable == true) {
|
||
|
Log.d(TAG, "Registering dun record");
|
||
|
SystemService.start("dund");
|
||
|
}
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 7, -1), 500);
|
||
|
break;
|
||
|
case 7:
|
||
|
if (mFTPenable == true) {
|
||
|
Log.d(TAG, "Registering ftp record");
|
||
|
SystemService.start("ftp");
|
||
|
}
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 8, -1), 500);
|
||
|
break;
|
||
|
case 8:
|
||
|
Log.d(TAG, "Registering map record instance id 1");
|
||
|
SystemService.start("map1");
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 9, -1), 500);
|
||
|
break;
|
||
|
case 9:
|
||
|
if (mSAPenable == true) {
|
||
|
Log.d(TAG, "Registering SAP record");
|
||
|
SystemService.start("sapd");
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_FINISH_DISABLE:
|
||
|
finishDisable(msg.arg1 != 0);
|
||
|
break;
|
||
|
case MESSAGE_UUID_INTENT:
|
||
|
String address = (String)msg.obj;
|
||
|
if (address != null) {
|
||
|
sendUuidIntent(address);
|
||
|
makeServiceChannelCallbacks(address);
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_DISCOVERABLE_TIMEOUT:
|
||
|
int mode = msg.arg1;
|
||
|
if (isEnabledInternal()) {
|
||
|
// TODO: Switch back to the previous scan mode
|
||
|
// This is ok for now, because we only use
|
||
|
// CONNECTABLE and CONNECTABLE_DISCOVERABLE
|
||
|
setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1);
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_START_DUN_SERVER:
|
||
|
if (mDUNenable == true) {
|
||
|
Log.d(TAG, "Starting BT-DUN server");
|
||
|
SystemService.start("bt-dun");
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
|
||
|
address = (String)msg.obj;
|
||
|
if (address != null) {
|
||
|
if (true == createBond(address)) {
|
||
|
setBondState(address,
|
||
|
BluetoothDevice.BOND_RETRY,
|
||
|
BluetoothDevice.UNBOND_REASON_AUTH_FAILED);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_GATT_INTENT:
|
||
|
address = (String)msg.obj;
|
||
|
if (address != null && mGattIntentTracker.containsKey(address)){
|
||
|
sendGattIntent(address, BluetoothDevice.GATT_RESULT_TIMEOUT);
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_START_SAP_SERVER:
|
||
|
if (mSAPenable == true) {
|
||
|
Log.d(TAG, "Starting BT-SAP server");
|
||
|
SystemService.start("bt-sap");
|
||
|
}
|
||
|
break;
|
||
|
case MESSAGE_DISCOVERABLE_CHANGE_TIMEOUT:
|
||
|
Log.i(TAG, "discoverable change timeout");
|
||
|
if (getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||
|
getAllProperties();
|
||
|
int newMode = getScanMode();
|
||
|
if (newMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||
|
Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
|
||
|
intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, newMode);
|
||
|
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
private EnableThread mEnableThread;
|
||
|
|
||
|
private class EnableThread extends Thread {
|
||
|
private final boolean mSaveSetting;
|
||
|
public EnableThread(boolean saveSetting) {
|
||
|
mSaveSetting = saveSetting;
|
||
|
}
|
||
|
public void run() {
|
||
|
boolean res = (enableNative() == 0);
|
||
|
if (res) {
|
||
|
int retryCount = 2;
|
||
|
boolean running = false;
|
||
|
while ((retryCount-- > 0) && !running) {
|
||
|
mEventLoop.start();
|
||
|
// it may take a momement for the other thread to do its
|
||
|
// thing. Check periodically for a while.
|
||
|
int pollCount = 5;
|
||
|
while ((pollCount-- > 0) && !running) {
|
||
|
if (mEventLoop.isEventLoopRunning()) {
|
||
|
running = true;
|
||
|
break;
|
||
|
}
|
||
|
try {
|
||
|
Thread.sleep(100);
|
||
|
} catch (InterruptedException e) {}
|
||
|
}
|
||
|
}
|
||
|
if (!running) {
|
||
|
log("bt EnableThread giving up");
|
||
|
res = false;
|
||
|
disableNative();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
if (res) {
|
||
|
if (!setupNativeDataNative()) {
|
||
|
return;
|
||
|
}
|
||
|
if (mSaveSetting) {
|
||
|
persistBluetoothOnSetting(true);
|
||
|
}
|
||
|
mIsDiscovering = false;
|
||
|
mBondState.readAutoPairingData();
|
||
|
mBondState.loadBondState();
|
||
|
initProfileState();
|
||
|
|
||
|
if (mDUNenable == true) {
|
||
|
mHandler.sendMessage(
|
||
|
mHandler.obtainMessage(MESSAGE_START_DUN_SERVER));
|
||
|
}
|
||
|
|
||
|
if (mSAPenable == true) {
|
||
|
mHandler.sendMessage(
|
||
|
mHandler.obtainMessage(MESSAGE_START_SAP_SERVER));
|
||
|
}
|
||
|
|
||
|
mHandler.sendMessageDelayed(
|
||
|
mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000);
|
||
|
|
||
|
// Log bluetooth on to battery stats.
|
||
|
long ident = Binder.clearCallingIdentity();
|
||
|
try {
|
||
|
mBatteryStats.noteBluetoothOn();
|
||
|
} catch (RemoteException e) {
|
||
|
} finally {
|
||
|
Binder.restoreCallingIdentity(ident);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mEnableThread = null;
|
||
|
|
||
|
setBluetoothState(res ?
|
||
|
BluetoothAdapter.STATE_ON :
|
||
|
BluetoothAdapter.STATE_OFF);
|
||
|
|
||
|
if (res) {
|
||
|
// Update mode
|
||
|
String[] propVal = {"Pairable", getProperty("Pairable")};
|
||
|
mEventLoop.onPropertyChanged(propVal);
|
||
|
}
|
||
|
|
||
|
if (mIsAirplaneSensitive && isAirplaneModeOn() && !mIsAirplaneToggleable) {
|
||
|
disable(false);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private ConnectionManager mConnectionManager;
|
||
|
|
||
|
// Track system Bluetooth profile connection state. A centralized location to
|
||
|
// control and enforce profile concurrency policies (e.g., do X when audio activity
|
||
|
// is present)
|
||
|
private class ConnectionManager {
|
||
|
private LinkedList<BtPolicyCallback> mConnectionList = new LinkedList<BtPolicyCallback>();
|
||
|
private boolean mScoAudioActive = false;
|
||
|
private boolean mA2dpAudioActive = false;
|
||
|
private boolean mUseWifiForBtTransfers = true;
|
||
|
|
||
|
private int mBtPolicyHandle = 1;
|
||
|
private LinkedList<Integer> mBtPolicyHandlesAvailable = new LinkedList<Integer>();
|
||
|
|
||
|
private class BtPolicyCallback {
|
||
|
IBluetoothCallback mCallback;
|
||
|
int mDesiredAmpPolicy = BluetoothSocket.BT_AMP_POLICY_REQUIRE_BR_EDR;
|
||
|
int mCurrentAmpPolicy = BluetoothSocket.BT_AMP_POLICY_REQUIRE_BR_EDR;
|
||
|
int mHandle = 0;
|
||
|
|
||
|
boolean removeHandle(int handle) {
|
||
|
Log.i(TAG, "Deallocating handle " + Integer.toString(mHandle) + " to track BT policy.");
|
||
|
mHandle = 0;
|
||
|
return mBtPolicyHandlesAvailable.add(handle);
|
||
|
}
|
||
|
|
||
|
int getHandle() {
|
||
|
return mHandle;
|
||
|
}
|
||
|
|
||
|
BtPolicyCallback(IBluetoothCallback newCallback, int newDesiredAmpPolicy) {
|
||
|
this.mCallback = newCallback;
|
||
|
this.mDesiredAmpPolicy = validateAmpPolicy(newDesiredAmpPolicy);
|
||
|
this.mCurrentAmpPolicy = getEffectiveAmpPolicy(newDesiredAmpPolicy);
|
||
|
|
||
|
/* Allocate a handle */
|
||
|
try {
|
||
|
mHandle = mBtPolicyHandlesAvailable.getFirst();
|
||
|
} catch (NoSuchElementException e) {
|
||
|
if (mBtPolicyHandle < Integer.MAX_VALUE) {
|
||
|
mHandle = ++mBtPolicyHandle;
|
||
|
Log.i(TAG, "Allocating handle " + Integer.toString(mHandle) + " to track BT policy.");
|
||
|
} else {
|
||
|
Log.e(TAG, "Trouble finding open handle to track BT policy!");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int validateAmpPolicy(int policy) {
|
||
|
if (policy == BluetoothSocket.BT_AMP_POLICY_PREFER_BR_EDR ||
|
||
|
policy == BluetoothSocket.BT_AMP_POLICY_PREFER_AMP) {
|
||
|
return policy;
|
||
|
}
|
||
|
return BluetoothSocket.BT_AMP_POLICY_REQUIRE_BR_EDR;
|
||
|
}
|
||
|
|
||
|
public synchronized int getEffectiveAmpPolicy(int policy) {
|
||
|
if (mScoAudioActive || mA2dpAudioActive || !mUseWifiForBtTransfers) {
|
||
|
return BluetoothSocket.BT_AMP_POLICY_REQUIRE_BR_EDR;
|
||
|
}
|
||
|
return validateAmpPolicy(policy);
|
||
|
}
|
||
|
|
||
|
public synchronized int registerEl2capConnection(IBluetoothCallback callback, int initialPolicy) {
|
||
|
BtPolicyCallback bpc = new BtPolicyCallback(callback, initialPolicy);
|
||
|
mConnectionList.add(bpc);
|
||
|
return bpc.getHandle();
|
||
|
}
|
||
|
|
||
|
public synchronized void deregisterEl2capConnection(int handle) {
|
||
|
ListIterator<BtPolicyCallback> it = mConnectionList.listIterator();
|
||
|
while (it.hasNext()) {
|
||
|
BtPolicyCallback thisBtPolicyCallback = it.next();
|
||
|
if (thisBtPolicyCallback.mHandle == handle) {
|
||
|
thisBtPolicyCallback.removeHandle(handle);
|
||
|
mConnectionList.remove(thisBtPolicyCallback);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setDesiredAmpPolicy(int handle, int policy) {
|
||
|
boolean result = true;
|
||
|
ListIterator<BtPolicyCallback> it = mConnectionList.listIterator();
|
||
|
policy = validateAmpPolicy(policy);
|
||
|
int allowedPolicy = getEffectiveAmpPolicy(policy);
|
||
|
while (it.hasNext()) {
|
||
|
BtPolicyCallback thisBtPolicyCallback = it.next();
|
||
|
if (thisBtPolicyCallback.mHandle == handle) {
|
||
|
if (thisBtPolicyCallback.mCurrentAmpPolicy != allowedPolicy) {
|
||
|
try {
|
||
|
thisBtPolicyCallback.mCallback.onAmpPolicyChange(allowedPolicy);
|
||
|
thisBtPolicyCallback.mCurrentAmpPolicy = allowedPolicy;
|
||
|
thisBtPolicyCallback.mDesiredAmpPolicy = policy;
|
||
|
} catch (RemoteException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
result = false;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
public synchronized void setScoAudioActive(boolean active) {
|
||
|
Log.d(TAG, "setScoAudioActive(): " + active);
|
||
|
|
||
|
if (mScoAudioActive != active) {
|
||
|
mScoAudioActive = active;
|
||
|
updateConnectionAmpPolicies();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized void setA2dpAudioActive(boolean active) {
|
||
|
Log.d(TAG, "setA2dpAudioActive(): " + active);
|
||
|
|
||
|
if (mA2dpAudioActive != active) {
|
||
|
mA2dpAudioActive = active;
|
||
|
updateConnectionAmpPolicies();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized void setUseWifiForBtTransfers(boolean useWifi) {
|
||
|
Log.d(TAG, "setUseWifiForBtTransfers(): " + useWifi);
|
||
|
|
||
|
if (mUseWifiForBtTransfers != useWifi) {
|
||
|
mUseWifiForBtTransfers = useWifi;
|
||
|
updateConnectionAmpPolicies();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// call when anything happens that can change an AMP policy
|
||
|
private boolean updateConnectionAmpPolicies() {
|
||
|
boolean result = true;
|
||
|
ListIterator<BtPolicyCallback> it = mConnectionList.listIterator();
|
||
|
while (it.hasNext()) {
|
||
|
BtPolicyCallback thisBtPolicyCallback = it.next();
|
||
|
int allowedPolicy = getEffectiveAmpPolicy(thisBtPolicyCallback.mDesiredAmpPolicy);
|
||
|
if (allowedPolicy != thisBtPolicyCallback.mCurrentAmpPolicy) {
|
||
|
try {
|
||
|
thisBtPolicyCallback.mCallback.onAmpPolicyChange(allowedPolicy);
|
||
|
thisBtPolicyCallback.mCurrentAmpPolicy = allowedPolicy;
|
||
|
} catch (RemoteException e) {
|
||
|
Log.e(TAG, "", e);
|
||
|
result = false; /* This one didn't update, but keep looping */
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void persistBluetoothOnSetting(boolean bluetoothOn) {
|
||
|
long origCallerIdentityToken = Binder.clearCallingIdentity();
|
||
|
Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON,
|
||
|
bluetoothOn ? 1 : 0);
|
||
|
Binder.restoreCallingIdentity(origCallerIdentityToken);
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized boolean attemptAutoPair(String address) {
|
||
|
if (!mBondState.hasAutoPairingFailed(address) &&
|
||
|
!mBondState.isAutoPairingBlacklisted(address)) {
|
||
|
mBondState.attempt(address);
|
||
|
setPin(address, BluetoothDevice.convertPinToBytes("0000"));
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) {
|
||
|
if (result == BluetoothDevice.BOND_SUCCESS) {
|
||
|
setBondState(address, BluetoothDevice.BOND_BONDED);
|
||
|
if (mBondState.isAutoPairingAttemptsInProgress(address)) {
|
||
|
mBondState.clearPinAttempts(address);
|
||
|
}
|
||
|
} else {
|
||
|
setBondState(address, BluetoothDevice.BOND_NONE, result);
|
||
|
if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
|
||
|
mBondState.getAttempt(address) == 1) {
|
||
|
mBondState.addAutoPairingFailure(address);
|
||
|
pairingAttempt(address, result);
|
||
|
} else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
|
||
|
mBondState.isAutoPairingAttemptsInProgress(address)) {
|
||
|
pairingAttempt(address, result);
|
||
|
} else {
|
||
|
if (mBondState.isAutoPairingAttemptsInProgress(address)) {
|
||
|
mBondState.clearPinAttempts(address);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String getPendingOutgoingBonding() {
|
||
|
return mBondState.getPendingOutgoingBonding();
|
||
|
}
|
||
|
|
||
|
private void pairingAttempt(String address, int result) {
|
||
|
// This happens when our initial guess of "0000" as the pass key
|
||
|
// fails. Try to create the bond again and display the pin dialog
|
||
|
// to the user. Use back-off while posting the delayed
|
||
|
// message. The initial value is
|
||
|
// INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
|
||
|
// MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
|
||
|
// reached, display an error to the user.
|
||
|
int attempt = mBondState.getAttempt(address);
|
||
|
if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
|
||
|
MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
|
||
|
mBondState.clearPinAttempts(address);
|
||
|
setBondState(address, BluetoothDevice.BOND_NONE, result);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
|
||
|
message.obj = address;
|
||
|
boolean postResult = mHandler.sendMessageDelayed(message,
|
||
|
attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
|
||
|
if (!postResult) {
|
||
|
mBondState.clearPinAttempts(address);
|
||
|
setBondState(address,
|
||
|
BluetoothDevice.BOND_NONE, result);
|
||
|
return;
|
||
|
}
|
||
|
mBondState.attempt(address);
|
||
|
}
|
||
|
|
||
|
/** local cache of bonding state.
|
||
|
/* we keep our own state to track the intermediate state BONDING, which
|
||
|
/* bluez does not track.
|
||
|
* All addresses must be passed in upper case.
|
||
|
*/
|
||
|
public class BondState {
|
||
|
private final HashMap<String, Integer> mState = new HashMap<String, Integer>();
|
||
|
private final HashMap<String, Integer> mPinAttempt = new HashMap<String, Integer>();
|
||
|
|
||
|
private static final String AUTO_PAIRING_BLACKLIST =
|
||
|
"/etc/bluetooth/auto_pairing.conf";
|
||
|
private static final String DYNAMIC_AUTO_PAIRING_BLACKLIST =
|
||
|
"/data/misc/bluetooth/dynamic_auto_pairing.conf";
|
||
|
private ArrayList<String> mAutoPairingAddressBlacklist;
|
||
|
private ArrayList<String> mAutoPairingExactNameBlacklist;
|
||
|
private ArrayList<String> mAutoPairingPartialNameBlacklist;
|
||
|
// Addresses added to blacklist dynamically based on usage.
|
||
|
private ArrayList<String> mAutoPairingDynamicAddressBlacklist;
|
||
|
|
||
|
|
||
|
// If this is an outgoing connection, store the address.
|
||
|
// There can be only 1 pending outgoing connection at a time,
|
||
|
private String mPendingOutgoingBonding;
|
||
|
|
||
|
private synchronized void setPendingOutgoingBonding(String address) {
|
||
|
mPendingOutgoingBonding = address;
|
||
|
}
|
||
|
|
||
|
public synchronized String getPendingOutgoingBonding() {
|
||
|
return mPendingOutgoingBonding;
|
||
|
}
|
||
|
|
||
|
public synchronized void loadBondState() {
|
||
|
if (mBluetoothState != BluetoothAdapter.STATE_TURNING_ON) {
|
||
|
return;
|
||
|
}
|
||
|
String []bonds = null;
|
||
|
String address = null;
|
||
|
String val = getPropertyInternal("Devices");
|
||
|
if (val != null) {
|
||
|
bonds = val.split(",");
|
||
|
}
|
||
|
if (bonds == null) {
|
||
|
return;
|
||
|
}
|
||
|
mState.clear();
|
||
|
Log.d(TAG, "found " + bonds.length + " bonded devices");
|
||
|
for (String device : bonds) {
|
||
|
address = getAddressFromObjectPath(device);
|
||
|
if (address == null) {
|
||
|
Log.e(TAG, "error! address is null");
|
||
|
continue;
|
||
|
}
|
||
|
String pairState = getUpdatedRemoteDeviceProperty(address, "Paired");
|
||
|
Log.d(TAG, "The paired state of the remote device is " + pairState);
|
||
|
if(pairState.equals("true")) {
|
||
|
Log.d(TAG, "The paired state of the remote device is true");
|
||
|
mState.put(address.toUpperCase(),
|
||
|
BluetoothDevice.BOND_BONDED);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized void setBondState(String address, int state) {
|
||
|
setBondState(address, state, 0);
|
||
|
}
|
||
|
|
||
|
/** reason is ignored unless state == BOND_NOT_BONDED */
|
||
|
public synchronized void setBondState(String address, int state, int reason) {
|
||
|
int oldState = getBondState(address);
|
||
|
if (oldState == state) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Check if this was an pending outgoing bonding.
|
||
|
// If yes, reset the state.
|
||
|
if (oldState == BluetoothDevice.BOND_BONDING) {
|
||
|
if (address.equals(mPendingOutgoingBonding)) {
|
||
|
mPendingOutgoingBonding = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (state == BluetoothDevice.BOND_BONDED) {
|
||
|
addProfileState(address);
|
||
|
}
|
||
|
|
||
|
if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" +
|
||
|
reason + ")");
|
||
|
Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, state);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState);
|
||
|
if (state == BluetoothDevice.BOND_NONE) {
|
||
|
if (reason <= 0) {
|
||
|
Log.w(TAG, "setBondState() called to unbond device, but reason code is " +
|
||
|
"invalid. Overriding reason code with BOND_RESULT_REMOVED");
|
||
|
reason = BluetoothDevice.UNBOND_REASON_REMOVED;
|
||
|
}
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_REASON, reason);
|
||
|
mState.remove(address);
|
||
|
} else {
|
||
|
mState.put(address, state);
|
||
|
}
|
||
|
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_PERM);
|
||
|
}
|
||
|
|
||
|
public boolean isAutoPairingBlacklisted(String address) {
|
||
|
if (mAutoPairingAddressBlacklist != null) {
|
||
|
for (String blacklistAddress : mAutoPairingAddressBlacklist) {
|
||
|
if (address.startsWith(blacklistAddress)) return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mAutoPairingDynamicAddressBlacklist != null) {
|
||
|
for (String blacklistAddress: mAutoPairingDynamicAddressBlacklist) {
|
||
|
if (address.equals(blacklistAddress)) return true;
|
||
|
}
|
||
|
}
|
||
|
String name = getRemoteName(address);
|
||
|
if (name != null) {
|
||
|
if (mAutoPairingExactNameBlacklist != null) {
|
||
|
for (String blacklistName : mAutoPairingExactNameBlacklist) {
|
||
|
if (name.equals(blacklistName)) return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (mAutoPairingPartialNameBlacklist != null) {
|
||
|
for (String blacklistName : mAutoPairingPartialNameBlacklist) {
|
||
|
if (name.startsWith(blacklistName)) return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public synchronized int getBondState(String address) {
|
||
|
Integer state = mState.get(address);
|
||
|
if (state == null) {
|
||
|
return BluetoothDevice.BOND_NONE;
|
||
|
}
|
||
|
return state.intValue();
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String[] listInState(int state) {
|
||
|
ArrayList<String> result = new ArrayList<String>(mState.size());
|
||
|
for (Map.Entry<String, Integer> e : mState.entrySet()) {
|
||
|
if (e.getValue().intValue() == state) {
|
||
|
result.add(e.getKey());
|
||
|
}
|
||
|
}
|
||
|
return result.toArray(new String[result.size()]);
|
||
|
}
|
||
|
|
||
|
public synchronized void addAutoPairingFailure(String address) {
|
||
|
if (mAutoPairingDynamicAddressBlacklist == null) {
|
||
|
mAutoPairingDynamicAddressBlacklist = new ArrayList<String>();
|
||
|
}
|
||
|
|
||
|
updateAutoPairingData(address);
|
||
|
mAutoPairingDynamicAddressBlacklist.add(address);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean isAutoPairingAttemptsInProgress(String address) {
|
||
|
return getAttempt(address) != 0;
|
||
|
}
|
||
|
|
||
|
public synchronized void clearPinAttempts(String address) {
|
||
|
mPinAttempt.remove(address);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean hasAutoPairingFailed(String address) {
|
||
|
if (mAutoPairingDynamicAddressBlacklist == null) return false;
|
||
|
|
||
|
return mAutoPairingDynamicAddressBlacklist.contains(address);
|
||
|
}
|
||
|
|
||
|
public synchronized int getAttempt(String address) {
|
||
|
Integer attempt = mPinAttempt.get(address);
|
||
|
if (attempt == null) {
|
||
|
return 0;
|
||
|
}
|
||
|
return attempt.intValue();
|
||
|
}
|
||
|
|
||
|
public synchronized void attempt(String address) {
|
||
|
Integer attempt = mPinAttempt.get(address);
|
||
|
int newAttempt;
|
||
|
if (attempt == null) {
|
||
|
newAttempt = 1;
|
||
|
} else {
|
||
|
newAttempt = attempt.intValue() + 1;
|
||
|
}
|
||
|
mPinAttempt.put(address, new Integer(newAttempt));
|
||
|
}
|
||
|
|
||
|
private void copyAutoPairingData() {
|
||
|
File file = null;
|
||
|
FileInputStream in = null;
|
||
|
FileOutputStream out = null;
|
||
|
try {
|
||
|
file = new File(DYNAMIC_AUTO_PAIRING_BLACKLIST);
|
||
|
if (file.exists()) return;
|
||
|
|
||
|
in = new FileInputStream(AUTO_PAIRING_BLACKLIST);
|
||
|
out= new FileOutputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
|
||
|
|
||
|
byte[] buf = new byte[1024];
|
||
|
int len;
|
||
|
while ((len = in.read(buf)) > 0) {
|
||
|
out.write(buf, 0, len);
|
||
|
}
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: in copyAutoPairingData");
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: in copyAutoPairingData");
|
||
|
} finally {
|
||
|
if (in != null) {
|
||
|
try {
|
||
|
in.close();
|
||
|
} catch (IOException e) {}
|
||
|
}
|
||
|
if (out != null) {
|
||
|
try {
|
||
|
out.close();
|
||
|
} catch (IOException e) {}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void readAutoPairingData() {
|
||
|
if (mAutoPairingAddressBlacklist != null) return;
|
||
|
copyAutoPairingData();
|
||
|
FileInputStream fstream = null;
|
||
|
try {
|
||
|
fstream = new FileInputStream(DYNAMIC_AUTO_PAIRING_BLACKLIST);
|
||
|
DataInputStream in = new DataInputStream(fstream);
|
||
|
BufferedReader file = new BufferedReader(new InputStreamReader(in));
|
||
|
String line;
|
||
|
while((line = file.readLine()) != null) {
|
||
|
line = line.trim();
|
||
|
if (line.length() == 0 || line.startsWith("//")) continue;
|
||
|
String[] value = line.split("=");
|
||
|
if (value != null && value.length == 2) {
|
||
|
String[] val = value[1].split(",");
|
||
|
if (value[0].equalsIgnoreCase("AddressBlacklist")) {
|
||
|
mAutoPairingAddressBlacklist =
|
||
|
new ArrayList<String>(Arrays.asList(val));
|
||
|
} else if (value[0].equalsIgnoreCase("ExactNameBlacklist")) {
|
||
|
mAutoPairingExactNameBlacklist =
|
||
|
new ArrayList<String>(Arrays.asList(val));
|
||
|
} else if (value[0].equalsIgnoreCase("PartialNameBlacklist")) {
|
||
|
mAutoPairingPartialNameBlacklist =
|
||
|
new ArrayList<String>(Arrays.asList(val));
|
||
|
} else if (value[0].equalsIgnoreCase("DynamicAddressBlacklist")) {
|
||
|
mAutoPairingDynamicAddressBlacklist =
|
||
|
new ArrayList<String>(Arrays.asList(val));
|
||
|
} else {
|
||
|
Log.e(TAG, "Error parsing Auto pairing blacklist file");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: readAutoPairingData" + e.toString());
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: readAutoPairingData" + e.toString());
|
||
|
} finally {
|
||
|
if (fstream != null) {
|
||
|
try {
|
||
|
fstream.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This function adds a bluetooth address to the auto pairing blacklist
|
||
|
// file. These addresses are added to DynamicAddressBlacklistSection
|
||
|
private void updateAutoPairingData(String address) {
|
||
|
BufferedWriter out = null;
|
||
|
try {
|
||
|
out = new BufferedWriter(new FileWriter(DYNAMIC_AUTO_PAIRING_BLACKLIST, true));
|
||
|
StringBuilder str = new StringBuilder();
|
||
|
if (mAutoPairingDynamicAddressBlacklist.size() == 0) {
|
||
|
str.append("DynamicAddressBlacklist=");
|
||
|
}
|
||
|
str.append(address);
|
||
|
str.append(",");
|
||
|
out.write(str.toString());
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: updateAutoPairingData" + e.toString());
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: updateAutoPairingData" + e.toString());
|
||
|
} finally {
|
||
|
if (out != null) {
|
||
|
try {
|
||
|
out.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static String toBondStateString(int bondState) {
|
||
|
switch (bondState) {
|
||
|
case BluetoothDevice.BOND_NONE:
|
||
|
return "not bonded";
|
||
|
case BluetoothDevice.BOND_BONDING:
|
||
|
return "bonding";
|
||
|
case BluetoothDevice.BOND_BONDED:
|
||
|
return "bonded";
|
||
|
default:
|
||
|
return "??????";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized boolean isAdapterPropertiesEmpty() {
|
||
|
return mAdapterProperties.isEmpty();
|
||
|
}
|
||
|
|
||
|
/*package*/synchronized void getAllProperties() {
|
||
|
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
mAdapterProperties.clear();
|
||
|
|
||
|
String properties[] = (String [])getAdapterPropertiesNative();
|
||
|
// The String Array consists of key-value pairs.
|
||
|
if (properties == null) {
|
||
|
Log.e(TAG, "*Error*: GetAdapterProperties returned NULL");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < properties.length; i++) {
|
||
|
String name = properties[i];
|
||
|
String newValue = null;
|
||
|
int len;
|
||
|
if (name == null) {
|
||
|
Log.e(TAG, "Error:Adapter Property at index" + i + "is null");
|
||
|
continue;
|
||
|
}
|
||
|
if (name.equals("Devices") || name.equals("UUIDs")) {
|
||
|
StringBuilder str = new StringBuilder();
|
||
|
len = Integer.valueOf(properties[++i]);
|
||
|
for (int j = 0; j < len; j++) {
|
||
|
str.append(properties[++i]);
|
||
|
str.append(",");
|
||
|
}
|
||
|
if (len > 0) {
|
||
|
newValue = str.toString();
|
||
|
}
|
||
|
} else {
|
||
|
newValue = properties[++i];
|
||
|
}
|
||
|
mAdapterProperties.put(name, newValue);
|
||
|
}
|
||
|
|
||
|
// Add adapter object path property.
|
||
|
String adapterPath = getAdapterPathNative();
|
||
|
if (adapterPath != null)
|
||
|
mAdapterProperties.put("ObjectPath", adapterPath + "/dev_");
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized void setProperty(String name, String value) {
|
||
|
mAdapterProperties.put(name, value);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setName(String name) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (name == null) {
|
||
|
return false;
|
||
|
}
|
||
|
return setPropertyString("Name", name);
|
||
|
}
|
||
|
|
||
|
//TODO(): setPropertyString, setPropertyInteger, setPropertyBoolean
|
||
|
// Either have a single property function with Object as the parameter
|
||
|
// or have a function for each property and then obfuscate in the JNI layer.
|
||
|
// The following looks dirty.
|
||
|
private boolean setPropertyString(String key, String value) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
return setAdapterPropertyStringNative(key, value);
|
||
|
}
|
||
|
|
||
|
private boolean setPropertyInteger(String key, int value) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
return setAdapterPropertyIntegerNative(key, value);
|
||
|
}
|
||
|
|
||
|
private boolean setPropertyBoolean(String key, boolean value) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
return setAdapterPropertyBooleanNative(key, value ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the discoverability window for the device. A timeout of zero
|
||
|
* makes the device permanently discoverable (if the device is
|
||
|
* discoverable). Setting the timeout to a nonzero value does not make
|
||
|
* a device discoverable; you need to call setMode() to make the device
|
||
|
* explicitly discoverable.
|
||
|
*
|
||
|
* @param timeout The discoverable timeout in seconds.
|
||
|
*/
|
||
|
public synchronized boolean setDiscoverableTimeout(int timeout) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
return setPropertyInteger("DiscoverableTimeout", timeout);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setScanMode(int mode, int duration) {
|
||
|
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS,
|
||
|
"Need WRITE_SECURE_SETTINGS permission");
|
||
|
boolean pairable = false;
|
||
|
boolean discoverable = false;
|
||
|
|
||
|
switch (mode) {
|
||
|
case BluetoothAdapter.SCAN_MODE_NONE:
|
||
|
pairable = false;
|
||
|
discoverable = false;
|
||
|
mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
|
||
|
break;
|
||
|
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
|
||
|
pairable = true;
|
||
|
discoverable = false;
|
||
|
mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
|
||
|
break;
|
||
|
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
|
||
|
setDiscoverableTimeout(duration);
|
||
|
pairable = true;
|
||
|
discoverable = true;
|
||
|
mHandler.removeMessages(MESSAGE_DISCOVERABLE_TIMEOUT);
|
||
|
if (duration > 0) {
|
||
|
Message msg = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_TIMEOUT);
|
||
|
mHandler.sendMessageDelayed(msg, duration * 1000);
|
||
|
Message msgd = mHandler.obtainMessage(MESSAGE_DISCOVERABLE_CHANGE_TIMEOUT);
|
||
|
mHandler.sendMessageDelayed(msgd, 200);
|
||
|
}
|
||
|
if (DBG) Log.d(TAG, "BT Discoverable for " + duration + " seconds");
|
||
|
break;
|
||
|
default:
|
||
|
Log.w(TAG, "Requested invalid scan mode " + mode);
|
||
|
return false;
|
||
|
}
|
||
|
setPropertyBoolean("Pairable", pairable);
|
||
|
setPropertyBoolean("Discoverable", discoverable);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String getProperty(String name) {
|
||
|
if (!isEnabledInternal()) return null;
|
||
|
return getPropertyInternal(name);
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String getPropertyInternal(String name) {
|
||
|
if (!mAdapterProperties.isEmpty())
|
||
|
return mAdapterProperties.get(name);
|
||
|
getAllProperties();
|
||
|
return mAdapterProperties.get(name);
|
||
|
}
|
||
|
|
||
|
public synchronized String getAddress() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return getProperty("Address");
|
||
|
}
|
||
|
|
||
|
public synchronized String getName() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return getProperty("Name");
|
||
|
}
|
||
|
|
||
|
public synchronized String getCOD() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return getProperty("Class");
|
||
|
}
|
||
|
|
||
|
public boolean setBluetoothClass(String address, int classOfDevice) {
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
return setDevicePropertyIntegerNative(getObjectPathFromAddress(address), "Class",
|
||
|
classOfDevice);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Returns the user-friendly name of a remote device. This value is
|
||
|
* returned from our local cache, which is updated when onPropertyChange
|
||
|
* event is received.
|
||
|
* Do not expect to retrieve the updated remote name immediately after
|
||
|
* changing the name on the remote device.
|
||
|
*
|
||
|
* @param address Bluetooth address of remote device.
|
||
|
*
|
||
|
* @return The user-friendly name of the specified remote device.
|
||
|
*/
|
||
|
public synchronized String getRemoteName(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return null;
|
||
|
}
|
||
|
return getRemoteDeviceProperty(address, "Name");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the discoverability window for the device. A timeout of zero
|
||
|
* means that the device is permanently discoverable (if the device is
|
||
|
* in the discoverable mode).
|
||
|
*
|
||
|
* @return The discoverability window of the device, in seconds. A negative
|
||
|
* value indicates an error.
|
||
|
*/
|
||
|
public synchronized int getDiscoverableTimeout() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
String timeout = getProperty("DiscoverableTimeout");
|
||
|
if (timeout != null)
|
||
|
return Integer.valueOf(timeout);
|
||
|
else
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
public synchronized int getScanMode() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal())
|
||
|
return BluetoothAdapter.SCAN_MODE_NONE;
|
||
|
|
||
|
boolean pairable = getProperty("Pairable").equals("true");
|
||
|
boolean discoverable = getProperty("Discoverable").equals("true");
|
||
|
return bluezStringToScanMode (pairable, discoverable);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean startDiscovery() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
|
||
|
log("**** startDiscovery");
|
||
|
|
||
|
return startDiscoveryNative();
|
||
|
}
|
||
|
|
||
|
public synchronized boolean cancelDiscovery() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
return stopDiscoveryNative();
|
||
|
}
|
||
|
|
||
|
public synchronized boolean isDiscovering() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return mIsDiscovering;
|
||
|
}
|
||
|
|
||
|
/* package */ void setIsDiscovering(boolean isDiscovering) {
|
||
|
mIsDiscovering = isDiscovering;
|
||
|
}
|
||
|
|
||
|
private boolean isBondingFeasible(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
address = address.toUpperCase();
|
||
|
|
||
|
if (mBondState.getPendingOutgoingBonding() != null) {
|
||
|
log("Ignoring createBond(): another device is bonding");
|
||
|
// a different device is currently bonding, fail
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Check for bond state only if we are not performing auto
|
||
|
// pairing exponential back-off attempts.
|
||
|
if (!mBondState.isAutoPairingAttemptsInProgress(address) &&
|
||
|
mBondState.getBondState(address) != BluetoothDevice.BOND_NONE) {
|
||
|
log("Ignoring createBond(): this device is already bonding or bonded");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (address.equals(mDockAddress)) {
|
||
|
if (!writeDockPin()) {
|
||
|
log("Error while writing Pin for the dock");
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean createBond(String address) {
|
||
|
if (!isBondingFeasible(address)) return false;
|
||
|
|
||
|
if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mBondState.setPendingOutgoingBonding(address);
|
||
|
mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean createBondOutOfBand(String address, byte[] hash,
|
||
|
byte[] randomizer) {
|
||
|
if (!isBondingFeasible(address)) return false;
|
||
|
|
||
|
if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
setDeviceOutOfBandData(address, hash, randomizer);
|
||
|
mBondState.setPendingOutgoingBonding(address);
|
||
|
mBondState.setBondState(address, BluetoothDevice.BOND_BONDING);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash,
|
||
|
byte[] randomizer) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer);
|
||
|
|
||
|
if (DBG) {
|
||
|
log("Setting out of band data for:" + address + ":" +
|
||
|
Arrays.toString(hash) + ":" + Arrays.toString(randomizer));
|
||
|
}
|
||
|
|
||
|
mDeviceOobData.put(address, value);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) {
|
||
|
return mDeviceOobData.get(device.getAddress());
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized byte[] readOutOfBandData() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return null;
|
||
|
|
||
|
return readAdapterOutOfBandDataNative();
|
||
|
}
|
||
|
|
||
|
public synchronized boolean cancelBondProcess(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
address = address.toUpperCase();
|
||
|
if (mBondState.getBondState(address) != BluetoothDevice.BOND_BONDING) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
|
||
|
BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
|
||
|
cancelDeviceCreationNative(address);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean removeBond(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
state.sendMessage(BluetoothDeviceProfileState.UNPAIR);
|
||
|
return true;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public synchronized boolean removeBondInternal(String address) {
|
||
|
// Unset the trusted device state and then unpair
|
||
|
setTrust(address, false);
|
||
|
return removeDeviceNative(getObjectPathFromAddress(address));
|
||
|
}
|
||
|
|
||
|
public synchronized String[] listBonds() {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return mBondState.listInState(BluetoothDevice.BOND_BONDED);
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String[] listInState(int state) {
|
||
|
return mBondState.listInState(state);
|
||
|
}
|
||
|
|
||
|
public synchronized int getBondState(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return BluetoothDevice.ERROR;
|
||
|
}
|
||
|
return mBondState.getBondState(address.toUpperCase());
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized boolean setBondState(String address, int state) {
|
||
|
return setBondState(address, state, 0);
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized boolean setBondState(String address, int state, int reason) {
|
||
|
mBondState.setBondState(address.toUpperCase(), state);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean isBluetoothDock(String address) {
|
||
|
SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
|
||
|
mContext.MODE_PRIVATE);
|
||
|
|
||
|
return sp.contains(SHARED_PREFERENCE_DOCK_ADDRESS + address);
|
||
|
}
|
||
|
|
||
|
/*package*/ boolean isRemoteDeviceInCache(String address) {
|
||
|
return (mDeviceProperties.get(address) != null);
|
||
|
}
|
||
|
|
||
|
/*package*/ String[] getRemoteDeviceProperties(String address) {
|
||
|
Log.d(TAG, "getRemoteDeviceProperties: " + address);
|
||
|
if (!isEnabledInternal()) return null;
|
||
|
|
||
|
String objectPath = getObjectPathFromAddress(address);
|
||
|
return (String [])getDevicePropertiesNative(objectPath);
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized String getRemoteDeviceProperty(String address, String property) {
|
||
|
Map<String, String> properties = mDeviceProperties.get(address);
|
||
|
Log.d(TAG, "getRemoteDeviceProperty: " + property + ", " + address);
|
||
|
if (properties != null) {
|
||
|
return properties.get(property);
|
||
|
} else {
|
||
|
// Query for remote device properties, again.
|
||
|
// We will need to reload the cache when we switch Bluetooth on / off
|
||
|
// or if we crash.
|
||
|
if (updateRemoteDevicePropertiesCache(address))
|
||
|
return getRemoteDeviceProperty(address, property);
|
||
|
}
|
||
|
Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized boolean updateRemoteDevicePropertiesCache(String address) {
|
||
|
String[] propValues = getRemoteDeviceProperties(address);
|
||
|
Log.d(TAG, "updateRemoteDevicePropertiesCache: " + address);
|
||
|
if (propValues != null) {
|
||
|
addRemoteDeviceProperties(address, propValues);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private String getUpdatedRemoteDeviceProperty(String address,
|
||
|
String property) {
|
||
|
String objectPath = getObjectPathFromAddress(address);
|
||
|
String[] propValues = (String [])getDevicePropertiesNative(objectPath);
|
||
|
if (propValues != null) {
|
||
|
addRemoteDeviceProperties(address, propValues);
|
||
|
return getRemoteDeviceProperty(address, property);
|
||
|
}
|
||
|
Log.e(TAG, "getRemoteDeviceProperty: " + property + "not present:" + address);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized void addRemoteDeviceProperties(String address, String[] properties) {
|
||
|
/*
|
||
|
* We get a DeviceFound signal every time RSSI changes or name changes.
|
||
|
* Don't create a new Map object every time */
|
||
|
Map<String, String> propertyValues = mDeviceProperties.get(address);
|
||
|
if (propertyValues == null) {
|
||
|
propertyValues = new HashMap<String, String>();
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < properties.length; i++) {
|
||
|
String name = properties[i];
|
||
|
String newValue = null;
|
||
|
int len;
|
||
|
if (name == null) {
|
||
|
Log.e(TAG, "Error: Remote Device Property at index" + i + "is null");
|
||
|
continue;
|
||
|
}
|
||
|
if (name.equals("UUIDs") || name.equals("Nodes") || name.equals("Services")) {
|
||
|
StringBuilder str = new StringBuilder();
|
||
|
len = Integer.valueOf(properties[++i]);
|
||
|
for (int j = 0; j < len; j++) {
|
||
|
str.append(properties[++i]);
|
||
|
str.append(",");
|
||
|
}
|
||
|
if (len > 0) {
|
||
|
newValue = str.toString();
|
||
|
}
|
||
|
} else {
|
||
|
newValue = properties[++i];
|
||
|
}
|
||
|
propertyValues.put(name, newValue);
|
||
|
}
|
||
|
|
||
|
mDeviceProperties.put(address, propertyValues);
|
||
|
|
||
|
// We have added a new remote device or updated its properties.
|
||
|
// Also update the serviceChannel cache.
|
||
|
updateDeviceServiceChannelCache(address);
|
||
|
}
|
||
|
|
||
|
/* package */ void removeRemoteDeviceProperties(String address) {
|
||
|
mDeviceProperties.remove(address);
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized void setRemoteDeviceProperty(String address, String name,
|
||
|
String value) {
|
||
|
Map <String, String> propVal = mDeviceProperties.get(address);
|
||
|
if (propVal != null) {
|
||
|
propVal.put(name, value);
|
||
|
mDeviceProperties.put(address, propVal);
|
||
|
} else {
|
||
|
Log.e(TAG, "setRemoteDeviceProperty for a device not in cache:" + address);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the remote device trust state.
|
||
|
*
|
||
|
* @return boolean to indicate operation success or fail
|
||
|
*/
|
||
|
public synchronized boolean setTrust(String address, boolean value) {
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
return setDevicePropertyBooleanNative(getObjectPathFromAddress(address), "Trusted",
|
||
|
value ? 1 : 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the remote device trust state as boolean.
|
||
|
* Note: this value may be
|
||
|
* retrieved from cache if we retrieved the data before *
|
||
|
*
|
||
|
* @return boolean to indicate trust or untrust state
|
||
|
*/
|
||
|
public synchronized boolean getTrustState(String address) {
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
String val = getRemoteDeviceProperty(address, "Trusted");
|
||
|
if (val == null) {
|
||
|
return false;
|
||
|
} else {
|
||
|
return val.equals("true") ? true : false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the remote major, minor classes encoded as a 32-bit
|
||
|
* integer.
|
||
|
*
|
||
|
* Note: this value is retrieved from cache, because we get it during
|
||
|
* remote-device discovery.
|
||
|
*
|
||
|
* @return 32-bit integer encoding the remote major, minor, and service
|
||
|
* classes.
|
||
|
*/
|
||
|
public synchronized int getRemoteClass(String address) {
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
return BluetoothClass.ERROR;
|
||
|
}
|
||
|
String val = getRemoteDeviceProperty(address, "Class");
|
||
|
if (val == null)
|
||
|
return BluetoothClass.ERROR;
|
||
|
else {
|
||
|
return Integer.valueOf(val);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the UUIDs supported by the remote device
|
||
|
*
|
||
|
* @return array of 128bit ParcelUuids
|
||
|
*/
|
||
|
public synchronized ParcelUuid[] getRemoteUuids(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return null;
|
||
|
}
|
||
|
return getUuidFromCache(address);
|
||
|
}
|
||
|
|
||
|
private ParcelUuid[] getUuidFromCache(String address) {
|
||
|
String value = getRemoteDeviceProperty(address, "UUIDs");
|
||
|
if (value == null) return null;
|
||
|
|
||
|
String[] uuidStrings = null;
|
||
|
// The UUIDs are stored as a "," separated string.
|
||
|
uuidStrings = value.split(",");
|
||
|
ParcelUuid[] uuids = new ParcelUuid[uuidStrings.length];
|
||
|
|
||
|
for (int i = 0; i < uuidStrings.length; i++) {
|
||
|
uuids[i] = ParcelUuid.fromString(uuidStrings[i]);
|
||
|
}
|
||
|
return uuids;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Connect and fetch new UUID's using SDP.
|
||
|
* The UUID's found are broadcast as intents.
|
||
|
* Optionally takes a uuid and callback to fetch the RFCOMM channel for the
|
||
|
* a given uuid.
|
||
|
* TODO: Don't wait UUID_INTENT_DELAY to broadcast UUID intents on success
|
||
|
* TODO: Don't wait UUID_INTENT_DELAY to handle the failure case for
|
||
|
* callback and broadcast intents.
|
||
|
*/
|
||
|
public synchronized boolean fetchRemoteUuids(String address, ParcelUuid uuid,
|
||
|
IBluetoothCallback callback) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
RemoteService service = new RemoteService(address, uuid);
|
||
|
if (uuid != null && mUuidCallbackTracker.get(service) != null) {
|
||
|
// An SDP query for this address & uuid is already in progress
|
||
|
// Do not add this callback for the uuid
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (mUuidIntentTracker.contains(address)) {
|
||
|
// An SDP query for this address is already in progress
|
||
|
// Add this uuid onto the in-progress SDP query
|
||
|
if (uuid != null) {
|
||
|
mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
boolean ret;
|
||
|
// Just do the SDP if the device is already created and UUIDs are not
|
||
|
// NULL, else create the device and then do SDP.
|
||
|
if (isRemoteDeviceInCache(address) && getRemoteUuids(address) != null && findDeviceNative(address) != null) {
|
||
|
String path = getObjectPathFromAddress(address);
|
||
|
if (path == null) return false;
|
||
|
|
||
|
// Use an empty string for the UUID pattern
|
||
|
ret = discoverServicesNative(path, "");
|
||
|
} else {
|
||
|
ret = createDeviceNative(address);
|
||
|
}
|
||
|
|
||
|
mUuidIntentTracker.add(address);
|
||
|
if (uuid != null) {
|
||
|
mUuidCallbackTracker.put(new RemoteService(address, uuid), callback);
|
||
|
}
|
||
|
|
||
|
Message message = mHandler.obtainMessage(MESSAGE_UUID_INTENT);
|
||
|
message.obj = address;
|
||
|
mHandler.sendMessageDelayed(message, UUID_INTENT_DELAY);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the rfcomm channel associated with the UUID.
|
||
|
* Pulls records from the cache only.
|
||
|
*
|
||
|
* @param address Address of the remote device
|
||
|
* @param uuid ParcelUuid of the service attribute
|
||
|
*
|
||
|
* @return rfcomm channel associated with the service attribute
|
||
|
* -1 on error
|
||
|
*/
|
||
|
public int getRemoteServiceChannel(String address, ParcelUuid uuid) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return -1;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return BluetoothDevice.ERROR;
|
||
|
}
|
||
|
// Check if we are recovering from a crash.
|
||
|
if (mDeviceProperties.isEmpty()) {
|
||
|
if (!updateRemoteDevicePropertiesCache(address))
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
Map<ParcelUuid, Integer> value = mDeviceServiceChannelCache.get(address);
|
||
|
if (value != null && value.containsKey(uuid))
|
||
|
return value.get(uuid);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Gets the remote features list associated with the feature name.
|
||
|
* Pulls records from the cache only.
|
||
|
*
|
||
|
* @param address Address of the remote device
|
||
|
* @param feature the name of feature to get
|
||
|
*
|
||
|
* @return feature list string value
|
||
|
* null on error
|
||
|
*/
|
||
|
public String getRemoteFeature(String address, String feature) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return null;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
Map<String, String> value = mDeviceFeatureCache.get(address);
|
||
|
if (value != null && value.containsKey(feature))
|
||
|
return value.get(feature);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the L2CAP PSM associated with the UUID.
|
||
|
* Pulls records from the cache only.
|
||
|
*
|
||
|
* @param address Address of the remote device
|
||
|
* @param uuid ParcelUuid of the service attribute
|
||
|
*
|
||
|
* @return L2CAP PSM associated with the service attribute
|
||
|
* -1 on error
|
||
|
*/
|
||
|
public int getRemoteL2capPsm(String address, ParcelUuid uuid) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return -1;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return BluetoothDevice.ERROR;
|
||
|
}
|
||
|
// Check if we are recovering from a crash.
|
||
|
if (mDeviceProperties.isEmpty()) {
|
||
|
if (!updateRemoteDevicePropertiesCache(address)) {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Map<ParcelUuid, Integer> value = mDeviceL2capPsmCache.get(address);
|
||
|
if (value != null && value.containsKey(uuid)) {
|
||
|
return value.get(uuid);
|
||
|
}
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setPin(String address, byte[] pin) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (pin == null || pin.length <= 0 || pin.length > 16 ||
|
||
|
!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "setPin(" + address + ") called but no native data available, " +
|
||
|
"ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
|
||
|
" or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
// bluez API wants pin as a string
|
||
|
String pinString;
|
||
|
try {
|
||
|
pinString = new String(pin, "UTF8");
|
||
|
} catch (UnsupportedEncodingException uee) {
|
||
|
Log.e(TAG, "UTF8 not supported?!?");
|
||
|
return false;
|
||
|
}
|
||
|
return setPinNative(address, pinString, data.intValue());
|
||
|
}
|
||
|
|
||
|
public synchronized boolean sapAuthorize(String address, boolean access) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getAuthorizationRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "sapAuthorize(" + address + ") called but no native data available, " +
|
||
|
"ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
|
||
|
" or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return sapAuthorizeNative(address, access, data.intValue());
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setPasskey(String address, int passkey) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (passkey < 0 || passkey > 999999 || !BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
|
||
|
"ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
|
||
|
" or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
return setPasskeyNative(address, passkey, data.intValue());
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setPairingConfirmation(String address, boolean confirm) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "setPasskey(" + address + ") called but no native data available, " +
|
||
|
"ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
|
||
|
" or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
return setPairingConfirmationNative(address, confirm, data.intValue());
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setRemoteOutOfBandData(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " +
|
||
|
"ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" +
|
||
|
" or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Pair<byte[], byte[]> val = mDeviceOobData.get(address);
|
||
|
byte[] hash, randomizer;
|
||
|
if (val == null) {
|
||
|
// TODO: check what should be passed in this case.
|
||
|
hash = new byte[16];
|
||
|
randomizer = new byte[16];
|
||
|
} else {
|
||
|
hash = val.first;
|
||
|
randomizer = val.second;
|
||
|
}
|
||
|
return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue());
|
||
|
}
|
||
|
|
||
|
public synchronized boolean cancelPairingUserInput(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
|
||
|
"Need BLUETOOTH_ADMIN permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
mBondState.setBondState(address, BluetoothDevice.BOND_NONE,
|
||
|
BluetoothDevice.UNBOND_REASON_AUTH_CANCELED);
|
||
|
address = address.toUpperCase();
|
||
|
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
|
||
|
if (data == null) {
|
||
|
Log.w(TAG, "cancelUserInputNative(" + address + ") called but no native data " +
|
||
|
"available, ignoring. Maybe the PasskeyAgent Request was already cancelled " +
|
||
|
"by the remote or by bluez.\n");
|
||
|
return false;
|
||
|
}
|
||
|
return cancelPairingUserInputNative(address, data.intValue());
|
||
|
}
|
||
|
|
||
|
/*package*/ void updateDeviceServiceChannelCache(String address) {
|
||
|
if (!isEnabledInternal()) {
|
||
|
log("Bluetooth is not on");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ParcelUuid[] deviceUuids = getRemoteUuids(address);
|
||
|
// We are storing the RFCOMM channel numbers and L2CAP PSMs
|
||
|
// only for the uuids we are interested in.
|
||
|
int channel;
|
||
|
int psm;
|
||
|
if (DBG) log("updateDeviceServiceChannelCache(" + address + ")");
|
||
|
|
||
|
// Remove service channel timeout handler
|
||
|
mHandler.removeMessages(MESSAGE_UUID_INTENT);
|
||
|
|
||
|
ArrayList<ParcelUuid> applicationUuids = new ArrayList();
|
||
|
|
||
|
synchronized (this) {
|
||
|
for (RemoteService service : mUuidCallbackTracker.keySet()) {
|
||
|
if (service.address.equals(address)) {
|
||
|
applicationUuids.add(service.uuid);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Map <ParcelUuid, Integer> rfcommValue = new HashMap<ParcelUuid, Integer>();
|
||
|
Map <ParcelUuid, Integer> l2capPsmValue = new HashMap<ParcelUuid, Integer>();
|
||
|
Map <String, String> feature = new HashMap<String, String>();
|
||
|
|
||
|
// Retrieve RFCOMM channel and L2CAP PSM (if available) for default uuids
|
||
|
for (ParcelUuid uuid : RFCOMM_UUIDS) {
|
||
|
if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
|
||
|
psm = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), SDP_ATTR_GOEP_L2CAP_PSM);
|
||
|
l2capPsmValue.put(uuid, psm);
|
||
|
if (DBG) log("\tuuid(system): " + uuid + " PSM: "+ psm );
|
||
|
if(!BluetoothUuid.isPrintingStatus(uuid)){
|
||
|
channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), SDP_ATTR_PROTO_DESC_LIST);
|
||
|
rfcommValue.put(uuid, channel);
|
||
|
if (DBG) log("\tuuid(system): " + uuid + " SCN: "+ channel );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Retrieve Additional RFCOMM Channel for BPP
|
||
|
if (BluetoothUuid.isPrintingStatus(uuid)) {
|
||
|
channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), 0x000d); // find additional Channel Number
|
||
|
if (DBG) log("\tuuid(system): " + uuid + " " + channel);
|
||
|
rfcommValue.put(uuid, channel);
|
||
|
}
|
||
|
|
||
|
if (BluetoothUuid.isDirectPrinting(uuid)) {
|
||
|
String SupportedFormats;
|
||
|
SupportedFormats = getDeviceStringAttrValue(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), 0x0350); // find additional Channel Number
|
||
|
if (DBG) log("\tSupportedFormats: " + uuid + " " + SupportedFormats);
|
||
|
feature.put("SupportedFormats", SupportedFormats);
|
||
|
mDeviceFeatureCache.put(address, feature);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
// Retrieve RFCOMM channel and L2CAP PSM (if available) for
|
||
|
// application requested uuids
|
||
|
for (ParcelUuid uuid : applicationUuids) {
|
||
|
if (BluetoothUuid.isUuidPresent(deviceUuids, uuid)) {
|
||
|
psm = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), SDP_ATTR_GOEP_L2CAP_PSM);
|
||
|
channel = getDeviceServiceChannelNative(getObjectPathFromAddress(address),
|
||
|
uuid.toString(), SDP_ATTR_PROTO_DESC_LIST);
|
||
|
if (DBG) log("\tuuid(application): " + uuid + " SCN: " + channel +
|
||
|
" PSM: " + psm);
|
||
|
rfcommValue.put(uuid, channel);
|
||
|
l2capPsmValue.put(uuid, psm);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
synchronized (this) {
|
||
|
// Make application callbacks (RFCOMM channels only)
|
||
|
for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
|
||
|
iter.hasNext();) {
|
||
|
RemoteService service = iter.next();
|
||
|
if (service.address.equals(address)) {
|
||
|
channel = -1;
|
||
|
if (rfcommValue.get(service.uuid) != null) {
|
||
|
channel = rfcommValue.get(service.uuid);
|
||
|
}
|
||
|
if (channel != -1) {
|
||
|
if (DBG) log("Making callback for " + service.uuid + " with result " +
|
||
|
channel);
|
||
|
IBluetoothCallback callback = mUuidCallbackTracker.get(service);
|
||
|
if (callback != null) {
|
||
|
try {
|
||
|
callback.onRfcommChannelFound(channel);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
}
|
||
|
|
||
|
iter.remove();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Update cache
|
||
|
mDeviceServiceChannelCache.put(address, rfcommValue);
|
||
|
mDeviceL2capPsmCache.put(address, l2capPsmValue);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* b is a handle to a Binder instance, so that this service can be notified
|
||
|
* for Applications that terminate unexpectedly, to clean there service
|
||
|
* records
|
||
|
*/
|
||
|
public synchronized int addRfcommServiceRecord(String serviceName, ParcelUuid uuid,
|
||
|
int channel, IBinder b) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return -1;
|
||
|
|
||
|
if (serviceName == null || uuid == null || channel < 1 ||
|
||
|
channel > BluetoothSocket.MAX_RFCOMM_CHANNEL) {
|
||
|
return -1;
|
||
|
}
|
||
|
if (BluetoothUuid.isUuidPresent(BluetoothUuid.RESERVED_UUIDS, uuid)) {
|
||
|
Log.w(TAG, "Attempted to register a reserved UUID: " + uuid);
|
||
|
return -1;
|
||
|
}
|
||
|
int handle = addRfcommServiceRecordNative(serviceName,
|
||
|
uuid.getUuid().getMostSignificantBits(), uuid.getUuid().getLeastSignificantBits(),
|
||
|
(short)channel);
|
||
|
if (DBG) log("new handle " + Integer.toHexString(handle));
|
||
|
if (handle == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int pid = Binder.getCallingPid();
|
||
|
mServiceRecordToPid.put(new Integer(handle), new Integer(pid));
|
||
|
try {
|
||
|
b.linkToDeath(new Reaper(handle, pid), 0);
|
||
|
} catch (RemoteException e) {}
|
||
|
return handle;
|
||
|
}
|
||
|
|
||
|
public void removeServiceRecord(int handle) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
checkAndRemoveRecord(handle, Binder.getCallingPid());
|
||
|
}
|
||
|
|
||
|
public int registerEl2capConnection(IBluetoothCallback callback, int desiredAmpPolicy) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
return mConnectionManager.registerEl2capConnection(callback, desiredAmpPolicy);
|
||
|
}
|
||
|
|
||
|
public void deregisterEl2capConnection(int handle) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
mConnectionManager.deregisterEl2capConnection(handle);
|
||
|
}
|
||
|
|
||
|
public int getEffectiveAmpPolicy(int desiredPolicy) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
return mConnectionManager.getEffectiveAmpPolicy(desiredPolicy);
|
||
|
}
|
||
|
|
||
|
public boolean setDesiredAmpPolicy(int handle, int policy) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
return mConnectionManager.setDesiredAmpPolicy(handle, policy);
|
||
|
}
|
||
|
|
||
|
public void setUseWifiForBtTransfers(boolean useWifi) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
|
||
|
"Need BLUETOOTH permission");
|
||
|
mConnectionManager.setUseWifiForBtTransfers(useWifi);
|
||
|
}
|
||
|
|
||
|
private synchronized void checkAndRemoveRecord(int handle, int pid) {
|
||
|
Integer handleInt = new Integer(handle);
|
||
|
Integer owner = mServiceRecordToPid.get(handleInt);
|
||
|
if (owner != null && pid == owner.intValue()) {
|
||
|
if (DBG) log("Removing service record " + Integer.toHexString(handle) + " for pid " +
|
||
|
pid);
|
||
|
mServiceRecordToPid.remove(handleInt);
|
||
|
removeServiceRecordNative(handle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private class Reaper implements IBinder.DeathRecipient {
|
||
|
int pid;
|
||
|
int handle;
|
||
|
Reaper(int handle, int pid) {
|
||
|
this.pid = pid;
|
||
|
this.handle = handle;
|
||
|
}
|
||
|
public void binderDied() {
|
||
|
synchronized (BluetoothService.this) {
|
||
|
if (DBG) log("Tracked app " + pid + " died");
|
||
|
checkAndRemoveRecord(handle, pid);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||
|
@Override
|
||
|
public void onReceive(Context context, Intent intent) {
|
||
|
if (intent == null) return;
|
||
|
|
||
|
String action = intent.getAction();
|
||
|
if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
|
||
|
ContentResolver resolver = context.getContentResolver();
|
||
|
// Query the airplane mode from Settings.System just to make sure that
|
||
|
// some random app is not sending this intent and disabling bluetooth
|
||
|
boolean enabled = !isAirplaneModeOn();
|
||
|
// If bluetooth is currently expected to be on, then enable or disable bluetooth
|
||
|
if (Settings.Secure.getInt(resolver, Settings.Secure.BLUETOOTH_ON, 0) > 0) {
|
||
|
if (enabled) {
|
||
|
enable(false);
|
||
|
} else {
|
||
|
disable(false);
|
||
|
}
|
||
|
}
|
||
|
} else if (Intent.ACTION_DOCK_EVENT.equals(action)) {
|
||
|
int state = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
|
||
|
Intent.EXTRA_DOCK_STATE_UNDOCKED);
|
||
|
if (DBG) Log.v(TAG, "Received ACTION_DOCK_EVENT with State:" + state);
|
||
|
if (state == Intent.EXTRA_DOCK_STATE_UNDOCKED) {
|
||
|
mDockAddress = null;
|
||
|
mDockPin = null;
|
||
|
} else {
|
||
|
SharedPreferences.Editor editor =
|
||
|
mContext.getSharedPreferences(SHARED_PREFERENCES_NAME,
|
||
|
mContext.MODE_PRIVATE).edit();
|
||
|
editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true);
|
||
|
editor.apply();
|
||
|
}
|
||
|
} else if (BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED.equals(action)) {
|
||
|
int audioState = intent.getIntExtra(BluetoothHeadset.EXTRA_AUDIO_STATE,
|
||
|
BluetoothHeadset.AUDIO_STATE_DISCONNECTED);
|
||
|
mConnectionManager.setScoAudioActive(audioState == BluetoothHeadset.AUDIO_STATE_CONNECTED);
|
||
|
} else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(action)) {
|
||
|
BluetoothA2dp btA2dp = new BluetoothA2dp(context);
|
||
|
if (btA2dp != null) {
|
||
|
mConnectionManager.setA2dpAudioActive(btA2dp.isPlayingSink());
|
||
|
} else {
|
||
|
Log.e(TAG, "BluetoothA2dp service not available");
|
||
|
}
|
||
|
} else if (SAP_ACCESS_ALLOWED_ACTION.equals(action)) {
|
||
|
Log.i(TAG, "Received SAP_ACCESS_ALLOWED_ACTION");
|
||
|
String address = intent.getStringExtra("address");
|
||
|
boolean alwaysAllow = intent.getBooleanExtra(SAP_EXTRA_ALWAYS_ALLOWED, false);
|
||
|
if (address != null) {
|
||
|
sapAuthorize(address, true);
|
||
|
if (alwaysAllow) {
|
||
|
Log.i(TAG, "Setting trust state to true");
|
||
|
setTrust(address,true);
|
||
|
}
|
||
|
} else {
|
||
|
Log.e(TAG, "address is null");
|
||
|
}
|
||
|
} else if (SAP_ACCESS_DISALLOWED_ACTION.equals(action)) {
|
||
|
Log.i(TAG, "Received SAP_ACCESS_DISALLOWED_ACTION");
|
||
|
String address = intent.getStringExtra("address");
|
||
|
if (address != null) {
|
||
|
sapAuthorize(address, false);
|
||
|
} else {
|
||
|
Log.e(TAG, "address is null");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
private void registerForAirplaneMode(IntentFilter filter) {
|
||
|
final ContentResolver resolver = mContext.getContentResolver();
|
||
|
final String airplaneModeRadios = Settings.System.getString(resolver,
|
||
|
Settings.System.AIRPLANE_MODE_RADIOS);
|
||
|
final String toggleableRadios = Settings.System.getString(resolver,
|
||
|
Settings.System.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
|
||
|
|
||
|
mIsAirplaneSensitive = airplaneModeRadios == null ? true :
|
||
|
airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
|
||
|
mIsAirplaneToggleable = toggleableRadios == null ? false :
|
||
|
toggleableRadios.contains(Settings.System.RADIO_BLUETOOTH);
|
||
|
|
||
|
if (mIsAirplaneSensitive) {
|
||
|
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Returns true if airplane mode is currently on */
|
||
|
private final boolean isAirplaneModeOn() {
|
||
|
return Settings.System.getInt(mContext.getContentResolver(),
|
||
|
Settings.System.AIRPLANE_MODE_ON, 0) == 1;
|
||
|
}
|
||
|
|
||
|
/* Broadcast the Uuid intent */
|
||
|
/*package*/ synchronized void sendUuidIntent(String address) {
|
||
|
ParcelUuid[] uuid = getUuidFromCache(address);
|
||
|
Intent intent = new Intent(BluetoothDevice.ACTION_UUID);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_UUID, uuid);
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
|
||
|
|
||
|
if (mUuidIntentTracker.contains(address))
|
||
|
mUuidIntentTracker.remove(address);
|
||
|
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void makeServiceChannelCallbacks(String address) {
|
||
|
for (Iterator<RemoteService> iter = mUuidCallbackTracker.keySet().iterator();
|
||
|
iter.hasNext();) {
|
||
|
RemoteService service = iter.next();
|
||
|
if (service.address.equals(address)) {
|
||
|
if (DBG) log("Cleaning up failed UUID channel lookup: " + service.address +
|
||
|
" " + service.uuid);
|
||
|
IBluetoothCallback callback = mUuidCallbackTracker.get(service);
|
||
|
if (callback != null) {
|
||
|
try {
|
||
|
callback.onRfcommChannelFound(-1);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
}
|
||
|
|
||
|
iter.remove();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
|
||
|
switch(mBluetoothState) {
|
||
|
case BluetoothAdapter.STATE_OFF:
|
||
|
pw.println("Bluetooth OFF\n");
|
||
|
return;
|
||
|
case BluetoothAdapter.STATE_TURNING_ON:
|
||
|
pw.println("Bluetooth TURNING ON\n");
|
||
|
return;
|
||
|
case BluetoothAdapter.STATE_TURNING_OFF:
|
||
|
pw.println("Bluetooth TURNING OFF\n");
|
||
|
return;
|
||
|
case BluetoothAdapter.STATE_ON:
|
||
|
pw.println("Bluetooth ON\n");
|
||
|
}
|
||
|
|
||
|
pw.println("mIsAirplaneSensitive = " + mIsAirplaneSensitive);
|
||
|
pw.println("mIsAirplaneToggleable = " + mIsAirplaneToggleable);
|
||
|
|
||
|
pw.println("Local address = " + getAddress());
|
||
|
pw.println("Local name = " + getName());
|
||
|
pw.println("isDiscovering() = " + isDiscovering());
|
||
|
|
||
|
BluetoothHeadset headset = new BluetoothHeadset(mContext, null);
|
||
|
|
||
|
pw.println("\n--Known devices--");
|
||
|
for (String address : mDeviceProperties.keySet()) {
|
||
|
int bondState = mBondState.getBondState(address);
|
||
|
pw.printf("%s %10s (%d) %s\n", address,
|
||
|
toBondStateString(bondState),
|
||
|
mBondState.getAttempt(address),
|
||
|
getRemoteName(address));
|
||
|
|
||
|
Map<ParcelUuid, Integer> uuidChannels = mDeviceServiceChannelCache.get(address);
|
||
|
if (uuidChannels == null) {
|
||
|
pw.println("\tuuids = null");
|
||
|
} else {
|
||
|
for (ParcelUuid uuid : uuidChannels.keySet()) {
|
||
|
Integer channel = uuidChannels.get(uuid);
|
||
|
if (channel == null) {
|
||
|
pw.println("\t" + uuid);
|
||
|
} else {
|
||
|
pw.println("\t" + uuid + " RFCOMM channel = " + channel);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Map<ParcelUuid, Integer> uuidPsms = mDeviceL2capPsmCache.get(address);
|
||
|
if (uuidPsms == null) {
|
||
|
pw.println("\tuuids = null");
|
||
|
} else {
|
||
|
for (ParcelUuid uuid : uuidPsms.keySet()) {
|
||
|
Integer psm = uuidPsms.get(uuid);
|
||
|
if (psm == null) {
|
||
|
pw.println("\t" + uuid);
|
||
|
} else {
|
||
|
pw.println("\t" + uuid + " L2CAP PSM = " + psm);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (RemoteService service : mUuidCallbackTracker.keySet()) {
|
||
|
if (service.address.equals(address)) {
|
||
|
pw.println("\tPENDING CALLBACK: " + service.uuid);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
String value = getProperty("Devices");
|
||
|
String[] devicesObjectPath = null;
|
||
|
if (value != null) {
|
||
|
devicesObjectPath = value.split(",");
|
||
|
}
|
||
|
pw.println("\n--ACL connected devices--");
|
||
|
if (devicesObjectPath != null) {
|
||
|
for (String device : devicesObjectPath) {
|
||
|
pw.println(getAddressFromObjectPath(device));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Rather not do this from here, but no-where else and I need this
|
||
|
// dump
|
||
|
pw.println("\n--Headset Service--");
|
||
|
switch (headset.getState(headset.getCurrentHeadset())) {
|
||
|
case BluetoothHeadset.STATE_DISCONNECTED:
|
||
|
pw.println("getState() = STATE_DISCONNECTED");
|
||
|
break;
|
||
|
case BluetoothHeadset.STATE_CONNECTING:
|
||
|
pw.println("getState() = STATE_CONNECTING");
|
||
|
break;
|
||
|
case BluetoothHeadset.STATE_CONNECTED:
|
||
|
pw.println("getState() = STATE_CONNECTED");
|
||
|
break;
|
||
|
case BluetoothHeadset.STATE_ERROR:
|
||
|
pw.println("getState() = STATE_ERROR");
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
pw.println("\ngetCurrentHeadset() = " + headset.getCurrentHeadset());
|
||
|
pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint());
|
||
|
headset.close();
|
||
|
pw.println("\n--Application Service Records--");
|
||
|
for (Integer handle : mServiceRecordToPid.keySet()) {
|
||
|
Integer pid = mServiceRecordToPid.get(handle);
|
||
|
pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* package */ static int bluezStringToScanMode(boolean pairable, boolean discoverable) {
|
||
|
if (pairable && discoverable)
|
||
|
return BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
|
||
|
else if (pairable && !discoverable)
|
||
|
return BluetoothAdapter.SCAN_MODE_CONNECTABLE;
|
||
|
else
|
||
|
return BluetoothAdapter.SCAN_MODE_NONE;
|
||
|
}
|
||
|
|
||
|
/* package */ static String scanModeToBluezString(int mode) {
|
||
|
switch (mode) {
|
||
|
case BluetoothAdapter.SCAN_MODE_NONE:
|
||
|
return "off";
|
||
|
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
|
||
|
return "connectable";
|
||
|
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
|
||
|
return "discoverable";
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/*package*/ String getAddressFromObjectPath(String objectPath) {
|
||
|
String adapterObjectPath = getPropertyInternal("ObjectPath");
|
||
|
if (adapterObjectPath == null || objectPath == null) {
|
||
|
Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
|
||
|
" or deviceObjectPath:" + objectPath + " is null");
|
||
|
return null;
|
||
|
}
|
||
|
if (!objectPath.startsWith(adapterObjectPath)) {
|
||
|
Log.e(TAG, "getAddressFromObjectPath: AdapterObjectPath:" + adapterObjectPath +
|
||
|
" is not a prefix of deviceObjectPath:" + objectPath +
|
||
|
"bluetoothd crashed ?");
|
||
|
return null;
|
||
|
}
|
||
|
String address = objectPath.substring(adapterObjectPath.length());
|
||
|
if (address != null) return address.replace('_', ':');
|
||
|
|
||
|
Log.e(TAG, "getAddressFromObjectPath: Address being returned is null");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/*package*/ String getObjectPathFromAddress(String address) {
|
||
|
String path = getPropertyInternal("ObjectPath");
|
||
|
if (path == null) {
|
||
|
Log.e(TAG, "Error: Object Path is null");
|
||
|
return null;
|
||
|
}
|
||
|
path = path + address.replace(":", "_");
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
/*package */ void setLinkTimeout(String address, int num_slots) {
|
||
|
String path = getObjectPathFromAddress(address);
|
||
|
boolean result = setLinkTimeoutNative(path, num_slots);
|
||
|
|
||
|
if (!result) log("Set Link Timeout to:" + num_slots + " slots failed");
|
||
|
}
|
||
|
|
||
|
public boolean connectHeadset(String address) {
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
|
||
|
msg.obj = state;
|
||
|
mHfpProfileState.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean disconnectHeadset(String address) {
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING;
|
||
|
msg.obj = state;
|
||
|
mHfpProfileState.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean connectSink(String address) {
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
|
||
|
msg.obj = state;
|
||
|
mA2dpProfileState.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean disconnectSink(String address) {
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING;
|
||
|
msg.obj = state;
|
||
|
mA2dpProfileState.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private BluetoothDeviceProfileState addProfileState(String address) {
|
||
|
BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
|
||
|
if (state != null) return state;
|
||
|
|
||
|
state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService);
|
||
|
mDeviceProfileState.put(address, state);
|
||
|
state.start();
|
||
|
return state;
|
||
|
}
|
||
|
|
||
|
private void initProfileState() {
|
||
|
String []bonds = null;
|
||
|
String val = getPropertyInternal("Devices");
|
||
|
if (val != null) {
|
||
|
bonds = val.split(",");
|
||
|
}
|
||
|
if (bonds == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for (String path : bonds) {
|
||
|
String address = getAddressFromObjectPath(path);
|
||
|
BluetoothDeviceProfileState state = addProfileState(address);
|
||
|
// Allow 8 secs for SDP records to get registered.
|
||
|
Message msg = new Message();
|
||
|
msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES;
|
||
|
state.sendMessageDelayed(msg, 8000);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public boolean notifyConnectA2dp(String address) {
|
||
|
BluetoothDeviceProfileState state =
|
||
|
mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES;
|
||
|
msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
|
||
|
state.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean notifyIncomingConnection(String address) {
|
||
|
BluetoothDeviceProfileState state =
|
||
|
mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
|
||
|
state.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*package*/ boolean notifyIncomingA2dpConnection(String address) {
|
||
|
BluetoothDeviceProfileState state =
|
||
|
mDeviceProfileState.get(address);
|
||
|
if (state != null) {
|
||
|
Message msg = new Message();
|
||
|
msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING;
|
||
|
state.sendMessage(msg);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/*package*/ void setA2dpService(BluetoothA2dpService a2dpService) {
|
||
|
mA2dpService = a2dpService;
|
||
|
}
|
||
|
|
||
|
/*package*/ Integer getAuthorizationAgentRequestData(String address) {
|
||
|
Integer data = mEventLoop.getAuthorizationAgentRequestData().remove(address);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
public void sendProfileStateMessage(int profile, int cmd) {
|
||
|
Message msg = new Message();
|
||
|
msg.what = cmd;
|
||
|
if (profile == BluetoothProfileState.HFP) {
|
||
|
mHfpProfileState.sendMessage(msg);
|
||
|
} else if (profile == BluetoothProfileState.A2DP) {
|
||
|
mA2dpProfileState.sendMessage(msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void createIncomingConnectionStateFile() {
|
||
|
File f = new File(INCOMING_CONNECTION_FILE);
|
||
|
if (!f.exists()) {
|
||
|
try {
|
||
|
f.createNewFile();
|
||
|
} catch (IOException e) {
|
||
|
Log.e(TAG, "IOException: cannot create file");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public Pair<Integer, String> getIncomingState(String address) {
|
||
|
if (mIncomingConnections.isEmpty()) {
|
||
|
createIncomingConnectionStateFile();
|
||
|
readIncomingConnectionState();
|
||
|
}
|
||
|
return mIncomingConnections.get(address);
|
||
|
}
|
||
|
|
||
|
private void readIncomingConnectionState() {
|
||
|
synchronized(mIncomingConnections) {
|
||
|
FileInputStream fstream = null;
|
||
|
try {
|
||
|
fstream = new FileInputStream(INCOMING_CONNECTION_FILE);
|
||
|
DataInputStream in = new DataInputStream(fstream);
|
||
|
BufferedReader file = new BufferedReader(new InputStreamReader(in));
|
||
|
String line;
|
||
|
while((line = file.readLine()) != null) {
|
||
|
line = line.trim();
|
||
|
if (line.length() == 0) continue;
|
||
|
String[] value = line.split(",");
|
||
|
if (value != null && value.length == 3) {
|
||
|
Integer val1 = Integer.parseInt(value[1]);
|
||
|
Pair<Integer, String> val = new Pair(val1, value[2]);
|
||
|
mIncomingConnections.put(value[0], val);
|
||
|
}
|
||
|
}
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: readIncomingConnectionState" + e.toString());
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: readIncomingConnectionState" + e.toString());
|
||
|
} finally {
|
||
|
if (fstream != null) {
|
||
|
try {
|
||
|
fstream.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void truncateIncomingConnectionFile() {
|
||
|
RandomAccessFile r = null;
|
||
|
try {
|
||
|
r = new RandomAccessFile(INCOMING_CONNECTION_FILE, "rw");
|
||
|
r.setLength(0);
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: truncateIncomingConnectionState" + e.toString());
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: truncateIncomingConnectionState" + e.toString());
|
||
|
} finally {
|
||
|
if (r != null) {
|
||
|
try {
|
||
|
r.close();
|
||
|
} catch (IOException e) {
|
||
|
// ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** @hide */
|
||
|
public void writeIncomingConnectionState(String address, Pair<Integer, String> data) {
|
||
|
synchronized(mIncomingConnections) {
|
||
|
mIncomingConnections.put(address, data);
|
||
|
|
||
|
truncateIncomingConnectionFile();
|
||
|
BufferedWriter out = null;
|
||
|
StringBuilder value = new StringBuilder();
|
||
|
try {
|
||
|
out = new BufferedWriter(new FileWriter(INCOMING_CONNECTION_FILE, true));
|
||
|
for (String devAddress: mIncomingConnections.keySet()) {
|
||
|
Pair<Integer, String> val = mIncomingConnections.get(devAddress);
|
||
|
value.append(devAddress);
|
||
|
value.append(",");
|
||
|
value.append(val.first.toString());
|
||
|
value.append(",");
|
||
|
value.append(val.second);
|
||
|
value.append("\n");
|
||
|
}
|
||
|
out.write(value.toString());
|
||
|
} catch (FileNotFoundException e) {
|
||
|
log("FileNotFoundException: writeIncomingConnectionState" + e.toString());
|
||
|
} catch (IOException e) {
|
||
|
log("IOException: writeIncomingConnectionState" + e.toString());
|
||
|
} finally {
|
||
|
if (out != null) {
|
||
|
try {
|
||
|
out.close();
|
||
|
} catch (IOException e) {
|
||
|
// Ignore
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void log(String msg) {
|
||
|
Log.d(TAG, msg);
|
||
|
}
|
||
|
|
||
|
|
||
|
public synchronized String getGattServiceProperty(String path, String property) {
|
||
|
Map<String, String> properties = mGattProperties.get(path);
|
||
|
Log.d(TAG, "getGattServiceProperty: " + property + ", path "+ path);
|
||
|
if (properties != null) {
|
||
|
return properties.get(property);
|
||
|
} else {
|
||
|
// Query for GATT service properties, again.
|
||
|
if (updateGattServicePropertiesCache(path))
|
||
|
return getGattServiceProperty(path, property);
|
||
|
}
|
||
|
Log.e(TAG, "getGattServiceProperty: " + property + " not present: " + path);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized boolean updateGattServicePropertiesCache(String path) {
|
||
|
String[] propValues = (String []) getGattServicePropertiesNative(path);
|
||
|
if (propValues != null) {
|
||
|
addGattServiceProperties(path, propValues);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* package */ synchronized void addGattServiceProperties(String path, String[] properties) {
|
||
|
Map<String, String> propertyValues = mGattProperties.get(path);
|
||
|
if (propertyValues == null) {
|
||
|
propertyValues = new HashMap<String, String>();
|
||
|
}
|
||
|
|
||
|
for (int i = 0; i < properties.length; i++) {
|
||
|
String name = properties[i];
|
||
|
String newValue = null;
|
||
|
int len;
|
||
|
if (name == null) {
|
||
|
Log.e(TAG, "Error: Gatt Service Property at index" + i + "is null");
|
||
|
continue;
|
||
|
}
|
||
|
if (name.equals("Characteristics")) {
|
||
|
StringBuilder str = new StringBuilder();
|
||
|
len = Integer.valueOf(properties[++i]);
|
||
|
for (int j = 0; j < len; j++) {
|
||
|
str.append(properties[++i]);
|
||
|
str.append(",");
|
||
|
}
|
||
|
if (len > 0) {
|
||
|
newValue = str.toString();
|
||
|
}
|
||
|
} else {
|
||
|
newValue = properties[++i];
|
||
|
}
|
||
|
propertyValues.put(name, newValue);
|
||
|
}
|
||
|
|
||
|
mGattProperties.put(path, propertyValues);
|
||
|
|
||
|
}
|
||
|
|
||
|
/* package */ void removeGattServiceProperties(String path) {
|
||
|
mGattProperties.remove(path);
|
||
|
}
|
||
|
|
||
|
private synchronized String[] getRemoteGattServices(String address) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return null;
|
||
|
}
|
||
|
return getGattServicesFromCache(address);
|
||
|
}
|
||
|
|
||
|
private String[] getGattServicesFromCache(String address) {
|
||
|
|
||
|
Log.d (TAG, "getGattServicesFromCache");
|
||
|
|
||
|
String value = getRemoteDeviceProperty(address, "Services");
|
||
|
if (value == null) {
|
||
|
Log.d(TAG, "getGattServicesFromCache: no services found");
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
String[] path = null;
|
||
|
// The paths are stored as a "," separated string.
|
||
|
path = value.split(",");
|
||
|
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
private String[] matchGattService(String address, ParcelUuid uuid)
|
||
|
{
|
||
|
// The properties should be cached at this point
|
||
|
String value = getRemoteDeviceProperty(address, "Services");
|
||
|
if (value == null) {
|
||
|
Log.e(TAG, "matchGattService: No GATT based services were found on " + address);
|
||
|
return null;
|
||
|
} else {
|
||
|
Log.d(TAG, "matchGattService: Value " + value);
|
||
|
}
|
||
|
|
||
|
String[] gattServicePaths = null;
|
||
|
// The object paths are stored as a "," separated string.
|
||
|
gattServicePaths = value.split(",");
|
||
|
|
||
|
ArrayList<String> matchList = new ArrayList<String>();
|
||
|
int count = 0;
|
||
|
String stringUuid;
|
||
|
|
||
|
stringUuid = uuid.toString();
|
||
|
|
||
|
Log.d(TAG, "Requested GATT UUID to match: " + stringUuid);
|
||
|
|
||
|
for (int i = 0; i < gattServicePaths.length; i++) {
|
||
|
boolean match = true;
|
||
|
String serviceUuid = getGattServiceProperty(gattServicePaths[i], "UUID");
|
||
|
|
||
|
if (serviceUuid != null) {
|
||
|
Log.d(TAG, "Found GATT UUID: " + serviceUuid);
|
||
|
|
||
|
if (!serviceUuid.equalsIgnoreCase(stringUuid)){
|
||
|
Log.d(TAG,"UUID does not match");
|
||
|
match = false;
|
||
|
}
|
||
|
|
||
|
if (match) {
|
||
|
matchList.add(gattServicePaths[i]);
|
||
|
count++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (count == 0)
|
||
|
return null;
|
||
|
|
||
|
Log.d(TAG,"Found " + count+ " instances of service " + stringUuid);
|
||
|
|
||
|
String[] ret = new String[count];
|
||
|
|
||
|
matchList.toArray(ret);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
/* Broadcast the GATT services intent */
|
||
|
/*package*/ synchronized void sendGattIntent(String address, int result) {
|
||
|
Intent intent = new Intent(BluetoothDevice.ACTION_GATT);
|
||
|
ParcelUuid[] uuids = null;
|
||
|
String[] gattPath;
|
||
|
int count = 1;
|
||
|
int i;
|
||
|
boolean isScheduled = false;
|
||
|
|
||
|
synchronized (this) {
|
||
|
if (mGattIntentTracker.containsKey(address)) {
|
||
|
|
||
|
ArrayList<ParcelUuid> serviceUuids = mGattIntentTracker.get(address);
|
||
|
isScheduled = true;
|
||
|
|
||
|
if(serviceUuids != null) {
|
||
|
uuids = new ParcelUuid[serviceUuids.size()];
|
||
|
uuids = serviceUuids.toArray(uuids);
|
||
|
}
|
||
|
|
||
|
Log.d(TAG, "Clear GATT INTENT tracker");
|
||
|
mGattIntentTracker.remove(address);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!isScheduled)
|
||
|
return;
|
||
|
|
||
|
if (uuids != null) {
|
||
|
count = uuids.length;
|
||
|
for (i = 0; i < count; i++) {
|
||
|
|
||
|
gattPath = matchGattService (address, uuids[i]);
|
||
|
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_UUID, uuids[i]);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_GATT, gattPath);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_GATT_RESULT, result);
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
|
||
|
}
|
||
|
} else {
|
||
|
Log.d(TAG, "Send intents about all services found on the remote devices");
|
||
|
|
||
|
String value = getRemoteDeviceProperty(address, "Services");
|
||
|
if (value == null) {
|
||
|
Log.e(TAG, "No GATT based services were found on " + address);
|
||
|
return;
|
||
|
}
|
||
|
Log.d(TAG, "GattServices: " + value);
|
||
|
|
||
|
String[] gattServicePaths = null;
|
||
|
// The object paths are stored as a "," separated string.
|
||
|
gattServicePaths = value.split(",");
|
||
|
|
||
|
//Send one intent per GATT service
|
||
|
for (i = 0; i < gattServicePaths.length; i++) {
|
||
|
String serviceUuid = getGattServiceProperty(gattServicePaths[i], "UUID");
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_UUID, serviceUuid);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_GATT, gattServicePaths[i]);
|
||
|
intent.putExtra(BluetoothDevice.EXTRA_GATT_RESULT, result);
|
||
|
mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private String[] getCharacteristicsFromCache(String servicePath) {
|
||
|
String value = getGattServiceProperty(servicePath, "Characteristics");
|
||
|
if (value == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
String[] paths = null;
|
||
|
// The Charateristic paths are stored as a "," separated string.
|
||
|
paths = value.split(",");
|
||
|
return paths;
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void makeDiscoverCharacteristicsCallback(String servicePath, boolean result) {
|
||
|
IBluetoothGattService callback = mGattServiceTracker.get(servicePath);
|
||
|
|
||
|
Log.d(TAG, "makeDiscoverCharacteristicsCallback for service: " + servicePath);
|
||
|
|
||
|
if (callback != null) {
|
||
|
String[] charPaths = null;
|
||
|
if (result)
|
||
|
charPaths = getCharacteristicsFromCache(servicePath);
|
||
|
try {
|
||
|
callback.onCharacteristicsDiscovered(charPaths, result);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
} else
|
||
|
Log.d(TAG, "Discover Characteristics Callback for service " + servicePath + " not queued");
|
||
|
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void makeSetCharacteristicPropertyCallback(String charPath, String property, boolean result) {
|
||
|
Log.d(TAG, "makeSetCharacteristicPropertyCallback for char: " + charPath);
|
||
|
|
||
|
if (charPath == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
String servicePath = charPath.substring(0, charPath.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IBluetoothGattService callback = mGattServiceTracker.get(servicePath);
|
||
|
|
||
|
if (callback != null) {
|
||
|
try {
|
||
|
callback.onSetCharacteristicProperty(charPath, property, result);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
} else
|
||
|
Log.d(TAG, "Set Characteristics Property Callback for service " + servicePath + " not queued");
|
||
|
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void makeWatcherValueChangedCallback(String charPath, String value) {
|
||
|
|
||
|
if (charPath == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
String servicePath = charPath.substring(0, charPath.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Log.d(TAG, "WatcherValueChanged : service Path = " + servicePath);
|
||
|
|
||
|
IBluetoothGattService callback = mGattWatcherTracker.get(servicePath);
|
||
|
|
||
|
if (callback != null) {
|
||
|
try {
|
||
|
callback.onValueChanged(charPath, value);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
} else {
|
||
|
Log.d(TAG, "Callback for service " + servicePath + " not registered");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void makeUpdateCharacteristicValueCallback(String charPath, boolean result) {
|
||
|
|
||
|
String servicePath = charPath.substring(0, charPath.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IBluetoothGattService callback = mGattServiceTracker.get(servicePath);
|
||
|
|
||
|
Log.d(TAG, "makeCharacteristicValueUpdatedCallback for service: " + charPath);
|
||
|
|
||
|
if (callback != null) {
|
||
|
try {
|
||
|
callback.onCharacteristicValueUpdated(charPath, result);
|
||
|
} catch (RemoteException e) {Log.e(TAG, "", e);}
|
||
|
} else {
|
||
|
Log.d(TAG, "Callback for service " + servicePath + " not registered");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Bluetooth: Support for GATT Update Characteristic Value
|
||
|
* Returns the user-friendly name of a GATT based service. This value is
|
||
|
* returned from our local cache.
|
||
|
*/
|
||
|
public String getGattServiceName(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(getAddressFromObjectPath(path))) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return getGattServiceProperty(path, "Name");
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Connect and fetch object paths for GATT based services.
|
||
|
* TODO: for BR/EDR use SDP mechanism
|
||
|
*/
|
||
|
public synchronized boolean getGattServices(String address, ParcelUuid uuid) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Log.d(TAG, "getGattServices");
|
||
|
|
||
|
if (!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean ret = true;
|
||
|
boolean delay = false;
|
||
|
boolean discovering = false;
|
||
|
|
||
|
synchronized (this) {
|
||
|
discovering = mGattIntentTracker.containsKey(address);
|
||
|
}
|
||
|
|
||
|
if (!discovering) {
|
||
|
if (isRemoteDeviceInCache(address) && getRemoteGattServices(address) != null && findDeviceNative(address) != null) {
|
||
|
String value = getRemoteDeviceProperty(address, "Services");
|
||
|
if (value == null) {
|
||
|
Log.e(TAG, "No GATT based services were found on " + address);
|
||
|
ret = false;
|
||
|
}
|
||
|
} else {
|
||
|
Log.d(TAG, "Need to Create Remote Device" + address + " before accessing properties");
|
||
|
ret = createDeviceNative(address);
|
||
|
delay = true;
|
||
|
}
|
||
|
} else
|
||
|
Log.d(TAG, "GATT service discovery for remote device " + address + "is in progress");
|
||
|
|
||
|
if (!ret)
|
||
|
return false;
|
||
|
|
||
|
ArrayList<ParcelUuid> serviceUuids;
|
||
|
synchronized (this) {
|
||
|
if (mGattIntentTracker.containsKey(address)) {
|
||
|
serviceUuids = mGattIntentTracker.get(address);
|
||
|
mGattIntentTracker.remove(address);
|
||
|
} else {
|
||
|
serviceUuids = new ArrayList<ParcelUuid>();
|
||
|
}
|
||
|
|
||
|
serviceUuids.add(uuid);
|
||
|
mGattIntentTracker.put(address, serviceUuids);
|
||
|
}
|
||
|
|
||
|
if (!discovering) {
|
||
|
Message message = mHandler.obtainMessage(MESSAGE_GATT_INTENT);
|
||
|
message.obj = address;
|
||
|
if (delay)
|
||
|
mHandler.sendMessageDelayed(message, GATT_INTENT_DELAY);
|
||
|
else
|
||
|
mHandler.sendMessage(message);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean discoverCharacteristics(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Log.d(TAG, "discoverCharacteristics");
|
||
|
|
||
|
if (!mGattServices.containsKey(path)) {
|
||
|
Log.d(TAG, "Service not present " + path);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean ret = discoverCharacteristicsNative(path);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
public synchronized String[] getCharacteristicProperties(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return null;
|
||
|
|
||
|
Log.d(TAG, "getCharacteristicProperties");
|
||
|
|
||
|
if (path == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
String servicePath = path.substring(0, path.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
if (!mGattServices.containsKey(servicePath)) {
|
||
|
Log.d(TAG, "Service not present " + servicePath);
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
String[] propValues = (String []) getCharacteristicPropertiesNative(path);
|
||
|
return propValues;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean setCharacteristicProperty(String path, String key, byte[] value, boolean reliable) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (path == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
String servicePath = path.substring(0, path.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!mGattServices.containsKey(servicePath)) {
|
||
|
Log.d(TAG, "Service not present " + servicePath);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean ret = setCharacteristicPropertyNative(path, key, value, value.length, reliable);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean updateCharacteristicValue(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Log.d(TAG, "updateCharacteristicValue");
|
||
|
|
||
|
if (path == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
String servicePath = path.substring(0, path.indexOf("/characteristic"));
|
||
|
|
||
|
if (servicePath == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!mGattServices.containsKey(servicePath)) {
|
||
|
Log.d(TAG, "Service not present " + servicePath);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return updateCharacteristicValueNative(path);
|
||
|
}
|
||
|
|
||
|
public synchronized boolean registerCharacteristicsWatcher(String path, IBluetoothGattService gattCallback) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Log.d(TAG, "registerCharacteristicsWatcher");
|
||
|
|
||
|
if (!mGattServices.containsKey(path)) {
|
||
|
Log.d(TAG, "Service not present " + path);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (mGattWatcherTracker.get(path) != null) {
|
||
|
// Do not add this callback
|
||
|
Log.d(TAG, "registerCharacteristicsWatcher: already registered for " + path);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
boolean ret = registerCharacteristicsWatcherNative(path);
|
||
|
|
||
|
if (ret == true) {
|
||
|
mGattWatcherTracker.put(path, gattCallback);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean deregisterCharacteristicsWatcher(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
if (!mGattServices.containsKey(path)) {
|
||
|
Log.d(TAG, "Service not present " + path);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Log.d(TAG, "deregisterCharacteristicsWatcher");
|
||
|
|
||
|
boolean ret = deregisterCharacteristicsWatcherNative(path);
|
||
|
|
||
|
mGattWatcherTracker.remove(path);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
public synchronized void disconnectSap() {
|
||
|
Log.d(TAG, "disconnectSap");
|
||
|
int res = disConnectSapNative();
|
||
|
Log.d(TAG, "disconnectSap returns -" + res);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
public synchronized boolean startRemoteGattService(String path, IBluetoothGattService gattCallback) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return false;
|
||
|
|
||
|
Log.d(TAG, "startRemoteGattService(");
|
||
|
|
||
|
if (mGattServiceTracker.get(path) != null) {
|
||
|
// Do not add this callback, its already there
|
||
|
Log.d(TAG, "startRemoteGattService: callback already registered " + path);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
mGattServiceTracker.put(path, gattCallback);
|
||
|
|
||
|
if (!mGattServices.containsKey(path))
|
||
|
mGattServices.put(path, 1);
|
||
|
else {
|
||
|
Integer refCount = mGattServices.get(path);
|
||
|
refCount++;
|
||
|
mGattServices.remove(path);
|
||
|
mGattServices.put(path, refCount);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public synchronized void closeRemoteGattService(String path) {
|
||
|
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
|
||
|
if (!isEnabledInternal()) return;
|
||
|
|
||
|
if (!mGattServices.containsKey(path)) {
|
||
|
Log.d(TAG, "removeRemoteGattService: service not found " + path);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Integer refCount = mGattServices.get(path);
|
||
|
refCount--;
|
||
|
|
||
|
Log.d(TAG, "removeRemoteGattService: refCount for " + path + " is " + refCount);
|
||
|
|
||
|
if (refCount > 0) {
|
||
|
mGattServices.remove(path);
|
||
|
mGattServices.put(path, refCount);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Map<String, String> properties = mGattProperties.get(path);
|
||
|
|
||
|
if (properties != null) {
|
||
|
String chars = properties.get("Characteristics");
|
||
|
|
||
|
if (chars != null) {
|
||
|
String[] charPaths = chars.split(",");
|
||
|
|
||
|
for (int i = 0; i < charPaths.length; i++)
|
||
|
mGattServiceTracker.remove(charPaths[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
removeGattServiceProperties(path);
|
||
|
mGattServiceTracker.remove(path);
|
||
|
mGattServices.remove(path);
|
||
|
|
||
|
//Check if we can disconnect from the remote device (LE only)
|
||
|
String devicePath = path.substring(0, path.indexOf("/service"));
|
||
|
|
||
|
if (devicePath == null)
|
||
|
return;
|
||
|
|
||
|
String address = getAddressFromObjectPath(devicePath);
|
||
|
|
||
|
if(address == null) {
|
||
|
Log.d(TAG, "adress is null????");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
SortedMap subMap = mGattServices.tailMap(devicePath);
|
||
|
|
||
|
if (!subMap.isEmpty()) {
|
||
|
String nextServicePath = (String) subMap.firstKey();
|
||
|
if (devicePath.equals(nextServicePath.substring(0, path.indexOf("/service")))) {
|
||
|
Log.d(TAG, "removeRemoteGattService: more GATT services are running on device " + nextServicePath);
|
||
|
// There are still other GATT services used on this remote device
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Log.d(TAG, "removeRemoteGattService: disconnect" + address);
|
||
|
|
||
|
boolean res;
|
||
|
res = disconnectGattNative(path);
|
||
|
Log.d(TAG, "disconnectGatt " + res);
|
||
|
|
||
|
}
|
||
|
|
||
|
/*package*/ synchronized void clearRemoteDeviceGattServices(String address) {
|
||
|
Log.d(TAG, "clearRemoteDeviceGattServices");
|
||
|
|
||
|
String value = getRemoteDeviceProperty(address, "Services");
|
||
|
if (value == null) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
String[] services = null;
|
||
|
services = value.split(",");
|
||
|
|
||
|
for(int i = 0; i < services.length; i++)
|
||
|
closeRemoteGattService(services[i]);
|
||
|
|
||
|
setRemoteDeviceProperty(address, "Services", null);
|
||
|
}
|
||
|
|
||
|
|
||
|
private native static void classInitNative();
|
||
|
private native void initializeNativeDataNative();
|
||
|
private native boolean setupNativeDataNative();
|
||
|
private native boolean tearDownNativeDataNative();
|
||
|
private native void cleanupNativeDataNative();
|
||
|
private native String getAdapterPathNative();
|
||
|
|
||
|
private native int isEnabledNative();
|
||
|
private native int enableNative();
|
||
|
private native int disableNative();
|
||
|
|
||
|
private native Object[] getAdapterPropertiesNative();
|
||
|
private native Object[] getDevicePropertiesNative(String objectPath);
|
||
|
private native boolean setAdapterPropertyStringNative(String key, String value);
|
||
|
private native boolean setAdapterPropertyIntegerNative(String key, int value);
|
||
|
private native boolean setAdapterPropertyBooleanNative(String key, int value);
|
||
|
|
||
|
private native boolean startDiscoveryNative();
|
||
|
private native boolean stopDiscoveryNative();
|
||
|
private native int listConnectionNative();
|
||
|
private native boolean createPairedDeviceNative(String address, int timeout_ms);
|
||
|
private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms);
|
||
|
private native byte[] readAdapterOutOfBandDataNative();
|
||
|
|
||
|
private native boolean cancelDeviceCreationNative(String address);
|
||
|
private native boolean removeDeviceNative(String objectPath);
|
||
|
private native int getDeviceServiceChannelNative(String objectPath, String uuid,
|
||
|
int attributeId);
|
||
|
private native String getDeviceStringAttrValue(String objectPath, String uuid,
|
||
|
int attributeId);
|
||
|
|
||
|
private native boolean cancelPairingUserInputNative(String address, int nativeData);
|
||
|
private native boolean setPinNative(String address, String pin, int nativeData);
|
||
|
private native boolean sapAuthorizeNative(String address, boolean access, int nativeData);
|
||
|
private native boolean setPasskeyNative(String address, int passkey, int nativeData);
|
||
|
private native boolean setPairingConfirmationNative(String address, boolean confirm,
|
||
|
int nativeData);
|
||
|
private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash,
|
||
|
byte[] randomizer, int nativeData);
|
||
|
|
||
|
private native boolean setDevicePropertyBooleanNative(String objectPath, String key,
|
||
|
int value);
|
||
|
private native boolean setDevicePropertyIntegerNative(String objectPath, String key,
|
||
|
int value);
|
||
|
|
||
|
private native boolean createDeviceNative(String address);
|
||
|
/*package*/ native boolean discoverServicesNative(String objectPath, String pattern);
|
||
|
|
||
|
private native int addRfcommServiceRecordNative(String name, long uuidMsb, long uuidLsb,
|
||
|
short channel);
|
||
|
private native boolean removeServiceRecordNative(int handle);
|
||
|
private native boolean setLinkTimeoutNative(String path, int num_slots);
|
||
|
native boolean setAuthorizationNative(String address, boolean value, int data);
|
||
|
private native String findDeviceNative(String address);
|
||
|
|
||
|
private native Object[] getGattServicePropertiesNative(String path);
|
||
|
private native boolean discoverCharacteristicsNative(String path);
|
||
|
private native Object[] getCharacteristicPropertiesNative(String path);
|
||
|
private native boolean setCharacteristicPropertyNative(String path, String key, byte[] value, int length, boolean reliable);
|
||
|
private native boolean updateCharacteristicValueNative(String path);
|
||
|
private native boolean registerCharacteristicsWatcherNative(String path);
|
||
|
private native boolean deregisterCharacteristicsWatcherNative(String path);
|
||
|
private native int disConnectSapNative();
|
||
|
private native boolean disconnectGattNative(String path);
|
||
|
}
|