/* * 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 BluetoothEventLoop mEventLoop; private IntentFilter mIntentFilter; private boolean mIsAirplaneSensitive; private volatile boolean mIsEnabled; // local cache of isEnabledNative() private boolean mIsDiscovering; 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; 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() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mIsEnabled; } private native int isEnabledNative(); /** * Disable bluetooth. Returns true on success. */ public synchronized boolean disable() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (mEnableThread != null && mEnableThread.isAlive()) { return false; } if (!mIsEnabled) { return true; } mEventLoop.stop(); disableNative(); mIsEnabled = false; Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, 0); mIsDiscovering = false; Intent intent = new Intent(BluetoothIntent.DISABLED_ACTION); mContext.sendBroadcast(intent, BLUETOOTH_PERM); 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) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); // 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 String mOutgoingBondingDevAddress = null; 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; Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BLUETOOTH_ON, 1); mIsDiscovering = false; Intent intent = new Intent(BluetoothIntent.ENABLED_ACTION); mContext.sendBroadcast(intent, BLUETOOTH_PERM); mHandler.sendMessageDelayed(mHandler.obtainMessage(REGISTER_SDP_RECORDS), 3000); } mEnableThread = null; } }; private native int enableNative(); private native int disableNative(); public synchronized String getAddress() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getAddressNative(); } private native String getAddressNative(); public synchronized String getName() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getNameNative(); } private native String getNameNative(); public synchronized boolean setName(String name) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 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() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return listBondingsNative(); } private native String[] listBondingsNative(); public synchronized String getMajorClass() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getMajorClassNative(); } private native String getMajorClassNative(); public synchronized String getMinorClass() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 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) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 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) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 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() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return cancelDiscoveryNative(); } private native boolean cancelDiscoveryNative(); public synchronized boolean isDiscovering() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return mIsDiscovering; } /* package */ void setIsDiscovering(boolean isDiscovering) { mIsDiscovering = isDiscovering; } public synchronized boolean startPeriodicDiscovery() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return startPeriodicDiscoveryNative(); } private native boolean startPeriodicDiscoveryNative(); public synchronized boolean stopPeriodicDiscovery() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return stopPeriodicDiscoveryNative(); } private native boolean stopPeriodicDiscoveryNative(); public synchronized boolean isPeriodicDiscovery() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 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) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); 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() { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); return getDiscoverableTimeoutNative(); } private native int getDiscoverableTimeoutNative(); public synchronized boolean isAclConnected(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); 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() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
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() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
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() {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
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) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
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) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
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) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
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) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
return clearRemoteAliasNative(address);
}
private native boolean clearRemoteAliasNative(String address);
public synchronized boolean disconnectRemoteDeviceAcl(String address) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
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) {
mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM,
"Need BLUETOOTH_ADMIN permission");
if (!BluetoothDevice.checkBluetoothAddress(address)) {
return false;
}
HashMap