blob: 10f9f7c15075b01e034fdc7772e29d62c514e0ec [file] [log] [blame]
/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* TODO: Move this to
* java/services/com/android/server/BluetoothDeviceService.java
* and make the contructor package private again.
*
* @hide
*/
package android.server;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset; // just for dump()
import android.bluetooth.BluetoothIntent;
import android.bluetooth.IBluetoothDevice;
import android.bluetooth.IBluetoothDeviceCallback;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.provider.Settings;
import android.util.Log;
import android.os.Binder;
import android.os.Handler;
import android.os.Message;
import android.os.SystemService;
import java.io.IOException;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
public class BluetoothDeviceService extends IBluetoothDevice.Stub {
private static final String TAG = "BluetoothDeviceService";
private int mNativeData;
private Context mContext;
private BluetoothEventLoop mEventLoop;
private IntentFilter mIntentFilter;
private boolean mIsAirplaneSensitive;
private volatile boolean mIsEnabled; // local cache of isEnabledNative()
private boolean mIsDiscovering;
static {
classInitNative();
}
private native static void classInitNative();
public BluetoothDeviceService(Context context) {
mContext = context;
}
/** Must be called after construction, and before any other method.
*/
public synchronized void init() {
initializeNativeDataNative();
mIsEnabled = (isEnabledNative() == 1);
mIsDiscovering = false;
mEventLoop = new BluetoothEventLoop(mContext, this);
registerForAirplaneMode();
disableEsco(); // TODO: enable eSCO support once its fully supported
}
private native void initializeNativeDataNative();
@Override
protected void finalize() throws Throwable {
if (mIsAirplaneSensitive) {
mContext.unregisterReceiver(mReceiver);
}
try {
cleanupNativeDataNative();
} finally {
super.finalize();
}
}
private native void cleanupNativeDataNative();
public boolean isEnabled() {
checkPermissionBluetooth();
return mIsEnabled;
}
private native int isEnabledNative();
/**
* Disable bluetooth. Returns true on success.
*/
public synchronized boolean disable() {
checkPermissionBluetoothAdmin();
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
}
if (!mIsEnabled) {
return true;
}
mEventLoop.stop();
disableNative();
mIsEnabled = false;
mIsDiscovering = false;
Intent intent = new Intent(BluetoothIntent.DISABLED_ACTION);
mContext.sendBroadcast(intent);
return true;
}
/**
* Enable this Bluetooth device, asynchronously.
* This turns on/off the underlying hardware.
*
* @return True on success (so far), guarenteeing the callback with be
* notified when complete.
*/
public synchronized boolean enable(IBluetoothDeviceCallback callback) {
checkPermissionBluetoothAdmin();
// Airplane mode can prevent Bluetooth radio from being turned on.
if (mIsAirplaneSensitive && isAirplaneModeOn()) {
return false;
}
if (mIsEnabled) {
return false;
}
if (mEnableThread != null && mEnableThread.isAlive()) {
return false;
}
mEnableThread = new EnableThread(callback);
mEnableThread.start();
return true;
}
private static final int REGISTER_SDP_RECORDS = 1;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case REGISTER_SDP_RECORDS:
//TODO: Don't assume HSP/HFP is running, don't use sdptool,
if (isEnabled()) {
SystemService.start("hsag");
SystemService.start("hfag");
}
}
}
};
private EnableThread mEnableThread;
private class EnableThread extends Thread {
private final IBluetoothDeviceCallback mEnableCallback;
public EnableThread(IBluetoothDeviceCallback callback) {
mEnableCallback = callback;
}
public void run() {
boolean res = (enableNative() == 0);
if (res) {
mEventLoop.start();
}
if (mEnableCallback != null) {
try {
mEnableCallback.onEnableResult(res ?
BluetoothDevice.RESULT_SUCCESS :
BluetoothDevice.RESULT_FAILURE);
} catch (RemoteException e) {}
}
if (res) {
mIsEnabled = true;
mIsDiscovering = false;
Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION);
mContext.sendBroadcast(intent);
mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000);
}
mEnableThread = null;
}
};
private native int enableNative();
private native int disableNative();
public synchronized String getAddress() {
checkPermissionBluetooth();
return getAddressNative();
}
private native String getAddressNative();
public synchronized String getName() {
checkPermissionBluetooth();
return getNameNative();
}
private native String getNameNative();
public synchronized boolean setName(String name) {
checkPermissionBluetoothAdmin();
if (name == null) {
return false;
}
// hcid handles persistance of the bluetooth name
return setNameNative(name);
}
private native boolean setNameNative(String name);
public synchronized String[] listBondings() {
checkPermissionBluetooth();
return listBondingsNative();
}
private native String[] listBondingsNative();
public synchronized String getMajorClass() {
checkPermissionBluetooth();
return getMajorClassNative();
}
private native String getMajorClassNative();
public synchronized String getMinorClass() {
checkPermissionBluetooth();
return getMinorClassNative();
}
private native String getMinorClassNative();
/**
* Returns the user-friendly name of a remote device. This value is
* retrned from our local cache, which is updated during device discovery.
* 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) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteNameNative(address);
}
private native String getRemoteNameNative(String address);
/* pacakge */ native String getAdapterPathNative();
/**
* Initiate a remote-device-discovery procedure. This procedure may be
* canceled by calling {@link #stopDiscovery}. Remote-device discoveries
* are returned as intents
* <p>
* Typically, when a remote device is found, your
* android.bluetooth.DiscoveryEventNotifier#notifyRemoteDeviceFound
* method will be invoked, and subsequently, your
* android.bluetooth.RemoteDeviceEventNotifier#notifyRemoteNameUpdated
* will tell you the user-friendly name of the remote device. However,
* it is possible that the name update may fail for various reasons, so you
* should display the device's Bluetooth address as soon as you get a
* notifyRemoteDeviceFound event, and update the name when you get the
* remote name.
*
* @return true if discovery has started,
* false otherwise.
*/
public synchronized boolean startDiscovery(boolean resolveNames) {
checkPermissionBluetoothAdmin();
return startDiscoveryNative(resolveNames);
}
private native boolean startDiscoveryNative(boolean resolveNames);
/**
* Cancel a remote-device discovery.
*
* Note: you may safely call this method even when discovery has not been
* started.
*/
public synchronized boolean cancelDiscovery() {
checkPermissionBluetoothAdmin();
return cancelDiscoveryNative();
}
private native boolean cancelDiscoveryNative();
public synchronized boolean isDiscovering() {
checkPermissionBluetooth();
return mIsDiscovering;
}
/* package */ void setIsDiscovering(boolean isDiscovering) {
mIsDiscovering = isDiscovering;
}
public synchronized boolean startPeriodicDiscovery() {
checkPermissionBluetoothAdmin();
return startPeriodicDiscoveryNative();
}
private native boolean startPeriodicDiscoveryNative();
public synchronized boolean stopPeriodicDiscovery() {
checkPermissionBluetoothAdmin();
return stopPeriodicDiscoveryNative();
}
private native boolean stopPeriodicDiscoveryNative();
public synchronized boolean isPeriodicDiscovery() {
checkPermissionBluetooth();
return isPeriodicDiscoveryNative();
}
private native boolean isPeriodicDiscoveryNative();
/**
* 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_s The discoverable timeout in seconds.
*/
public synchronized boolean setDiscoverableTimeout(int timeout) {
checkPermissionBluetoothAdmin();
return setDiscoverableTimeoutNative(timeout);
}
private native boolean setDiscoverableTimeoutNative(int timeout_s);
/**
* 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() {
checkPermissionBluetooth();
return getDiscoverableTimeoutNative();
}
private native int getDiscoverableTimeoutNative();
public synchronized boolean isAclConnected(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return isConnectedNative(address);
}
private native boolean isConnectedNative(String address);
/**
* Detetermines whether this device is connectable (that is, whether remote
* devices can connect to it.)
* <p>
* Note: A Bluetooth adapter has separate connectable and discoverable
* states, and you could have any combination of those. Although
* any combination is possible (such as discoverable but not
* connectable), we restrict the possible combinations to one of
* three possibilities: discoverable and connectable, connectable
* but not discoverable, and neither connectable nor discoverable.
*
* @return true if this adapter is connectable
* false otherwise
*
* @see #isDiscoverable
* @see #getMode
* @see #setMode
*/
public synchronized boolean isConnectable() {
checkPermissionBluetooth();
return isConnectableNative();
}
private native boolean isConnectableNative();
/**
* Detetermines whether this device is discoverable.
*
* Note: a Bluetooth adapter has separate connectable and discoverable
* states, and you could have any combination of those. Although
* any combination is possible (such as discoverable but not
* connectable), we restrict the possible combinations to one of
* three possibilities: discoverable and connectable, connectable
* but not discoverable, and neither connectable nor discoverable.
*
* @return true if this adapter is discoverable
* false otherwise
*
* @see #isConnectable
* @see #getMode
* @see #setMode
*/
public synchronized boolean isDiscoverable() {
checkPermissionBluetooth();
return isDiscoverableNative();
}
private native boolean isDiscoverableNative();
/**
* Determines which one of three modes this adapter is in: discoverable and
* connectable, not discoverable but connectable, or neither.
*
* @return Mode enumeration containing the current mode.
*
* @see #setMode
*/
public synchronized int getMode() {
checkPermissionBluetooth();
String mode = getModeNative();
if (mode == null) {
return BluetoothDevice.MODE_UNKNOWN;
}
if (mode.equalsIgnoreCase("off")) {
return BluetoothDevice.MODE_OFF;
}
else if (mode.equalsIgnoreCase("connectable")) {
return BluetoothDevice.MODE_CONNECTABLE;
}
else if (mode.equalsIgnoreCase("discoverable")) {
return BluetoothDevice.MODE_DISCOVERABLE;
}
else {
return BluetoothDevice.MODE_UNKNOWN;
}
}
private native String getModeNative();
/**
* Set the discoverability and connectability mode of this adapter. The
* possibilities are discoverable and connectable (MODE_DISCOVERABLE),
* connectable but not discoverable (MODE_CONNECTABLE), and neither
* (MODE_OFF).
*
* Note: MODE_OFF does not mean that the adapter is physically off. It
* may be neither discoverable nor connectable, but it could still
* initiate outgoing connections, or could participate in a
* connection initiated by a remote device before its mode was set
* to MODE_OFF.
*
* @param mode the new mode
* @see #getMode
*/
public synchronized boolean setMode(int mode) {
checkPermissionBluetoothAdmin();
switch (mode) {
case BluetoothDevice.MODE_OFF:
return setModeNative("off");
case BluetoothDevice.MODE_CONNECTABLE:
return setModeNative("connectable");
case BluetoothDevice.MODE_DISCOVERABLE:
return setModeNative("discoverable");
}
return false;
}
private native boolean setModeNative(String mode);
/**
* Retrieves the alias of a remote device. The alias is a local feature,
* and allows us to associate a name with a remote device that is different
* from that remote device's user-friendly name. The remote device knows
* nothing about this. The alias can be changed with
* {@link #setRemoteAlias}, and it may be removed with
* {@link #clearRemoteAlias}
*
* @param address Bluetooth address of remote device.
*
* @return The alias of the remote device.
*/
public synchronized String getRemoteAlias(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteAliasNative(address);
}
private native String getRemoteAliasNative(String address);
/**
* Changes the alias of a remote device. The alias is a local feature,
* from that remote device's user-friendly name. The remote device knows
* nothing about this. The alias can be retrieved with
* {@link #getRemoteAlias}, and it may be removed with
* {@link #clearRemoteAlias}.
*
* @param address Bluetooth address of remote device
* @param alias Alias for the remote device
*/
public synchronized boolean setRemoteAlias(String address, String alias) {
checkPermissionBluetoothAdmin();
if (alias == null || !BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return setRemoteAliasNative(address, alias);
}
private native boolean setRemoteAliasNative(String address, String alias);
/**
* Removes the alias of a remote device. The alias is a local feature,
* from that remote device's user-friendly name. The remote device knows
* nothing about this. The alias can be retrieved with
* {@link #getRemoteAlias}.
*
* @param address Bluetooth address of remote device
*/
public synchronized boolean clearRemoteAlias(String address) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return clearRemoteAliasNative(address);
}
private native boolean clearRemoteAliasNative(String address);
public synchronized boolean disconnectRemoteDeviceAcl(String address) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return disconnectRemoteDeviceNative(address);
}
private native boolean disconnectRemoteDeviceNative(String address);
private static final int MAX_OUTSTANDING_ASYNC = 32;
/**
* This method initiates a Bonding request to a remote device.
*
*
* @param address The Bluetooth address of the remote device
*
* @see #createBonding
* @see #cancelBondingProcess
* @see #removeBonding
* @see #hasBonding
* @see #listBondings
*
* @see android.bluetooth.PasskeyAgent
*/
public synchronized boolean createBonding(String address, IBluetoothDeviceCallback callback) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
HashMap<String, IBluetoothDeviceCallback> callbacks = mEventLoop.getCreateBondingCallbacks();
if (callbacks.containsKey(address)) {
Log.w(TAG, "createBonding() already in progress for " + address);
return false;
}
// Protect from malicious clients - limit number of outstanding requests
if (callbacks.size() > MAX_OUTSTANDING_ASYNC) {
Log.w(TAG, "Too many outstanding bonding requests, dropping request for " + address);
return false;
}
callbacks.put(address, callback);
if (!createBondingNative(address, 60000 /* 1 minute */)) {
callbacks.remove(address);
return false;
}
return true;
}
private native boolean createBondingNative(String address, int timeout_ms);
/**
* This method cancels a pending bonding request.
*
* @param address The Bluetooth address of the remote device to which a
* bonding request has been initiated.
*
* Note: When a request is canceled, method
* {@link CreateBondingResultNotifier#notifyAuthenticationFailed}
* will be called on the object passed to method
* {@link #createBonding}.
*
* Note: it is safe to call this method when there is no outstanding
* bonding request.
*
* @see #createBonding
* @see #cancelBondingProcess
* @see #removeBonding
* @see #hasBonding
* @see #listBondings
*/
public synchronized boolean cancelBondingProcess(String address) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return cancelBondingProcessNative(address);
}
private native boolean cancelBondingProcessNative(String address);
/**
* This method removes a bonding to a remote device. This is a local
* operation only, resulting in this adapter "forgetting" the bonding
* information about the specified remote device. The other device itself
* does not know what the bonding has been torn down. The next time either
* device attemps to connect to the other, the connection will fail, and
* the pairing procedure will have to be re-initiated.
*
* @param address The Bluetooth address of the remote device.
*
* @see #createBonding
* @see #cancelBondingProcess
* @see #removeBonding
* @see #hasBonding
* @see #listBondings
*/
public synchronized boolean removeBonding(String address) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return removeBondingNative(address);
}
private native boolean removeBondingNative(String address);
public synchronized boolean hasBonding(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return hasBondingNative(address);
}
private native boolean hasBondingNative(String address);
public synchronized String[] listAclConnections() {
checkPermissionBluetooth();
return listConnectionsNative();
}
private native String[] listConnectionsNative();
/**
* This method lists all remote devices that this adapter is aware of.
* This is a list not only of all most-recently discovered devices, but of
* all devices discovered by this adapter up to some point in the past.
* Note that many of these devices may not be in the neighborhood anymore,
* and attempting to connect to them will result in an error.
*
* @return An array of strings representing the Bluetooth addresses of all
* remote devices that this adapter is aware of.
*/
public synchronized String[] listRemoteDevices() {
checkPermissionBluetooth();
return listRemoteDevicesNative();
}
private native String[] listRemoteDevicesNative();
/**
* Returns the version of the Bluetooth chip. This version is compiled from
* the LMP version. In case of EDR the features attribute must be checked.
* Example: "Bluetooth 2.0 + EDR".
*
* @return a String representation of the this Adapter's underlying
* Bluetooth-chip version.
*/
public synchronized String getVersion() {
checkPermissionBluetooth();
return getVersionNative();
}
private native String getVersionNative();
/**
* Returns the revision of the Bluetooth chip. This is a vendor-specific
* value and in most cases it represents the firmware version. This might
* derive from the HCI revision and LMP subversion values or via extra
* vendord specific commands.
* In case the revision of a chip is not available. This method should
* return the LMP subversion value as a string.
* Example: "HCI 19.2"
*
* @return The HCI revision of this adapter.
*/
public synchronized String getRevision() {
checkPermissionBluetooth();
return getRevisionNative();
}
private native String getRevisionNative();
/**
* Returns the manufacturer of the Bluetooth chip. If the company id is not
* known the sting "Company ID %d" where %d should be replaced with the
* numeric value from the manufacturer field.
* Example: "Cambridge Silicon Radio"
*
* @return Manufacturer name.
*/
public synchronized String getManufacturer() {
checkPermissionBluetooth();
return getManufacturerNative();
}
private native String getManufacturerNative();
/**
* Returns the company name from the OUI database of the Bluetooth device
* address. This function will need a valid and up-to-date oui.txt from
* the IEEE. This value will be different from the manufacturer string in
* the most cases.
* If the oui.txt file is not present or the OUI part of the Bluetooth
* address is not listed, it should return the string "OUI %s" where %s is
* the actual OUI.
*
* Example: "Apple Computer"
*
* @return company name
*/
public synchronized String getCompany() {
checkPermissionBluetooth();
return getCompanyNative();
}
private native String getCompanyNative();
/**
* Like getVersion(), but for a remote device.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device Bluetooth version
*
* @see #getVersion
*/
public synchronized String getRemoteVersion(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteVersionNative(address);
}
private native String getRemoteVersionNative(String address);
/**
* Like getRevision(), but for a remote device.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device HCI revision
*
* @see #getRevision
*/
public synchronized String getRemoteRevision(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteRevisionNative(address);
}
private native String getRemoteRevisionNative(String address);
/**
* Like getManufacturer(), but for a remote device.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device Bluetooth chip manufacturer
*
* @see #getManufacturer
*/
public synchronized String getRemoteManufacturer(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteManufacturerNative(address);
}
private native String getRemoteManufacturerNative(String address);
/**
* Like getCompany(), but for a remote device.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device company
*
* @see #getCompany
*/
public synchronized String getRemoteCompany(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteCompanyNative(address);
}
private native String getRemoteCompanyNative(String address);
/**
* Returns the date and time when the specified remote device has been seen
* by a discover procedure.
* Example: "2006-02-08 12:00:00 GMT"
*
* @return a String with the timestamp.
*/
public synchronized String lastSeen(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return lastSeenNative(address);
}
private native String lastSeenNative(String address);
/**
* Returns the date and time when the specified remote device has last been
* connected to
* Example: "2006-02-08 12:00:00 GMT"
*
* @return a String with the timestamp.
*/
public synchronized String lastUsed(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return lastUsedNative(address);
}
private native String lastUsedNative(String address);
/**
* Gets the major device class of the specified device.
* Example: "computer"
*
* Note: This is simply a string desciption of the major class of the
* device-class information, which is returned as a 32-bit value
* during device discovery.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device major class
*
* @see #getRemoteClass
*/
public synchronized String getRemoteMajorClass(String address) {
if (!BluetoothDevice.checkBluetoothAddress(address)) {
checkPermissionBluetooth();
return null;
}
return getRemoteMajorClassNative(address);
}
private native String getRemoteMajorClassNative(String address);
/**
* Gets the minor device class of the specified device.
* Example: "laptop"
*
* Note: This is simply a string desciption of the minor class of the
* device-class information, which is returned as a 32-bit value
* during device discovery.
*
* @param address The Bluetooth address of the remote device.
*
* @return remote-device minor class
*
* @see #getRemoteClass
*/
public synchronized String getRemoteMinorClass(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteMinorClassNative(address);
}
private native String getRemoteMinorClassNative(String address);
/**
* Gets the service classes of the specified device.
* Example: ["networking", "object transfer"]
*
* @return a String array with the descriptions of the service classes.
*
* @see #getRemoteClass
*/
public synchronized String[] getRemoteServiceClasses(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteServiceClassesNative(address);
}
private native String[] getRemoteServiceClassesNative(String address);
/**
* Gets the remote major, minor, and service 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.
*
* @see #getRemoteMajorClass
* @see #getRemoteMinorClass
* @see #getRemoteServiceClasses
*/
public synchronized int getRemoteClass(String address) {
if (!BluetoothDevice.checkBluetoothAddress(address)) {
checkPermissionBluetooth();
return -1;
}
return getRemoteClassNative(address);
}
private native int getRemoteClassNative(String address);
/**
* Gets the remote features encoded as bit mask.
*
* Note: This method may be obsoleted soon.
*
* @return byte array of features.
*/
public synchronized byte[] getRemoteFeatures(String address) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteFeaturesNative(address);
}
private native byte[] getRemoteFeaturesNative(String address);
/**
* This method and {@link #getRemoteServiceRecord} query the SDP service
* on a remote device. They do not interpret the data, but simply return
* it raw to the user. To read more about SDP service handles and records,
* consult the Bluetooth core documentation (www.bluetooth.com).
*
* @param address Bluetooth address of remote device.
* @param match a String match to narrow down the service-handle search.
* The only supported value currently is "hsp" for the headset
* profile. To retrieve all service handles, simply pass an empty
* match string.
*
* @return all service handles corresponding to the string match.
*
* @see #getRemoteServiceRecord
*/
public synchronized int[] getRemoteServiceHandles(String address, String match) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
if (match == null) {
match = "";
}
return getRemoteServiceHandlesNative(address, match);
}
private native int[] getRemoteServiceHandlesNative(String address, String match);
/**
* This method retrieves the service records corresponding to a given
* service handle (method {@link #getRemoteServiceHandles} retrieves the
* service handles.)
*
* This method and {@link #getRemoteServiceHandles} do not interpret their
* data, but simply return it raw to the user. To read more about SDP
* service handles and records, consult the Bluetooth core documentation
* (www.bluetooth.com).
*
* @param address Bluetooth address of remote device.
* @param handle Service handle returned by {@link #getRemoteServiceHandles}
*
* @return a byte array of all service records corresponding to the
* specified service handle.
*
* @see #getRemoteServiceHandles
*/
public synchronized byte[] getRemoteServiceRecord(String address, int handle) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return null;
}
return getRemoteServiceRecordNative(address, handle);
}
private native byte[] getRemoteServiceRecordNative(String address, int handle);
// AIDL does not yet support short's
public synchronized boolean getRemoteServiceChannel(String address, int uuid16,
IBluetoothDeviceCallback callback) {
checkPermissionBluetooth();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
HashMap<String, IBluetoothDeviceCallback> callbacks =
mEventLoop.getRemoteServiceChannelCallbacks();
if (callbacks.containsKey(address)) {
Log.w(TAG, "SDP request already in progress for " + address);
return false;
}
// Protect from malicious clients - only allow 32 bonding requests per minute.
if (callbacks.size() > MAX_OUTSTANDING_ASYNC) {
Log.w(TAG, "Too many outstanding SDP requests, dropping request for " + address);
return false;
}
callbacks.put(address, callback);
if (!getRemoteServiceChannelNative(address, (short)uuid16)) {
callbacks.remove(address);
return false;
}
return true;
}
private native boolean getRemoteServiceChannelNative(String address, short uuid16);
public synchronized boolean setPin(String address, byte[] pin) {
checkPermissionBluetoothAdmin();
if (pin == null || pin.length <= 0 || pin.length > 16 ||
!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
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());
}
private native boolean setPinNative(String address, String pin, int nativeData);
public synchronized boolean cancelPin(String address) {
checkPermissionBluetoothAdmin();
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address);
if (data == null) {
Log.w(TAG, "cancelPin(" + 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 cancelPinNative(address, data.intValue());
}
private native boolean cancelPinNative(String address, int natveiData);
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
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.System.getInt(resolver, Settings.System.BLUETOOTH_ON, 0) > 0) {
if (enabled) {
enable(null);
} else {
disable();
}
}
}
}
};
private void registerForAirplaneMode() {
String airplaneModeRadios = Settings.System.getString(mContext.getContentResolver(),
Settings.System.AIRPLANE_MODE_RADIOS);
mIsAirplaneSensitive = airplaneModeRadios == null
? true : airplaneModeRadios.contains(Settings.System.RADIO_BLUETOOTH);
if (mIsAirplaneSensitive) {
mIntentFilter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
mContext.registerReceiver(mReceiver, mIntentFilter);
}
}
/* 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;
}
private static final String BLUETOOTH_ADMIN = android.Manifest.permission.BLUETOOTH_ADMIN;
private static final String BLUETOOTH = android.Manifest.permission.BLUETOOTH;
private void checkPermissionBluetoothAdmin() {
if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires BLUETOOTH_ADMIN permission");
}
}
private void checkPermissionBluetooth() {
if (mContext.checkCallingOrSelfPermission(BLUETOOTH_ADMIN) !=
PackageManager.PERMISSION_GRANTED &&
mContext.checkCallingOrSelfPermission(BLUETOOTH) !=
PackageManager.PERMISSION_GRANTED ) {
throw new SecurityException("Requires BLUETOOTH or BLUETOOTH_ADMIN permission");
}
}
private static final String DISABLE_ESCO_PATH = "/sys/module/sco/parameters/disable_esco";
private static void disableEsco() {
try {
FileWriter file = new FileWriter(DISABLE_ESCO_PATH);
file.write("Y");
file.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mIsEnabled) {
pw.println("\nBluetooth ENABLED: " + getAddress() + " (" + getName() + ")");
pw.println("\nisDiscovering() = " + isDiscovering());
BluetoothHeadset headset = new BluetoothHeadset(mContext);
pw.println("\n--Bondings--");
String[] addresses = listBondings();
for (String address : addresses) {
String name = getRemoteName(address);
pw.println(address + " (" + name + ")");
}
pw.println("\n--Current ACL Connections--");
addresses = listAclConnections();
for (String address : addresses) {
String name = getRemoteName(address);
pw.println(address + " (" + name + ")");
}
pw.println("\n--Known Devices--");
addresses = listRemoteDevices();
for (String address : addresses) {
String name = getRemoteName(address);
pw.println(address + " (" + name + ")");
}
// Rather not do this from here, but no-where else and I need this
// dump
pw.println("\n--Headset Service--");
switch (headset.getState()) {
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("getHeadsetAddress() = " + headset.getHeadsetAddress());
headset.close();
} else {
pw.println("\nBluetooth DISABLED");
}
pw.println("\nmIsAirplaneSensitive = " + mIsAirplaneSensitive);
}
}