/* * 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 *

* 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.) *

* 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 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 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); } }