diff options
Diffstat (limited to 'core/java/android/bluetooth')
23 files changed, 2876 insertions, 1010 deletions
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index 2ea45d5..e8a69d8 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -25,7 +25,10 @@ import android.os.RemoteException; import android.os.IBinder; import android.util.Log; -import java.util.List; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; /** * Public API for controlling the Bluetooth A2DP Profile Service. @@ -39,32 +42,30 @@ import java.util.List; * * Currently the BluetoothA2dp service runs in the system server and this * proxy object will be immediately bound to the service on construction. - * However this may change in future releases, and error codes such as - * BluetoothError.ERROR_IPC_NOT_READY will be returned from this API when the - * proxy object is not yet attached. * * Currently this class provides methods to connect to A2DP audio sinks. * * @hide */ -public class BluetoothA2dp { +public final class BluetoothA2dp { private static final String TAG = "BluetoothA2dp"; private static final boolean DBG = false; - /** int extra for SINK_STATE_CHANGED_ACTION */ - public static final String SINK_STATE = - "android.bluetooth.a2dp.intent.SINK_STATE"; - /** int extra for SINK_STATE_CHANGED_ACTION */ - public static final String SINK_PREVIOUS_STATE = - "android.bluetooth.a2dp.intent.SINK_PREVIOUS_STATE"; + /** int extra for ACTION_SINK_STATE_CHANGED */ + public static final String EXTRA_SINK_STATE = + "android.bluetooth.a2dp.extra.SINK_STATE"; + /** int extra for ACTION_SINK_STATE_CHANGED */ + public static final String EXTRA_PREVIOUS_SINK_STATE = + "android.bluetooth.a2dp.extra.PREVIOUS_SINK_STATE"; /** Indicates the state of an A2DP audio sink has changed. - * This intent will always contain SINK_STATE, SINK_PREVIOUS_STATE and - * BluetoothIntent.ADDRESS extras. + * This intent will always contain EXTRA_SINK_STATE, + * EXTRA_PREVIOUS_SINK_STATE and BluetoothDevice.EXTRA_DEVICE + * extras. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String SINK_STATE_CHANGED_ACTION = - "android.bluetooth.a2dp.intent.action.SINK_STATE_CHANGED"; + public static final String ACTION_SINK_STATE_CHANGED = + "android.bluetooth.a2dp.action.SINK_STATE_CHANGED"; public static final int STATE_DISCONNECTED = 0; public static final int STATE_CONNECTING = 1; @@ -79,6 +80,7 @@ public class BluetoothA2dp { /** Default priority for a2dp devices that should not allow incoming * connections */ public static final int PRIORITY_OFF = 0; + private final IBluetoothA2dp mService; private final Context mContext; @@ -89,84 +91,123 @@ public class BluetoothA2dp { */ public BluetoothA2dp(Context c) { mContext = c; + IBinder b = ServiceManager.getService(BluetoothA2dpService.BLUETOOTH_A2DP_SERVICE); - if (b == null) { - throw new RuntimeException("Bluetooth A2DP service not available!"); + if (b != null) { + mService = IBluetoothA2dp.Stub.asInterface(b); + } else { + Log.w(TAG, "Bluetooth A2DP service not available!"); + + // Instead of throwing an exception which prevents people from going + // into Wireless settings in the emulator. Let it crash later when it is actually used. + mService = null; } - mService = IBluetoothA2dp.Stub.asInterface(b); } /** Initiate a connection to an A2DP sink. * Listen for SINK_STATE_CHANGED_ACTION to find out when the * connection is completed. - * @param address Remote BT address. - * @return Result code, negative indicates an immediate error. + * @param device Remote BT device. + * @return false on immediate error, true otherwise * @hide */ - public int connectSink(String address) { - if (DBG) log("connectSink(" + address + ")"); + public boolean connectSink(BluetoothDevice device) { + if (DBG) log("connectSink(" + device + ")"); try { - return mService.connectSink(address); + return mService.connectSink(device); } catch (RemoteException e) { - Log.w(TAG, "", e); - return BluetoothError.ERROR_IPC; + Log.e(TAG, "", e); + return false; } } /** Initiate disconnect from an A2DP sink. * Listen for SINK_STATE_CHANGED_ACTION to find out when * disconnect is completed. - * @param address Remote BT address. - * @return Result code, negative indicates an immediate error. + * @param device Remote BT device. + * @return false on immediate error, true otherwise + * @hide + */ + public boolean disconnectSink(BluetoothDevice device) { + if (DBG) log("disconnectSink(" + device + ")"); + try { + return mService.disconnectSink(device); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** Initiate suspend from an A2DP sink. + * Listen for SINK_STATE_CHANGED_ACTION to find out when + * suspend is completed. + * @param device Remote BT device. + * @return false on immediate error, true otherwise + * @hide + */ + public boolean suspendSink(BluetoothDevice device) { + try { + return mService.suspendSink(device); + } catch (RemoteException e) { + Log.e(TAG, "", e); + return false; + } + } + + /** Initiate resume from an suspended A2DP sink. + * Listen for SINK_STATE_CHANGED_ACTION to find out when + * resume is completed. + * @param device Remote BT device. + * @return false on immediate error, true otherwise * @hide */ - public int disconnectSink(String address) { - if (DBG) log("disconnectSink(" + address + ")"); + public boolean resumeSink(BluetoothDevice device) { try { - return mService.disconnectSink(address); + return mService.resumeSink(device); } catch (RemoteException e) { - Log.w(TAG, "", e); - return BluetoothError.ERROR_IPC; + Log.e(TAG, "", e); + return false; } } /** Check if a specified A2DP sink is connected. - * @param address Remote BT address. + * @param device Remote BT device. * @return True if connected (or playing), false otherwise and on error. * @hide */ - public boolean isSinkConnected(String address) { - if (DBG) log("isSinkConnected(" + address + ")"); - int state = getSinkState(address); + public boolean isSinkConnected(BluetoothDevice device) { + if (DBG) log("isSinkConnected(" + device + ")"); + int state = getSinkState(device); return state == STATE_CONNECTED || state == STATE_PLAYING; } /** Check if any A2DP sink is connected. - * @return a List of connected A2DP sinks, or null on error. + * @return a unmodifiable set of connected A2DP sinks, or null on error. * @hide */ - public List<String> listConnectedSinks() { - if (DBG) log("listConnectedSinks()"); + public Set<BluetoothDevice> getConnectedSinks() { + if (DBG) log("getConnectedSinks()"); try { - return mService.listConnectedSinks(); + return Collections.unmodifiableSet( + new HashSet<BluetoothDevice>(Arrays.asList(mService.getConnectedSinks()))); } catch (RemoteException e) { - Log.w(TAG, "", e); + Log.e(TAG, "", e); return null; } } /** Get the state of an A2DP sink - * @param address Remote BT address. - * @return State code, or negative on error + * @param device Remote BT device. + * @return State code, one of STATE_ * @hide */ - public int getSinkState(String address) { - if (DBG) log("getSinkState(" + address + ")"); + public int getSinkState(BluetoothDevice device) { + if (DBG) log("getSinkState(" + device + ")"); try { - return mService.getSinkState(address); + return mService.getSinkState(device); } catch (RemoteException e) { - Log.w(TAG, "", e); - return BluetoothError.ERROR_IPC; + Log.e(TAG, "", e); + return BluetoothA2dp.STATE_DISCONNECTED; } } @@ -177,58 +218,33 @@ public class BluetoothA2dp { * Sinks with priority greater than zero will accept incoming connections * (if no sink is currently connected). * Priority for unpaired sink must be PRIORITY_NONE. - * @param address Paired sink + * @param device Paired sink * @param priority Integer priority, for example PRIORITY_AUTO or * PRIORITY_NONE - * @return Result code, negative indicates an error + * @return true if priority is set, false on error */ - public int setSinkPriority(String address, int priority) { - if (DBG) log("setSinkPriority(" + address + ", " + priority + ")"); + public boolean setSinkPriority(BluetoothDevice device, int priority) { + if (DBG) log("setSinkPriority(" + device + ", " + priority + ")"); try { - return mService.setSinkPriority(address, priority); + return mService.setSinkPriority(device, priority); } catch (RemoteException e) { - Log.w(TAG, "", e); - return BluetoothError.ERROR_IPC; + Log.e(TAG, "", e); + return false; } } /** * Get priority of a2dp sink. - * @param address Sink + * @param device Sink * @return non-negative priority, or negative error code on error. */ - public int getSinkPriority(String address) { - if (DBG) log("getSinkPriority(" + address + ")"); + public int getSinkPriority(BluetoothDevice device) { + if (DBG) log("getSinkPriority(" + device + ")"); try { - return mService.getSinkPriority(address); + return mService.getSinkPriority(device); } catch (RemoteException e) { - Log.w(TAG, "", e); - return BluetoothError.ERROR_IPC; - } - } - - /** - * Check class bits for possible A2DP Sink support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might be a A2DP Sink. It is not accurate for all - * devices. It tries to err on the side of false positives. - * @return True if this device might be a A2DP sink - */ - public static boolean doesClassMatchSink(int btClass) { - if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) { - return true; - } - // By the A2DP spec, sinks must indicate the RENDER service. - // However we found some that do not (Chordette). So lets also - // match on some other class bits. - switch (BluetoothClass.Device.getDevice(btClass)) { - case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: - case BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER: - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; + Log.e(TAG, "", e); + return PRIORITY_OFF; } } diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java new file mode 100644 index 0000000..bd5b07c --- /dev/null +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -0,0 +1,849 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +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.util.Log; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Random; +import java.util.Set; +import java.util.UUID; + +/** + * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter} + * lets you perform fundamental Bluetooth tasks, such as initiate + * device discovery, query a list of bonded (paired) devices, + * instantiate a {@link BluetoothDevice} using a known MAC address, and create + * a {@link BluetoothServerSocket} to listen for connection requests from other + * devices. + * + * <p>To get a {@link BluetoothAdapter} representing the local Bluetooth + * adapter, call the static {@link #getDefaultAdapter} method. + * Fundamentally, this is your starting point for all + * Bluetooth actions. Once you have the local adapter, you can get a set of + * {@link BluetoothDevice} objects representing all paired devices with + * {@link #getBondedDevices()}; start device discovery with + * {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to + * listen for incoming connection requests with + * {@link #listenUsingRfcommWithServiceRecord(String,UUID)}. + * + * <p class="note"><strong>Note:</strong> + * Most methods require the {@link android.Manifest.permission#BLUETOOTH} + * permission and some also require the + * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission. + * + * {@see BluetoothDevice} + * {@see BluetoothServerSocket} + */ +public final class BluetoothAdapter { + private static final String TAG = "BluetoothAdapter"; + private static final boolean DBG = false; + + /** + * Sentinel error value for this class. Guaranteed to not equal any other + * integer constant in this class. Provided as a convenience for functions + * that require a sentinel error value, for example: + * <p><code>Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + * BluetoothAdapter.ERROR)</code> + */ + public static final int ERROR = Integer.MIN_VALUE; + + /** + * Broadcast Action: The state of the local Bluetooth adapter has been + * changed. + * <p>For example, Bluetooth has been turned on or off. + * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link + * #EXTRA_PREVIOUS_STATE} containing the new and old states + * respectively. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_STATE_CHANGED = + "android.bluetooth.adapter.action.STATE_CHANGED"; + + /** + * Used as an int extra field in {@link #ACTION_STATE_CHANGED} + * intents to request the current power state. Possible values are: + * {@link #STATE_OFF}, + * {@link #STATE_TURNING_ON}, + * {@link #STATE_ON}, + * {@link #STATE_TURNING_OFF}, + */ + public static final String EXTRA_STATE = + "android.bluetooth.adapter.extra.STATE"; + /** + * Used as an int extra field in {@link #ACTION_STATE_CHANGED} + * intents to request the previous power state. Possible values are: + * {@link #STATE_OFF}, + * {@link #STATE_TURNING_ON}, + * {@link #STATE_ON}, + * {@link #STATE_TURNING_OFF}, + */ + public static final String EXTRA_PREVIOUS_STATE = + "android.bluetooth.adapter.extra.PREVIOUS_STATE"; + + /** + * Indicates the local Bluetooth adapter is off. + */ + public static final int STATE_OFF = 10; + /** + * Indicates the local Bluetooth adapter is turning on. However local + * clients should wait for {@link #STATE_ON} before attempting to + * use the adapter. + */ + public static final int STATE_TURNING_ON = 11; + /** + * Indicates the local Bluetooth adapter is on, and ready for use. + */ + public static final int STATE_ON = 12; + /** + * Indicates the local Bluetooth adapter is turning off. Local clients + * should immediately attempt graceful disconnection of any remote links. + */ + public static final int STATE_TURNING_OFF = 13; + + /** + * Activity Action: Show a system activity that requests discoverable mode. + * <p>This activity will also request the user to turn on Bluetooth if it + * is not currently enabled. + * <p>Discoverable mode is equivalent to {@link + * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see + * this Bluetooth adapter when they perform a discovery. + * <p>For privacy, Android is not by default discoverable. + * <p>The sender can optionally use extra field {@link + * #EXTRA_DISCOVERABLE_DURATION} to request the duration of + * discoverability. Currently the default duration is 120 seconds, and + * maximum duration is capped at 300 seconds for each request. + * <p>Notification of the result of this activity is posted using the + * {@link android.app.Activity#onActivityResult} callback. The + * <code>resultCode</code> + * will be the duration (in seconds) of discoverability or + * {@link android.app.Activity#RESULT_CANCELED} if the user rejected + * discoverability or an error has occurred. + * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED} + * for global notification whenever the scan mode changes. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_DISCOVERABLE = + "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"; + + /** + * Used as an optional int extra field in {@link + * #ACTION_REQUEST_DISCOVERABLE} intents to request a specific duration + * for discoverability in seconds. The current default is 120 seconds, and + * requests over 300 seconds will be capped. These values could change. + */ + public static final String EXTRA_DISCOVERABLE_DURATION = + "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION"; + + /** + * Activity Action: Show a system activity that allows the user to turn on + * Bluetooth. + * <p>This system activity will return once Bluetooth has completed turning + * on, or the user has decided not to turn Bluetooth on. + * <p>Notification of the result of this activity is posted using the + * {@link android.app.Activity#onActivityResult} callback. The + * <code>resultCode</code> + * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been + * turned on or {@link android.app.Activity#RESULT_CANCELED} if the user + * has rejected the request or an error has occurred. + * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED} + * for global notification whenever Bluetooth is turned on or off. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_ENABLE = + "android.bluetooth.adapter.action.REQUEST_ENABLE"; + + /** + * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter + * has changed. + * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link + * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes + * respectively. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_SCAN_MODE_CHANGED = + "android.bluetooth.adapter.action.SCAN_MODE_CHANGED"; + + /** + * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} + * intents to request the current scan mode. Possible values are: + * {@link #SCAN_MODE_NONE}, + * {@link #SCAN_MODE_CONNECTABLE}, + * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, + */ + public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE"; + /** + * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED} + * intents to request the previous scan mode. Possible values are: + * {@link #SCAN_MODE_NONE}, + * {@link #SCAN_MODE_CONNECTABLE}, + * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}, + */ + public static final String EXTRA_PREVIOUS_SCAN_MODE = + "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE"; + + /** + * Indicates that both inquiry scan and page scan are disabled on the local + * Bluetooth adapter. Therefore this device is neither discoverable + * nor connectable from remote Bluetooth devices. + */ + public static final int SCAN_MODE_NONE = 20; + /** + * Indicates that inquiry scan is disabled, but page scan is enabled on the + * local Bluetooth adapter. Therefore this device is not discoverable from + * remote Bluetooth devices, but is connectable from remote devices that + * have previously discovered this device. + */ + public static final int SCAN_MODE_CONNECTABLE = 21; + /** + * Indicates that both inquiry scan and page scan are enabled on the local + * Bluetooth adapter. Therefore this device is both discoverable and + * connectable from remote Bluetooth devices. + */ + public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23; + + + /** + * Broadcast Action: The local Bluetooth adapter has started the remote + * device discovery process. + * <p>This usually involves an inquiry scan of about 12 seconds, followed + * by a page scan of each new device to retrieve its Bluetooth name. + * <p>Register for {@link BluetoothDevice#ACTION_FOUND} to be notified as + * remote Bluetooth devices are found. + * <p>Device discovery is a heavyweight procedure. New connections to + * remote Bluetooth devices should not be attempted while discovery is in + * progress, and existing connections will experience limited bandwidth + * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing + * discovery. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DISCOVERY_STARTED = + "android.bluetooth.adapter.action.DISCOVERY_STARTED"; + /** + * Broadcast Action: The local Bluetooth adapter has finished the device + * discovery process. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DISCOVERY_FINISHED = + "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; + + /** + * Broadcast Action: The local Bluetooth adapter has changed its friendly + * Bluetooth name. + * <p>This name is visible to remote Bluetooth devices. + * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing + * the name. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LOCAL_NAME_CHANGED = + "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED"; + /** + * Used as a String extra field in {@link #ACTION_LOCAL_NAME_CHANGED} + * intents to request the local Bluetooth name. + */ + public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME"; + + /** @hide */ + public static final String BLUETOOTH_SERVICE = "bluetooth"; + + private static final int ADDRESS_LENGTH = 17; + + /** + * Lazyily initialized singleton. Guaranteed final after first object + * constructed. + */ + private static BluetoothAdapter sAdapter; + + private final IBluetooth mService; + + /** + * Get a handle to the default local Bluetooth adapter. + * <p>Currently Android only supports one Bluetooth adapter, but the API + * could be extended to support more. This will always return the default + * adapter. + * @return the default local adapter, or null if Bluetooth is not supported + * on this hardware platform + */ + public static synchronized BluetoothAdapter getDefaultAdapter() { + if (sAdapter == null) { + IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + if (b != null) { + IBluetooth service = IBluetooth.Stub.asInterface(b); + sAdapter = new BluetoothAdapter(service); + } + } + return sAdapter; + } + + /** + * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance. + * @hide + */ + public BluetoothAdapter(IBluetooth service) { + if (service == null) { + throw new IllegalArgumentException("service is null"); + } + mService = service; + } + + /** + * Get a {@link BluetoothDevice} object for the given Bluetooth hardware + * address. + * <p>Valid Bluetooth hardware addresses must be upper case, in a format + * such as "00:11:22:33:AA:BB". The helper {@link #checkBluetoothAddress} is + * available to validate a Bluetooth address. + * <p>A {@link BluetoothDevice} will always be returned for a valid + * hardware address, even if this adapter has never seen that device. + * + * @param address valid Bluetooth MAC address + * @throws IllegalArgumentException if address is invalid + */ + public BluetoothDevice getRemoteDevice(String address) { + return new BluetoothDevice(address); + } + + /** + * Return true if Bluetooth is currently enabled and ready for use. + * <p>Equivalent to: + * <code>getBluetoothState() == STATE_ON</code> + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return true if the local adapter is turned on + */ + public boolean isEnabled() { + try { + return mService.isEnabled(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Get the current state of the local Bluetooth adapter. + * <p>Possible return values are + * {@link #STATE_OFF}, + * {@link #STATE_TURNING_ON}, + * {@link #STATE_ON}, + * {@link #STATE_TURNING_OFF}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return current state of Bluetooth adapter + */ + public int getState() { + try { + return mService.getBluetoothState(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return STATE_OFF; + } + + /** + * Turn on the local Bluetooth adapter. + * <p>This powers on the underlying Bluetooth hardware, and starts all + * Bluetooth system services. + * <p>This is an asynchronous call: it will return immediately, and + * clients should listen for {@link #ACTION_STATE_CHANGED} + * to be notified of subsequent adapter state changes. If this call returns + * true, then the adapter state will immediately transition from {@link + * #STATE_OFF} to {@link #STATE_TURNING_ON}, and some time + * later transition to either {@link #STATE_OFF} or {@link + * #STATE_ON}. If this call returns false then there was an + * immediate problem that will prevent the adapter from being turned on - + * such as Airplane mode, or the adapter is already turned on. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @return true to indicate adapter startup has begun, or false on + * immediate error + */ + public boolean enable() { + try { + return mService.enable(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Turn off the local Bluetooth adapter. + * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth + * system services, and powers down the underlying Bluetooth hardware. + * <p>This is an asynchronous call: it will return immediately, and + * clients should listen for {@link #ACTION_STATE_CHANGED} + * to be notified of subsequent adapter state changes. If this call returns + * true, then the adapter state will immediately transition from {@link + * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time + * later transition to either {@link #STATE_OFF} or {@link + * #STATE_ON}. If this call returns false then there was an + * immediate problem that will prevent the adapter from being turned off - + * such as the adapter already being turned off. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @return true to indicate adapter shutdown has begun, or false on + * immediate error + */ + public boolean disable() { + try { + return mService.disable(true); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Returns the hardware address of the local Bluetooth adapter. + * <p>For example, "00:11:22:AA:BB:CC". + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return Bluetooth hardware address as string + */ + public String getAddress() { + try { + return mService.getAddress(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + + /** + * Get the friendly Bluetooth name of the local Bluetooth adapter. + * <p>This name is visible to remote Bluetooth devices. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return the Bluetooth name, or null on error + */ + public String getName() { + try { + return mService.getName(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + + /** + * Set the friendly Bluetooth name of the local Bluetoth adapter. + * <p>This name is visible to remote Bluetooth devices. + * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however + * many remote devices can only display the first 40 characters, and some + * may be limited to just 20. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param name a valid Bluetooth name + * @return true if the name was set, false otherwise + */ + public boolean setName(String name) { + try { + return mService.setName(name); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Get the current Bluetooth scan mode of the local Bluetooth adaper. + * <p>The Bluetooth scan mode determines if the local adapter is + * connectable and/or discoverable from remote Bluetooth devices. + * <p>Possible values are: + * {@link #SCAN_MODE_NONE}, + * {@link #SCAN_MODE_CONNECTABLE}, + * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return scan mode + */ + public int getScanMode() { + try { + return mService.getScanMode(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return SCAN_MODE_NONE; + } + + /** + * Set the Bluetooth scan mode of the local Bluetooth adapter. + * <p>The Bluetooth scan mode determines if the local adapter is + * connectable and/or discoverable from remote Bluetooth devices. + * <p>For privacy reasons, discoverable mode is automatically turned off + * after <code>duration</code> seconds. For example, 120 seconds should be + * enough for a remote device to initiate and complete its discovery + * process. + * <p>Valid scan mode values are: + * {@link #SCAN_MODE_NONE}, + * {@link #SCAN_MODE_CONNECTABLE}, + * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} + * <p>Applications cannot set the scan mode. They should use + * <code>startActivityForResult( + * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE}) + * </code>instead. + * + * @param mode valid scan mode + * @param duration time in seconds to apply scan mode, only used for + * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE} + * @return true if the scan mode was set, false otherwise + * @hide + */ + public boolean setScanMode(int mode, int duration) { + try { + return mService.setScanMode(mode, duration); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** @hide */ + public boolean setScanMode(int mode) { + return setScanMode(mode, 120); + } + + /** @hide */ + public int getDiscoverableTimeout() { + try { + return mService.getDiscoverableTimeout(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return -1; + } + + /** @hide */ + public void setDiscoverableTimeout(int timeout) { + try { + mService.setDiscoverableTimeout(timeout); + } catch (RemoteException e) {Log.e(TAG, "", e);} + } + + /** + * Start the remote device discovery process. + * <p>The discovery process usually involves an inquiry scan of about 12 + * seconds, followed by a page scan of each new device to retrieve its + * Bluetooth name. + * <p>This is an asynchronous call, it will return immediately. Register + * for {@link #ACTION_DISCOVERY_STARTED} and {@link + * #ACTION_DISCOVERY_FINISHED} intents to determine exactly when the + * discovery starts and completes. Register for {@link + * BluetoothDevice#ACTION_FOUND} to be notified as remote Bluetooth devices + * are found. + * <p>Device discovery is a heavyweight procedure. New connections to + * remote Bluetooth devices should not be attempted while discovery is in + * progress, and existing connections will experience limited bandwidth + * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing + * discovery. + * <p>Device discovery will only find remote devices that are currently + * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are + * not discoverable by default, and need to be entered into a special mode. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @return true on success, false on error + */ + public boolean startDiscovery() { + try { + return mService.startDiscovery(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Cancel the current device discovery process. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @return true on success, false on error + */ + public boolean cancelDiscovery() { + try { + mService.cancelDiscovery(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Return true if the local Bluetooth adapter is currently in the device + * discovery process. + * <p>Device discovery is a heavyweight procedure. New connections to + * remote Bluetooth devices should not be attempted while discovery is in + * progress, and existing connections will experience limited bandwidth + * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing + * discovery. + * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED} + * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery + * starts or completes. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return true if discovering + */ + public boolean isDiscovering() { + try { + return mService.isDiscovering(); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Return the set of {@link BluetoothDevice} objects that are bonded + * (paired) to the local adapter. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return unmodifiable set of {@link BluetoothDevice}, or null on error + */ + public Set<BluetoothDevice> getBondedDevices() { + try { + return toDeviceSet(mService.listBonds()); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + + /** + * Picks RFCOMM channels until none are left. + * Avoids reserved channels. + */ + private static class RfcommChannelPicker { + private static final int[] RESERVED_RFCOMM_CHANNELS = new int[] { + 10, // HFAG + 11, // HSAG + 12, // OPUSH + 19, // PBAP + }; + private static LinkedList<Integer> sChannels; // master list of non-reserved channels + private static Random sRandom; + + private final LinkedList<Integer> mChannels; // local list of channels left to try + + private final UUID mUuid; + + public RfcommChannelPicker(UUID uuid) { + synchronized (RfcommChannelPicker.class) { + if (sChannels == null) { + // lazy initialization of non-reserved rfcomm channels + sChannels = new LinkedList<Integer>(); + for (int i = 1; i <= BluetoothSocket.MAX_RFCOMM_CHANNEL; i++) { + sChannels.addLast(new Integer(i)); + } + for (int reserved : RESERVED_RFCOMM_CHANNELS) { + sChannels.remove(new Integer(reserved)); + } + sRandom = new Random(); + } + mChannels = (LinkedList<Integer>)sChannels.clone(); + } + mUuid = uuid; + } + /* Returns next random channel, or -1 if we're out */ + public int nextChannel() { + if (mChannels.size() == 0) { + return -1; + } + return mChannels.remove(sRandom.nextInt(mChannels.size())); + } + } + + /** + * Create a listening, secure RFCOMM Bluetooth socket. + * <p>A remote device connecting to this socket will be authenticated and + * communication on this socket will be encrypted. + * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming + * connections from a listening {@link BluetoothServerSocket}. + * <p>Valid RFCOMM channels are in range 1 to 30. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * @param channel RFCOMM channel to listen on + * @return a listening RFCOMM BluetoothServerSocket + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions, or channel in use. + * @hide + */ + public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException { + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_RFCOMM, true, true, channel); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + try { + socket.close(); + } catch (IOException e) {} + socket.mSocket.throwErrnoNative(errno); + } + return socket; + } + + /** + * Create a listening, secure RFCOMM Bluetooth socket with Service Record. + * <p>A remote device connecting to this socket will be authenticated and + * communication on this socket will be encrypted. + * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming + * connections from a listening {@link BluetoothServerSocket}. + * <p>The system will assign an unused RFCOMM channel to listen on. + * <p>The system will also register a Service Discovery + * Protocol (SDP) record with the local SDP server containing the specified + * UUID, service name, and auto-assigned channel. Remote Bluetooth devices + * can use the same UUID to query our SDP server and discover which channel + * to connect to. This SDP record will be removed when this socket is + * closed, or if this application closes unexpectedly. + * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to + * connect to this socket from another device using the same {@link UUID}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * @param name service name for SDP record + * @param uuid uuid for SDP record + * @return a listening RFCOMM BluetoothServerSocket + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions, or channel in use. + */ + public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) + throws IOException { + RfcommChannelPicker picker = new RfcommChannelPicker(uuid); + + BluetoothServerSocket socket; + int channel; + int errno; + while (true) { + channel = picker.nextChannel(); + + if (channel == -1) { + throw new IOException("No available channels"); + } + + socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_RFCOMM, true, true, channel); + errno = socket.mSocket.bindListen(); + if (errno == 0) { + if (DBG) Log.d(TAG, "listening on RFCOMM channel " + channel); + break; // success + } else if (errno == BluetoothSocket.EADDRINUSE) { + if (DBG) Log.d(TAG, "RFCOMM channel " + channel + " in use"); + try { + socket.close(); + } catch (IOException e) {} + continue; // try another channel + } else { + try { + socket.close(); + } catch (IOException e) {} + socket.mSocket.throwErrnoNative(errno); // Exception as a result of bindListen() + } + } + + int handle = -1; + try { + handle = mService.addRfcommServiceRecord(name, new ParcelUuid(uuid), channel, + new Binder()); + } catch (RemoteException e) {Log.e(TAG, "", e);} + if (handle == -1) { + try { + socket.close(); + } catch (IOException e) {} + throw new IOException("Not able to register SDP record for " + name); + } + socket.setCloseHandler(mHandler, handle); + return socket; + } + + /** + * Construct an unencrypted, unauthenticated, RFCOMM server socket. + * Call #accept to retrieve connections to this socket. + * @return An RFCOMM BluetoothServerSocket + * @throws IOException On error, for example Bluetooth not available, or + * insufficient permissions. + * @hide + */ + public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_RFCOMM, false, false, port); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + try { + socket.close(); + } catch (IOException e) {} + socket.mSocket.throwErrnoNative(errno); + } + return socket; + } + + /** + * Construct a SCO server socket. + * Call #accept to retrieve connections to this socket. + * @return A SCO BluetoothServerSocket + * @throws IOException On error, for example Bluetooth not available, or + * insufficient permissions. + * @hide + */ + public static BluetoothServerSocket listenUsingScoOn() throws IOException { + BluetoothServerSocket socket = new BluetoothServerSocket( + BluetoothSocket.TYPE_SCO, false, false, -1); + int errno = socket.mSocket.bindListen(); + if (errno != 0) { + try { + socket.close(); + } catch (IOException e) {} + socket.mSocket.throwErrnoNative(errno); + } + return socket; + } + + private Set<BluetoothDevice> toDeviceSet(String[] addresses) { + Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length); + for (int i = 0; i < addresses.length; i++) { + devices.add(getRemoteDevice(addresses[i])); + } + return Collections.unmodifiableSet(devices); + } + + private Handler mHandler = new Handler() { + public void handleMessage(Message msg) { + /* handle socket closing */ + int handle = msg.what; + try { + if (DBG) Log.d(TAG, "Removing service record " + Integer.toHexString(handle)); + mService.removeServiceRecord(handle); + } catch (RemoteException e) {Log.e(TAG, "", e);} + } + }; + + /** + * Validate a Bluetooth address, such as "00:43:A8:23:10:F0" + * <p>Alphabetic characters must be uppercase to be valid. + * + * @param address Bluetooth address as string + * @return true if the address is valid, false otherwise + */ + public static boolean checkBluetoothAddress(String address) { + if (address == null || address.length() != ADDRESS_LENGTH) { + return false; + } + for (int i = 0; i < ADDRESS_LENGTH; i++) { + char c = address.charAt(i); + switch (i % 3) { + case 0: + case 1: + if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { + // hex character, OK + break; + } + return false; + case 2: + if (c == ':') { + break; // OK + } + return false; + } + } + return true; + } +} diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java index f3afd2a..abd7723 100644 --- a/core/java/android/bluetooth/BluetoothAudioGateway.java +++ b/core/java/android/bluetooth/BluetoothAudioGateway.java @@ -9,24 +9,22 @@ import android.util.Log; /** * Listen's for incoming RFCOMM connection for the headset / handsfree service. * - * This class is planned for deletion, in favor of a generic Rfcomm class. + * TODO: Use the new generic BluetoothSocket class instead of this legacy code * * @hide */ -public class BluetoothAudioGateway { +public final class BluetoothAudioGateway { private static final String TAG = "BT Audio Gateway"; private static final boolean DBG = false; private int mNativeData; static { classInitNative(); } - private BluetoothDevice mBluetooth; - /* in */ private int mHandsfreeAgRfcommChannel = -1; private int mHeadsetAgRfcommChannel = -1; - /* out */ + /* out - written by native code */ private String mConnectingHeadsetAddress; private int mConnectingHeadsetRfcommChannel; /* -1 when not connected */ private int mConnectingHeadsetSocketFd; @@ -35,17 +33,18 @@ public class BluetoothAudioGateway { private int mConnectingHandsfreeSocketFd; private int mTimeoutRemainingMs; /* in/out */ + private final BluetoothAdapter mAdapter; + public static final int DEFAULT_HF_AG_CHANNEL = 10; public static final int DEFAULT_HS_AG_CHANNEL = 11; - public BluetoothAudioGateway(BluetoothDevice bluetooth) { - this(bluetooth, DEFAULT_HF_AG_CHANNEL, DEFAULT_HS_AG_CHANNEL); + public BluetoothAudioGateway(BluetoothAdapter adapter) { + this(adapter, DEFAULT_HF_AG_CHANNEL, DEFAULT_HS_AG_CHANNEL); } - public BluetoothAudioGateway(BluetoothDevice bluetooth, - int handsfreeAgRfcommChannel, - int headsetAgRfcommChannel) { - mBluetooth = bluetooth; + public BluetoothAudioGateway(BluetoothAdapter adapter, int handsfreeAgRfcommChannel, + int headsetAgRfcommChannel) { + mAdapter = adapter; mHandsfreeAgRfcommChannel = handsfreeAgRfcommChannel; mHeadsetAgRfcommChannel = headsetAgRfcommChannel; initializeNativeDataNative(); @@ -58,18 +57,17 @@ public class BluetoothAudioGateway { private Handler mCallback; public class IncomingConnectionInfo { - IncomingConnectionInfo(BluetoothDevice bluetooth, String address, int socketFd, - int rfcommChan) { - mBluetooth = bluetooth; - mAddress = address; + public BluetoothAdapter mAdapter; + public BluetoothDevice mRemoteDevice; + public int mSocketFd; + public int mRfcommChan; + IncomingConnectionInfo(BluetoothAdapter adapter, BluetoothDevice remoteDevice, + int socketFd, int rfcommChan) { + mAdapter = adapter; + mRemoteDevice = remoteDevice; mSocketFd = socketFd; mRfcommChan = rfcommChan; } - - public BluetoothDevice mBluetooth; - public String mAddress; - public int mSocketFd; - public int mRfcommChan; } public static final int MSG_INCOMING_HEADSET_CONNECTION = 100; @@ -111,12 +109,11 @@ public class BluetoothAudioGateway { mConnectingHeadsetRfcommChannel); Message msg = Message.obtain(mCallback); msg.what = MSG_INCOMING_HEADSET_CONNECTION; - msg.obj = - new IncomingConnectionInfo( - mBluetooth, - mConnectingHeadsetAddress, - mConnectingHeadsetSocketFd, - mConnectingHeadsetRfcommChannel); + msg.obj = new IncomingConnectionInfo( + mAdapter, + mAdapter.getRemoteDevice(mConnectingHeadsetAddress), + mConnectingHeadsetSocketFd, + mConnectingHeadsetRfcommChannel); msg.sendToTarget(); } if (mConnectingHandsfreeRfcommChannel >= 0) { @@ -126,12 +123,11 @@ public class BluetoothAudioGateway { Message msg = Message.obtain(); msg.setTarget(mCallback); msg.what = MSG_INCOMING_HANDSFREE_CONNECTION; - msg.obj = - new IncomingConnectionInfo( - mBluetooth, - mConnectingHandsfreeAddress, - mConnectingHandsfreeSocketFd, - mConnectingHandsfreeRfcommChannel); + msg.obj = new IncomingConnectionInfo( + mAdapter, + mAdapter.getRemoteDevice(mConnectingHandsfreeAddress), + mConnectingHandsfreeSocketFd, + mConnectingHandsfreeRfcommChannel); msg.sendToTarget(); } } diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java index 88ce18b..bc06713 100644 --- a/core/java/android/bluetooth/BluetoothClass.java +++ b/core/java/android/bluetooth/BluetoothClass.java @@ -16,39 +16,98 @@ package android.bluetooth; +import android.os.Parcel; +import android.os.Parcelable; + /** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * - * Static helper methods and constants to decode the device class bit vector - * returned by the Bluetooth API. + * Represents a Bluetooth class, which describes general characteristics + * and capabilities of a device. For example, a Bluetooth class will + * specify the general device type such as a phone, a computer, or + * headset, and whether it's capable of services such as audio or telephony. * - * The Android Bluetooth API returns a 32-bit integer to represent the class. - * The format of these bits is defined at - * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm - * (login required). This class provides static helper methods and constants to - * determine what Service Class(es) and Device Class are encoded in the 32-bit - * class. + * <p>The Bluetooth class is useful as a hint to roughly describe a device (for example to + * show an icon in the UI), but does not reliably describe which Bluetooth + * profiles or services are actually supported by a device. * - * Devices typically have zero or more service classes, and exactly one device - * class. The device class is encoded as a major and minor device class, the - * minor being a subset of the major. + * <p>Every Bluetooth class is composed of zero or more service classes, and + * exactly one device class. The device class is further broken down into major + * and minor device class components. * - * Class is useful to describe a device (for example to show an icon), - * but does not reliably describe what profiles a device supports. To determine - * profile support you usually need to perform SDP queries. + * <p>{@link BluetoothClass} is useful as a hint to roughly describe a device + * (for example to show an icon in the UI), but does not reliably describe which + * Bluetooth profiles or services are actually supported by a device. Accurate + * service discovery is done through SDP requests, which are automatically + * performed when creating an RFCOMM socket with {@link + * BluetoothDevice#createRfcommSocketToServiceRecord(UUID)} and {@link + * BluetoothAdapter#listenUsingRfcommWithServiceRecord(String,UUID)}</p> * - * Each of these helper methods takes the 32-bit integer class as an argument. + * <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for + * a remote device. * - * @hide + * <!-- + * The Bluetooth class is a 32 bit field. The format of these bits is defined at + * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm + * (login required). This class contains that 32 bit field, and provides + * constants and methods to determine which Service Class(es) and Device Class + * are encoded in that field. + * --> */ -public class BluetoothClass { - /** Indicates the Bluetooth API could not retrieve the class */ +public final class BluetoothClass implements Parcelable { + /** + * Legacy error value. Applications should use null instead. + * @hide + */ public static final int ERROR = 0xFF000000; - /** Every Bluetooth device has zero or more service classes */ - public static class Service { - public static final int BITMASK = 0xFFE000; + private final int mClass; + + /** @hide */ + public BluetoothClass(int classInt) { + mClass = classInt; + } + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothClass) { + return mClass == ((BluetoothClass)o).mClass; + } + return false; + } + + @Override + public int hashCode() { + return mClass; + } + + @Override + public String toString() { + return Integer.toHexString(mClass); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<BluetoothClass> CREATOR = + new Parcelable.Creator<BluetoothClass>() { + public BluetoothClass createFromParcel(Parcel in) { + return new BluetoothClass(in.readInt()); + } + public BluetoothClass[] newArray(int size) { + return new BluetoothClass[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mClass); + } + + /** + * Defines all service class constants. + * <p>Each {@link BluetoothClass} encodes zero or more service classes. + */ + public static final class Service { + private static final int BITMASK = 0xFFE000; public static final int LIMITED_DISCOVERABILITY = 0x002000; public static final int POSITIONING = 0x010000; @@ -59,32 +118,41 @@ public class BluetoothClass { public static final int AUDIO = 0x200000; public static final int TELEPHONY = 0x400000; public static final int INFORMATION = 0x800000; + } - /** Returns true if the given class supports the given Service Class. - * A bluetooth device can claim to support zero or more service classes. - * @param btClass The bluetooth class. - * @param serviceClass The service class constant to test for. For - * example, Service.AUDIO. Must be one of the - * Service.FOO constants. - * @return True if the service class is supported. - */ - public static boolean hasService(int btClass, int serviceClass) { - if (btClass == ERROR) { - return false; - } - return ((btClass & Service.BITMASK & serviceClass) != 0); - } + /** + * Return true if the specified service class is supported by this + * {@link BluetoothClass}. + * <p>Valid service classes are the public constants in + * {@link BluetoothClass.Service}. For example, {@link + * BluetoothClass.Service#AUDIO}. + * + * @param service valid service class + * @return true if the service class is supported + */ + public boolean hasService(int service) { + return ((mClass & Service.BITMASK & service) != 0); } - /** Every Bluetooth device has exactly one device class, comprimised of - * major and minor components. We have not included the minor classes for - * major classes: NETWORKING, PERIPHERAL and IMAGING yet because they work - * a little differently. */ + /** + * Defines all device class constants. + * <p>Each {@link BluetoothClass} encodes exactly one device class, with + * major and minor components. + * <p>The constants in {@link + * BluetoothClass.Device} represent a combination of major and minor + * device components (the complete device class). The constants in {@link + * BluetoothClass.Device.Major} represent only major device classes. + * <p>See {@link BluetoothClass.Service} for service class constants. + */ public static class Device { - public static final int BITMASK = 0x1FFC; + private static final int BITMASK = 0x1FFC; + /** + * Defines all major device class constants. + * <p>See {@link BluetoothClass.Device} for minor classes. + */ public static class Major { - public static final int BITMASK = 0x1F00; + private static final int BITMASK = 0x1F00; public static final int MISC = 0x0000; public static final int COMPUTER = 0x0100; @@ -97,18 +165,6 @@ public class BluetoothClass { public static final int TOY = 0x0800; public static final int HEALTH = 0x0900; public static final int UNCATEGORIZED = 0x1F00; - - /** Returns the Major Device Class component of a bluetooth class. - * Values returned from this function can be compared with the constants - * Device.Major.FOO. A bluetooth device can only be associated - * with one major class. - */ - public static int getDeviceMajor(int btClass) { - if (btClass == ERROR) { - return ERROR; - } - return (btClass & Device.Major.BITMASK); - } } // Devices in the COMPUTER major class @@ -174,18 +230,106 @@ public class BluetoothClass { public static final int HEALTH_PULSE_OXIMETER = 0x0914; public static final int HEALTH_PULSE_RATE = 0x0918; public static final int HEALTH_DATA_DISPLAY = 0x091C; + } - /** Returns the Device Class component of a bluetooth class. This includes - * both the major and minor device components. Values returned from this - * function can be compared with the constants Device.FOO. A bluetooth - * device can only be associated with one device class. - */ - public static int getDevice(int btClass) { - if (btClass == ERROR) { - return ERROR; + /** + * Return the major device class component of this {@link BluetoothClass}. + * <p>Values returned from this function can be compared with the + * public constants in {@link BluetoothClass.Device.Major} to determine + * which major class is encoded in this Bluetooth class. + * + * @return major device class component + */ + public int getMajorDeviceClass() { + return (mClass & Device.Major.BITMASK); + } + + /** + * Return the (major and minor) device class component of this + * {@link BluetoothClass}. + * <p>Values returned from this function can be compared with the + * public constants in {@link BluetoothClass.Device} to determine which + * device class is encoded in this Bluetooth class. + * + * @return device class component + */ + public int getDeviceClass() { + return (mClass & Device.BITMASK); + } + + /** @hide */ + public static final int PROFILE_HEADSET = 0; + /** @hide */ + public static final int PROFILE_A2DP = 1; + /** @hide */ + public static final int PROFILE_OPP = 2; + + /** + * Check class bits for possible bluetooth profile support. + * This is a simple heuristic that tries to guess if a device with the + * given class bits might support specified profile. It is not accurate for all + * devices. It tries to err on the side of false positives. + * @param profile The profile to be checked + * @return True if this device might support specified profile. + * @hide + */ + public boolean doesClassMatch(int profile) { + if (profile == PROFILE_A2DP) { + if (hasService(Service.RENDER)) { + return true; + } + // By the A2DP spec, sinks must indicate the RENDER service. + // However we found some that do not (Chordette). So lets also + // match on some other class bits. + switch (getDeviceClass()) { + case Device.AUDIO_VIDEO_HIFI_AUDIO: + case Device.AUDIO_VIDEO_HEADPHONES: + case Device.AUDIO_VIDEO_LOUDSPEAKER: + case Device.AUDIO_VIDEO_CAR_AUDIO: + return true; + default: + return false; + } + } else if (profile == PROFILE_HEADSET) { + // The render service class is required by the spec for HFP, so is a + // pretty good signal + if (hasService(Service.RENDER)) { + return true; } - return (btClass & Device.BITMASK); + // Just in case they forgot the render service class + switch (getDeviceClass()) { + case Device.AUDIO_VIDEO_HANDSFREE: + case Device.AUDIO_VIDEO_WEARABLE_HEADSET: + case Device.AUDIO_VIDEO_CAR_AUDIO: + return true; + default: + return false; + } + } else if (profile == PROFILE_OPP) { + if (hasService(Service.OBJECT_TRANSFER)) { + return true; + } + + switch (getDeviceClass()) { + case Device.COMPUTER_UNCATEGORIZED: + case Device.COMPUTER_DESKTOP: + case Device.COMPUTER_SERVER: + case Device.COMPUTER_LAPTOP: + case Device.COMPUTER_HANDHELD_PC_PDA: + case Device.COMPUTER_PALM_SIZE_PC_PDA: + case Device.COMPUTER_WEARABLE: + case Device.PHONE_UNCATEGORIZED: + case Device.PHONE_CELLULAR: + case Device.PHONE_CORDLESS: + case Device.PHONE_SMART: + case Device.PHONE_MODEM_OR_GATEWAY: + case Device.PHONE_ISDN: + return true; + default: + return false; + } + } else { + return false; } } } - diff --git a/core/java/android/bluetooth/BluetoothDevice.aidl b/core/java/android/bluetooth/BluetoothDevice.aidl new file mode 100644 index 0000000..daae74d --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDevice.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +parcelable BluetoothDevice; diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 951b4b0..6cb9770 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2009 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. @@ -16,515 +16,689 @@ package android.bluetooth; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelUuid; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.Log; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.util.UUID; /** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. + * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you + * create a connection with the repective device or query information about + * it, such as the name, address, class, and bonding state. * - * Manages the local Bluetooth device. Scan for devices, create bondings, - * power up and down the adapter. + * <p>This class is really just a thin wrapper for a Bluetooth hardware + * address. Objects of this class are immutable. Operations on this class + * are performed on the remote Bluetooth hardware address, using the + * {@link BluetoothAdapter} that was used to create this {@link + * BluetoothDevice}. * - * @hide + * <p>To get a {@link BluetoothDevice}, use + * {@link BluetoothAdapter#getRemoteDevice(String) + * BluetoothAdapter.getRemoteDevice(String)} to create one representing a device + * of a known MAC address (which you can get through device discovery with + * {@link BluetoothAdapter}) or get one from the set of bonded devices + * returned by {@link BluetoothAdapter#getBondedDevices() + * BluetoothAdapter.getBondedDevices()}. You can then open a + * {@link BluetoothSocket} for communciation with the remote device, using + * {@link #createRfcommSocketToServiceRecord(UUID)}. + * + * <p class="note"><strong>Note:</strong> + * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. + * + * {@see BluetoothAdapter} + * {@see BluetoothSocket} */ -public class BluetoothDevice { - - public static final int BLUETOOTH_STATE_OFF = 0; - public static final int BLUETOOTH_STATE_TURNING_ON = 1; - public static final int BLUETOOTH_STATE_ON = 2; - public static final int BLUETOOTH_STATE_TURNING_OFF = 3; - - /** Inquiry scan and page scan are both off. - * Device is neither discoverable nor connectable */ - public static final int SCAN_MODE_NONE = 0; - /** Page scan is on, inquiry scan is off. - * Device is connectable, but not discoverable */ - public static final int SCAN_MODE_CONNECTABLE = 1; - /** Page scan and inquiry scan are on. - * Device is connectable and discoverable */ - public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 3; - - public static final int RESULT_FAILURE = -1; - public static final int RESULT_SUCCESS = 0; - - /** We do not have a link key for the remote device, and are therefore not - * bonded */ - public static final int BOND_NOT_BONDED = 0; - /** We have a link key for the remote device, and are probably bonded. */ - public static final int BOND_BONDED = 1; - /** We are currently attempting bonding */ - public static final int BOND_BONDING = 2; - - //TODO: Unify these result codes in BluetoothResult or BluetoothError - /** A bond attempt failed because pins did not match, or remote device did - * not respond to pin request in time */ - public static final int UNBOND_REASON_AUTH_FAILED = 1; - /** A bond attempt failed because the other side explicilty rejected - * bonding */ - public static final int UNBOND_REASON_AUTH_REJECTED = 2; - /** A bond attempt failed because we canceled the bonding process */ - public static final int UNBOND_REASON_AUTH_CANCELED = 3; - /** A bond attempt failed because we could not contact the remote device */ - public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; - /** A bond attempt failed because a discovery is in progress */ - public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; - /** An existing bond was explicitly revoked */ - public static final int UNBOND_REASON_REMOVED = 6; - +public final class BluetoothDevice implements Parcelable { private static final String TAG = "BluetoothDevice"; - - private final IBluetoothDevice mService; + /** - * @hide - hide this because it takes a parameter of type - * IBluetoothDevice, which is a System private class. - * Also note that Context.getSystemService is a factory that - * returns a BlueToothDevice. That is the right way to get - * a BluetoothDevice. + * Sentinel error value for this class. Guaranteed to not equal any other + * integer constant in this class. Provided as a convenience for functions + * that require a sentinel error value, for example: + * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + * BluetoothDevice.ERROR)</code> */ - public BluetoothDevice(IBluetoothDevice service) { - mService = service; - } + public static final int ERROR = Integer.MIN_VALUE; /** - * Is Bluetooth currently turned on. - * - * @return true if Bluetooth enabled, false otherwise. + * Broadcast Action: Remote device discovered. + * <p>Sent when a remote device is found during discovery. + * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link + * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or + * {@link #EXTRA_RSSI} if they are available. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ - public boolean isEnabled() { - try { - return mService.isEnabled(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } + // TODO: Change API to not broadcast RSSI if not available (incoming connection) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_FOUND = + "android.bluetooth.device.action.FOUND"; /** - * Get the current state of Bluetooth. - * - * @return One of BLUETOOTH_STATE_ or BluetoothError.ERROR. + * Broadcast Action: Remote device disappeared. + * <p>Sent when a remote device that was found in the last discovery is not + * found in the current discovery. + * <p>Always contains the extra field {@link #EXTRA_DEVICE}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide */ - public int getBluetoothState() { - try { - return mService.getBluetoothState(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return BluetoothError.ERROR; - } + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DISAPPEARED = + "android.bluetooth.device.action.DISAPPEARED"; /** - * Enable the Bluetooth device. - * Turn on the underlying hardware. - * This is an asynchronous call, - * BluetoothIntent.BLUETOOTH_STATE_CHANGED_ACTION can be used to check if - * and when the device is sucessfully enabled. - * @return false if we cannot enable the Bluetooth device. True does not - * imply the device was enabled, it only implies that so far there were no - * problems. + * Broadcast Action: Bluetooth class of a remote device has changed. + * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link + * #EXTRA_CLASS}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @see {@link BluetoothClass} */ - public boolean enable() { - try { - return mService.enable(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CLASS_CHANGED = + "android.bluetooth.device.action.CLASS_CHANGED"; /** - * Disable the Bluetooth device. - * This turns off the underlying hardware. - * - * @return true if successful, false otherwise. + * Broadcast Action: Indicates a low level (ACL) connection has been + * established with a remote device. + * <p>Always contains the extra field {@link #EXTRA_DEVICE}. + * <p>ACL connections are managed automatically by the Android Bluetooth + * stack. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ - public boolean disable() { - try { - return mService.disable(true); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ACL_CONNECTED = + "android.bluetooth.device.action.ACL_CONNECTED"; - public String getAddress() { - try { - return mService.getAddress(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } + /** + * Broadcast Action: Indicates that a low level (ACL) disconnection has + * been requested for a remote device, and it will soon be disconnected. + * <p>This is useful for graceful disconnection. Applications should use + * this intent as a hint to immediately terminate higher level connections + * (RFCOMM, L2CAP, or profile connections) to the remote device. + * <p>Always contains the extra field {@link #EXTRA_DEVICE}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ACL_DISCONNECT_REQUESTED = + "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED"; /** - * Get the friendly Bluetooth name of this device. - * - * This name is visible to remote Bluetooth devices. Currently it is only - * possible to retrieve the Bluetooth name when Bluetooth is enabled. - * - * @return the Bluetooth name, or null if there was a problem. + * Broadcast Action: Indicates a low level (ACL) disconnection from a + * remote device. + * <p>Always contains the extra field {@link #EXTRA_DEVICE}. + * <p>ACL connections are managed automatically by the Android Bluetooth + * stack. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ - public String getName() { - try { - return mService.getName(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_ACL_DISCONNECTED = + "android.bluetooth.device.action.ACL_DISCONNECTED"; /** - * Set the friendly Bluetooth name of this device. - * - * This name is visible to remote Bluetooth devices. The Bluetooth Service - * is responsible for persisting this name. - * - * @param name the name to set - * @return true, if the name was successfully set. False otherwise. + * Broadcast Action: Indicates the friendly name of a remote device has + * been retrieved for the first time, or changed since the last retrieval. + * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link + * #EXTRA_NAME}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. */ - public boolean setName(String name) { - try { - return mService.setName(name); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NAME_CHANGED = + "android.bluetooth.device.action.NAME_CHANGED"; - public String getVersion() { - try { - return mService.getVersion(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } - public String getRevision() { - try { - return mService.getRevision(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } - public String getManufacturer() { - try { - return mService.getManufacturer(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } - public String getCompany() { - try { - return mService.getCompany(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } + /** + * Broadcast Action: Indicates a change in the bond state of a remote + * device. For example, if a device is bonded (paired). + * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link + * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + */ + // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also + // contain a hidden extra field EXTRA_REASON with the result code. + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_BOND_STATE_CHANGED = + "android.bluetooth.device.action.BOND_STATE_CHANGED"; /** - * Get the current scan mode. - * Used to determine if the local device is connectable and/or discoverable - * @return Scan mode, one of SCAN_MODE_* or an error code + * Used as a Parcelable {@link BluetoothDevice} extra field in every intent + * broadcast by this class. It contains the {@link BluetoothDevice} that + * the intent applies to. */ - public int getScanMode() { - try { - return mService.getScanMode(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return BluetoothError.ERROR_IPC; - } + public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE"; /** - * Set the current scan mode. - * Used to make the local device connectable and/or discoverable - * @param scanMode One of SCAN_MODE_* + * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link + * #ACTION_FOUND} intents. It contains the friendly Bluetooth name. */ - public void setScanMode(int scanMode) { - try { - mService.setScanMode(scanMode); - } catch (RemoteException e) {Log.e(TAG, "", e);} - } + public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME"; - public int getDiscoverableTimeout() { - try { - return mService.getDiscoverableTimeout(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return -1; - } - public void setDiscoverableTimeout(int timeout) { - try { - mService.setDiscoverableTimeout(timeout); - } catch (RemoteException e) {Log.e(TAG, "", e);} + /** + * Used as an optional short extra field in {@link #ACTION_FOUND} intents. + * Contains the RSSI value of the remote device as reported by the + * Bluetooth hardware. + */ + public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI"; + + /** + * Used as an Parcelable {@link BluetoothClass} extra field in {@link + * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents. + */ + public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS"; + + /** + * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. + * Contains the bond state of the remote device. + * <p>Possible values are: + * {@link #BOND_NONE}, + * {@link #BOND_BONDING}, + * {@link #BOND_BONDED}. + */ + public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE"; + /** + * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents. + * Contains the previous bond state of the remote device. + * <p>Possible values are: + * {@link #BOND_NONE}, + * {@link #BOND_BONDING}, + * {@link #BOND_BONDED}. + */ + public static final String EXTRA_PREVIOUS_BOND_STATE = + "android.bluetooth.device.extra.PREVIOUS_BOND_STATE"; + /** + * Indicates the remote device is not bonded (paired). + * <p>There is no shared link key with the remote device, so communication + * (if it is allowed at all) will be unauthenticated and unencrypted. + */ + public static final int BOND_NONE = 10; + /** + * Indicates bonding (pairing) is in progress with the remote device. + */ + public static final int BOND_BONDING = 11; + /** + * Indicates the remote device is bonded (paired). + * <p>A shared link keys exists locally for the remote device, so + * communication can be authenticated and encrypted. + * <p><i>Being bonded (paired) with a remote device does not necessarily + * mean the device is currently connected. It just means that the ponding + * procedure was compeleted at some earlier time, and the link key is still + * stored locally, ready to use on the next connection. + * </i> + */ + public static final int BOND_BONDED = 12; + + /** @hide */ + public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON"; + /** @hide */ + public static final String EXTRA_PAIRING_VARIANT = + "android.bluetooth.device.extra.PAIRING_VARIANT"; + /** @hide */ + public static final String EXTRA_PASSKEY = "android.bluetooth.device.extra.PASSKEY"; + + /** + * Broadcast Action: This intent is used to broadcast the {@link UUID} + * wrapped as a {@link android.os.ParcelUuid} of the remote device after it + * has been fetched. This intent is sent only when the UUIDs of the remote + * device are requested to be fetched using Service Discovery Protocol + * <p> Always contains the extra field {@link #EXTRA_DEVICE} + * <p> Always contains the extra filed {@link #EXTRA_UUID} + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_UUID = + "android.bleutooth.device.action.UUID"; + + /** + * Broadcast Action: Indicates a failure to retrieve the name of a remote + * device. + * <p>Always contains the extra field {@link #EXTRA_DEVICE}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. + * @hide + */ + //TODO: is this actually useful? + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_NAME_FAILED = + "android.bluetooth.device.action.NAME_FAILED"; + + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PAIRING_REQUEST = + "android.bluetooth.device.action.PAIRING_REQUEST"; + /** @hide */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PAIRING_CANCEL = + "android.bluetooth.device.action.PAIRING_CANCEL"; + + /** A bond attempt succeeded + * @hide */ + public static final int BOND_SUCCESS = 0; + /** A bond attempt failed because pins did not match, or remote device did + * not respond to pin request in time + * @hide */ + public static final int UNBOND_REASON_AUTH_FAILED = 1; + /** A bond attempt failed because the other side explicilty rejected + * bonding + * @hide */ + public static final int UNBOND_REASON_AUTH_REJECTED = 2; + /** A bond attempt failed because we canceled the bonding process + * @hide */ + public static final int UNBOND_REASON_AUTH_CANCELED = 3; + /** A bond attempt failed because we could not contact the remote device + * @hide */ + public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4; + /** A bond attempt failed because a discovery is in progress + * @hide */ + public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5; + /** A bond attempt failed because of authentication timeout + * @hide */ + public static final int UNBOND_REASON_AUTH_TIMEOUT = 6; + /** A bond attempt failed because of repeated attempts + * @hide */ + public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7; + /** A bond attempt failed because we received an Authentication Cancel + * by remote end + * @hide */ + public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8; + /** An existing bond was explicitly revoked + * @hide */ + public static final int UNBOND_REASON_REMOVED = 9; + + /** The user will be prompted to enter a pin + * @hide */ + public static final int PAIRING_VARIANT_PIN = 0; + /** The user will be prompted to enter a passkey + * @hide */ + public static final int PAIRING_VARIANT_PASSKEY = 1; + /** The user will be prompted to confirm the passkey displayed on the screen + * @hide */ + public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2; + /** The user will be prompted to accept or deny the incoming pairing request + * @hide */ + public static final int PAIRING_VARIANT_CONSENT = 3; + /** The user will be prompted to enter the passkey displayed on remote device + * @hide */ + public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; + + /** + * Used as an extra field in {@link #ACTION_UUID} intents, + * Contains the {@link android.os.ParcelUuid}s of the remote device which + * is a parcelable version of {@link UUID}. + * @hide + */ + public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; + + /** + * Lazy initialization. Guaranteed final after first object constructed, or + * getService() called. + * TODO: Unify implementation of sService amongst BluetoothFoo API's + */ + private static IBluetooth sService; + + private final String mAddress; + + /*package*/ static IBluetooth getService() { + synchronized (BluetoothDevice.class) { + if (sService == null) { + IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_SERVICE); + if (b == null) { + throw new RuntimeException("Bluetooth service not available"); + } + sService = IBluetooth.Stub.asInterface(b); + } + } + return sService; } - public boolean startDiscovery() { - return startDiscovery(true); + /** + * Create a new BluetoothDevice + * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB", + * and is validated in this constructor. + * @param address valid Bluetooth MAC address + * @throws RuntimeException Bluetooth is not available on this platform + * @throws IllegalArgumentException address is invalid + * @hide + */ + /*package*/ BluetoothDevice(String address) { + getService(); // ensures sService is initialized + if (!BluetoothAdapter.checkBluetoothAddress(address)) { + throw new IllegalArgumentException(address + " is not a valid Bluetooth address"); + } + + mAddress = address; } - public boolean startDiscovery(boolean resolveNames) { - try { - return mService.startDiscovery(resolveNames); - } catch (RemoteException e) {Log.e(TAG, "", e);} + + @Override + public boolean equals(Object o) { + if (o instanceof BluetoothDevice) { + return mAddress.equals(((BluetoothDevice)o).getAddress()); + } return false; } - public void cancelDiscovery() { - try { - mService.cancelDiscovery(); - } catch (RemoteException e) {Log.e(TAG, "", e);} + @Override + public int hashCode() { + return mAddress.hashCode(); } - public boolean isDiscovering() { - try { - return mService.isDiscovering(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + /** + * Returns a string representation of this BluetoothDevice. + * <p>Currently this is the Bluetooth hardware address, for example + * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress} + * if you explicitly require the Bluetooth hardware address in case the + * {@link #toString} representation changes in the future. + * @return string representation of this BluetoothDevice + */ + @Override + public String toString() { + return mAddress; } - public boolean startPeriodicDiscovery() { - try { - return mService.startPeriodicDiscovery(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + public int describeContents() { + return 0; } - public boolean stopPeriodicDiscovery() { - try { - return mService.stopPeriodicDiscovery(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; - } - public boolean isPeriodicDiscovery() { - try { - return mService.isPeriodicDiscovery(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + + public static final Parcelable.Creator<BluetoothDevice> CREATOR = + new Parcelable.Creator<BluetoothDevice>() { + public BluetoothDevice createFromParcel(Parcel in) { + return new BluetoothDevice(in.readString()); + } + public BluetoothDevice[] newArray(int size) { + return new BluetoothDevice[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeString(mAddress); } - public String[] listRemoteDevices() { - try { - return mService.listRemoteDevices(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + /** + * Returns the hardware address of this BluetoothDevice. + * <p> For example, "00:11:22:AA:BB:CC". + * @return Bluetooth hardware address as string + */ + public String getAddress() { + return mAddress; } /** - * List remote devices that have a low level (ACL) connection. - * - * RFCOMM, SDP and L2CAP are all built on ACL connections. Devices can have - * an ACL connection even when not paired - this is common for SDP queries - * or for in-progress pairing requests. + * Get the friendly Bluetooth name of the remote device. * - * In most cases you probably want to test if a higher level protocol is - * connected, rather than testing ACL connections. + * <p>The local adapter will automatically retrieve remote names when + * performing a device scan, and will cache them. This method just returns + * the name for this device from the cache. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * - * @return bluetooth hardware addresses of remote devices with a current - * ACL connection. Array size is 0 if no devices have a - * connection. Null on error. + * @return the Bluetooth name, or null if there was a problem. */ - public String[] listAclConnections() { + public String getName() { try { - return mService.listAclConnections(); + return sService.getRemoteName(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } /** - * Check if a specified remote device has a low level (ACL) connection. - * - * RFCOMM, SDP and L2CAP are all built on ACL connections. Devices can have - * an ACL connection even when not paired - this is common for SDP queries - * or for in-progress pairing requests. - * - * In most cases you probably want to test if a higher level protocol is - * connected, rather than testing ACL connections. + * Start the bonding (pairing) process with the remote device. + * <p>This is an asynchronous call, it will return immediately. Register + * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when + * the bonding process completes, and its result. + * <p>Android system services will handle the necessary user interactions + * to confirm and complete the bonding process. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * - * @param address the Bluetooth hardware address you want to check. - * @return true if there is an ACL connection, false otherwise and on - * error. + * @return false on immediate error, true if bonding will begin + * @hide */ - public boolean isAclConnected(String address) { + public boolean createBond() { try { - return mService.isAclConnected(address); + return sService.createBond(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** - * Perform a low level (ACL) disconnection of a remote device. + * Cancel an in-progress bonding request started with {@link #createBond}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * - * This forcably disconnects the ACL layer connection to a remote device, - * which will cause all RFCOMM, SDP and L2CAP connections to this remote - * device to close. - * - * @param address the Bluetooth hardware address you want to disconnect. - * @return true if the device was disconnected, false otherwise and on - * error. + * @return true on sucess, false on error + * @hide */ - public boolean disconnectRemoteDeviceAcl(String address) { + public boolean cancelBondProcess() { try { - return mService.disconnectRemoteDeviceAcl(address); + return sService.cancelBondProcess(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** - * Create a bonding with a remote bluetooth device. - * - * This is an asynchronous call. The result of this bonding attempt can be - * observed through BluetoothIntent.BOND_STATE_CHANGED_ACTION intents. + * Remove bond (pairing) with the remote device. + * <p>Delete the link key associated with the remote device, and + * immediately terminate connections to that device that require + * authentication and encryption. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * - * @param address the remote device Bluetooth address. - * @return false If there was an immediate problem creating the bonding, - * true otherwise. + * @return true on sucess, false on error + * @hide */ - public boolean createBond(String address) { + public boolean removeBond() { try { - return mService.createBond(address); + return sService.removeBond(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** - * Cancel an in-progress bonding request started with createBond. + * Get the bond state of the remote device. + * <p>Possible values for the bond state are: + * {@link #BOND_NONE}, + * {@link #BOND_BONDING}, + * {@link #BOND_BONDED}. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return the bond state */ - public boolean cancelBondProcess(String address) { + public int getBondState() { try { - return mService.cancelBondProcess(address); + return sService.getBondState(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + return BOND_NONE; } /** - * Remove an already exisiting bonding (delete the link key). + * Get the Bluetooth class of the remote device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return Bluetooth class object, or null on error */ - public boolean removeBond(String address) { + public BluetoothClass getBluetoothClass() { try { - return mService.removeBond(address); + int classInt = sService.getRemoteClass(mAddress); + if (classInt == BluetoothClass.ERROR) return null; + return new BluetoothClass(classInt); } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + return null; } /** - * List remote devices that are bonded (paired) to the local device. - * - * Bonding (pairing) is the process by which the user enters a pin code for - * the device, which generates a shared link key, allowing for - * authentication and encryption of future connections. In Android we - * require bonding before RFCOMM or SCO connections can be made to a remote - * device. - * - * This function lists which remote devices we have a link key for. It does - * not cause any RF transmission, and does not check if the remote device - * still has it's link key with us. If the other side no longer has its - * link key then the RFCOMM or SCO connection attempt will result in an - * error. - * - * This function does not check if the remote device is in range. - * - * Remote devices that have an in-progress bonding attempt are not - * returned. - * - * @return bluetooth hardware addresses of remote devices that are - * bonded. Array size is 0 if no devices are bonded. Null on error. + * Get trust state of a remote device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * @hide */ - public String[] listBonds() { + public boolean getTrustState() { try { - return mService.listBonds(); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + return sService.getTrustState(mAddress); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; } /** - * Get the bonding state of a remote device. - * - * Result is one of: - * BluetoothError.* - * BOND_* - * - * @param address Bluetooth hardware address of the remote device to check. - * @return Result code + * Set trust state for a remote device. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * @param value the trust state value (true or false) + * @hide */ - public int getBondState(String address) { + public boolean setTrust(boolean value) { try { - return mService.getBondState(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return BluetoothError.ERROR_IPC; + return sService.setTrust(mAddress, value); + } catch (RemoteException e) { + Log.e(TAG, "", e); + } + return false; } - public String getRemoteName(String address) { + /** @hide */ + public ParcelUuid[] getUuids() { try { - return mService.getRemoteName(address); + return sService.getRemoteUuids(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return null; } - public String getRemoteVersion(String address) { - try { - return mService.getRemoteVersion(address); + /** + * Perform a SDP query on the remote device to get the UUIDs + * supported. This API is asynchronous and an Intent is sent, + * with the UUIDs supported by the remote end. If there is an error + * in getting the SDP records or if the process takes a long time, + * an Intent is sent with the UUIDs that is currently present in the + * cache. Clients should use the {@link getUuids} to get UUIDs + * is SDP is not to be performed. + * + * @return False if the sanity check fails, True if the process + * of initiating an ACL connection to the remote device + * was started. + * @hide + */ + public boolean fetchUuidsWithSdp() { + try { + return sService.fetchRemoteUuids(mAddress, null, null); } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + return false; } - public String getRemoteRevision(String address) { + + /** @hide */ + public int getServiceChannel(ParcelUuid uuid) { + try { + return sService.getRemoteServiceChannel(mAddress, uuid); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return BluetoothDevice.ERROR; + } + + /** @hide */ + public boolean setPin(byte[] pin) { try { - return mService.getRemoteRevision(address); + return sService.setPin(mAddress, pin); } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + return false; } - public String getRemoteManufacturer(String address) { + + /** @hide */ + public boolean setPasskey(int passkey) { try { - return mService.getRemoteManufacturer(address); + return sService.setPasskey(mAddress, passkey); } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + return false; } - public String getRemoteCompany(String address) { + + /** @hide */ + public boolean setPairingConfirmation(boolean confirm) { try { - return mService.getRemoteCompany(address); + return sService.setPairingConfirmation(mAddress, confirm); } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + return false; } - /** - * Returns the RFCOMM channel associated with the 16-byte UUID on - * the remote Bluetooth address. - * - * Performs a SDP ServiceSearchAttributeRequest transaction. The provided - * uuid is verified in the returned record. If there was a problem, or the - * specified uuid does not exist, -1 is returned. - */ - public boolean getRemoteServiceChannel(String address, short uuid16, - IBluetoothDeviceCallback callback) { + /** @hide */ + public boolean cancelPairingUserInput() { try { - return mService.getRemoteServiceChannel(address, uuid16, callback); + return sService.cancelPairingUserInput(mAddress); } catch (RemoteException e) {Log.e(TAG, "", e);} return false; } /** - * Get the major, minor and servics classes of a remote device. - * These classes are encoded as a 32-bit integer. See BluetoothClass. - * @param address remote device - * @return 32-bit class suitable for use with BluetoothClass, or - * BluetoothClass.ERROR on error + * Create an RFCOMM {@link BluetoothSocket} ready to start a secure + * outgoing connection to this remote device on given channel. + * <p>The remote device will be authenticated and communication on this + * socket will be encrypted. + * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing + * connection. + * <p>Valid RFCOMM channels are in range 1 to 30. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param channel RFCOMM channel to connect to + * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions + * @hide */ - public int getRemoteClass(String address) { - try { - return mService.getRemoteClass(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return BluetoothClass.ERROR; + public BluetoothSocket createRfcommSocket(int channel) throws IOException { + return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel, + null); } - public byte[] getRemoteFeatures(String address) { - try { - return mService.getRemoteFeatures(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } - public String lastSeen(String address) { - try { - return mService.lastSeen(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; - } - public String lastUsed(String address) { - try { - return mService.lastUsed(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return null; + /** + * Create an RFCOMM {@link BluetoothSocket} ready to start a secure + * outgoing connection to this remote device using SDP lookup of uuid. + * <p>This is designed to be used with {@link + * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer + * Bluetooth applications. + * <p>Use {@link BluetoothSocket#connect} to intiate the outgoing + * connection. This will also perform an SDP lookup of the given uuid to + * determine which channel to connect to. + * <p>The remote device will be authenticated and communication on this + * socket will be encrypted. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @param uuid service record uuid to lookup RFCOMM channel + * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions + */ + public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException { + return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1, + new ParcelUuid(uuid)); } - public boolean setPin(String address, byte[] pin) { - try { - return mService.setPin(address, pin); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + /** + * Construct an insecure RFCOMM socket ready to start an outgoing + * connection. + * Call #connect on the returned #BluetoothSocket to begin the connection. + * The remote device will not be authenticated and communication on this + * socket will not be encrypted. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @param port remote port + * @return An RFCOMM BluetoothSocket + * @throws IOException On error, for example Bluetooth not available, or + * insufficient permissions. + * @hide + */ + public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException { + return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port, + null); } - public boolean cancelPin(String address) { - try { - return mService.cancelPin(address); - } catch (RemoteException e) {Log.e(TAG, "", e);} - return false; + + /** + * Construct a SCO socket ready to start an outgoing connection. + * Call #connect on the returned #BluetoothSocket to begin the connection. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} + * + * @return a SCO BluetoothSocket + * @throws IOException on error, for example Bluetooth not available, or + * insufficient permissions. + * @hide + */ + public BluetoothSocket createScoSocket() throws IOException { + return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null); } /** @@ -534,6 +708,7 @@ public class BluetoothDevice { * @param pin pin as java String * @return the pin code as a UTF8 byte array, or null if it is an invalid * Bluetooth pin. + * @hide */ public static byte[] convertPinToBytes(String pin) { if (pin == null) { @@ -552,28 +727,4 @@ public class BluetoothDevice { return pinBytes; } - private static final int ADDRESS_LENGTH = 17; - /** Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */ - public static boolean checkBluetoothAddress(String address) { - if (address == null || address.length() != ADDRESS_LENGTH) { - return false; - } - for (int i = 0; i < ADDRESS_LENGTH; i++) { - char c = address.charAt(i); - switch (i % 3) { - case 0: - case 1: - if (Character.digit(c, 16) != -1) { - break; // hex character, OK - } - return false; - case 2: - if (c == ':') { - break; // OK - } - return false; - } - } - return true; - } } diff --git a/core/java/android/bluetooth/BluetoothDevicePicker.java b/core/java/android/bluetooth/BluetoothDevicePicker.java new file mode 100644 index 0000000..05eed0e --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDevicePicker.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; + +/** + * A helper to show a system "Device Picker" activity to the user. + * + * @hide + */ +public interface BluetoothDevicePicker { + public static final String EXTRA_NEED_AUTH = + "android.bluetooth.devicepicker.extra.NEED_AUTH"; + public static final String EXTRA_FILTER_TYPE = + "android.bluetooth.devicepicker.extra.FILTER_TYPE"; + public static final String EXTRA_LAUNCH_PACKAGE = + "android.bluetooth.devicepicker.extra.LAUNCH_PACKAGE"; + public static final String EXTRA_LAUNCH_CLASS = + "android.bluetooth.devicepicker.extra.DEVICE_PICKER_LAUNCH_CLASS"; + + /** + * Broadcast when one BT device is selected from BT device picker screen. + * Selected BT device address is contained in extra string {@link BluetoothIntent} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_SELECTED = + "android.bluetooth.devicepicker.action.DEVICE_SELECTED"; + + /** + * Broadcast when someone want to select one BT device from devices list. + * This intent contains below extra data: + * - {@link #EXTRA_NEED_AUTH} (boolean): if need authentication + * - {@link #EXTRA_FILTER_TYPE} (int): what kinds of device should be + * listed + * - {@link #EXTRA_LAUNCH_PACKAGE} (string): where(which package) this + * intent come from + * - {@link #EXTRA_LAUNCH_CLASS} (string): where(which class) this intent + * come from + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_LAUNCH = + "android.bluetooth.devicepicker.action.LAUNCH"; + + /** Ask device picker to show all kinds of BT devices */ + public static final int FILTER_TYPE_ALL = 0; + /** Ask device picker to show BT devices that support AUDIO profiles */ + public static final int FILTER_TYPE_AUDIO = 1; + /** Ask device picker to show BT devices that support Object Transfer */ + public static final int FILTER_TYPE_TRANSFER = 2; +} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index fe1e09a..90cff6b 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -16,6 +16,8 @@ package android.bluetooth; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -49,11 +51,32 @@ import android.util.Log; * * @hide */ -public class BluetoothHeadset { +public final class BluetoothHeadset { private static final String TAG = "BluetoothHeadset"; private static final boolean DBG = false; + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_STATE_CHANGED = + "android.bluetooth.headset.action.STATE_CHANGED"; + /** + * TODO(API release): Consider incorporating as new state in + * HEADSET_STATE_CHANGED + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_AUDIO_STATE_CHANGED = + "android.bluetooth.headset.action.AUDIO_STATE_CHANGED"; + public static final String EXTRA_STATE = + "android.bluetooth.headset.extra.STATE"; + public static final String EXTRA_PREVIOUS_STATE = + "android.bluetooth.headset.extra.PREVIOUS_STATE"; + public static final String EXTRA_AUDIO_STATE = + "android.bluetooth.headset.extra.AUDIO_STATE"; + + /** + * TODO(API release): Consider incorporating as new state in + * HEADSET_STATE_CHANGED + */ private IBluetoothHeadset mService; private final Context mContext; private final ServiceListener mServiceListener; @@ -163,16 +186,16 @@ public class BluetoothHeadset { } /** - * Get the Bluetooth address of the current headset. - * @return The Bluetooth address, or null if not in connected or connecting + * Get the BluetoothDevice for the current headset. + * @return current headset, or null if not in connected or connecting * state, or if this proxy object is not connected to the Headset * service. */ - public String getHeadsetAddress() { - if (DBG) log("getHeadsetAddress()"); + public BluetoothDevice getCurrentHeadset() { + if (DBG) log("getCurrentHeadset()"); if (mService != null) { try { - return mService.getHeadsetAddress(); + return mService.getCurrentHeadset(); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -185,19 +208,19 @@ public class BluetoothHeadset { * Request to initiate a connection to a headset. * This call does not block. Fails if a headset is already connecting * or connected. - * Initiates auto-connection if address is null. Tries to connect to all + * Initiates auto-connection if device is null. Tries to connect to all * devices with priority greater than PRIORITY_AUTO in descending order. - * @param address The Bluetooth Address to connect to, or null to - * auto-connect to the last connected headset. - * @return False if there was a problem initiating the connection - * procedure, and no further HEADSET_STATE_CHANGED intents - * will be expected. + * @param device device to connect to, or null to auto-connect last connected + * headset + * @return false if there was a problem initiating the connection + * procedure, and no further HEADSET_STATE_CHANGED intents + * will be expected. */ - public boolean connectHeadset(String address) { - if (DBG) log("connectHeadset(" + address + ")"); + public boolean connectHeadset(BluetoothDevice device) { + if (DBG) log("connectHeadset(" + device + ")"); if (mService != null) { try { - if (mService.connectHeadset(address)) { + if (mService.connectHeadset(device)) { return true; } } catch (RemoteException e) {Log.e(TAG, e.toString());} @@ -213,11 +236,11 @@ public class BluetoothHeadset { * connecting). Returns false if not connected, or if this proxy object * if not currently connected to the headset service. */ - public boolean isConnected(String address) { - if (DBG) log("isConnected(" + address + ")"); + public boolean isConnected(BluetoothDevice device) { + if (DBG) log("isConnected(" + device + ")"); if (mService != null) { try { - return mService.isConnected(address); + return mService.isConnected(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -295,16 +318,16 @@ public class BluetoothHeadset { * auto-connected. * Incoming connections are ignored regardless of priority if there is * already a headset connected. - * @param address Paired headset + * @param device paired headset * @param priority Integer priority, for example PRIORITY_AUTO or * PRIORITY_NONE - * @return True if successful, false if there was some error. + * @return true if successful, false if there was some error */ - public boolean setPriority(String address, int priority) { - if (DBG) log("setPriority(" + address + ", " + priority + ")"); + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); if (mService != null) { try { - return mService.setPriority(address, priority); + return mService.setPriority(device, priority); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -315,14 +338,14 @@ public class BluetoothHeadset { /** * Get priority of headset. - * @param address Headset - * @return non-negative priority, or negative error code on error. + * @param device headset + * @return non-negative priority, or negative error code on error */ - public int getPriority(String address) { - if (DBG) log("getPriority(" + address + ")"); + public int getPriority(BluetoothDevice device) { + if (DBG) log("getPriority(" + device + ")"); if (mService != null) { try { - return mService.getPriority(address); + return mService.getPriority(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -356,30 +379,6 @@ public class BluetoothHeadset { return -1; } - /** - * Check class bits for possible HSP or HFP support. - * This is a simple heuristic that tries to guess if a device with the - * given class bits might support HSP or HFP. It is not accurate for all - * devices. It tries to err on the side of false positives. - * @return True if this device might support HSP or HFP. - */ - public static boolean doesClassMatch(int btClass) { - // The render service class is required by the spec for HFP, so is a - // pretty good signal - if (BluetoothClass.Service.hasService(btClass, BluetoothClass.Service.RENDER)) { - return true; - } - // Just in case they forgot the render service class - switch (BluetoothClass.Device.getDevice(btClass)) { - case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: - case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: - case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: - return true; - default: - return false; - } - } - private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/bluetooth/BluetoothInputStream.java b/core/java/android/bluetooth/BluetoothInputStream.java index ceae70c..03af953 100644 --- a/core/java/android/bluetooth/BluetoothInputStream.java +++ b/core/java/android/bluetooth/BluetoothInputStream.java @@ -24,7 +24,6 @@ import java.io.InputStream; * * Used to write to a Bluetooth socket. * - * TODO: Implement bulk writes (instead of one byte at a time). * @hide */ /*package*/ final class BluetoothInputStream extends InputStream { @@ -38,7 +37,7 @@ import java.io.InputStream; * Return number of bytes available before this stream will block. */ public int available() throws IOException { - return mSocket.availableNative(); + return mSocket.available(); } public void close() throws IOException { @@ -54,9 +53,46 @@ import java.io.InputStream; * @return the byte read or -1 if the end of stream has been reached. * @throws IOException * if the stream is closed or another IOException occurs. - * @since Android 1.0 + * @since Android 1.5 */ public int read() throws IOException { - return mSocket.readNative(); + byte b[] = new byte[1]; + int ret = mSocket.read(b, 0, 1); + if (ret == 1) { + return (int)b[0] & 0xff; + } else { + return -1; + } + } + + /** + * Reads at most {@code length} bytes from this stream and stores them in + * the byte array {@code b} starting at {@code offset}. + * + * @param b + * the byte array in which to store the bytes read. + * @param offset + * the initial position in {@code buffer} to store the bytes + * read from this stream. + * @param length + * the maximum number of bytes to store in {@code b}. + * @return the number of bytes actually read or -1 if the end of the stream + * has been reached. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code length < 0}, or if + * {@code offset + length} is greater than the length of + * {@code b}. + * @throws IOException + * if the stream is closed or another IOException occurs. + * @since Android 1.5 + */ + public int read(byte[] b, int offset, int length) throws IOException { + if (b == null) { + throw new NullPointerException("byte array is null"); + } + if ((offset | length) < 0 || length > b.length - offset) { + throw new ArrayIndexOutOfBoundsException("invalid offset or length"); + } + return mSocket.read(b, offset, length); } } diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java deleted file mode 100644 index 344601b..0000000 --- a/core/java/android/bluetooth/BluetoothIntent.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.bluetooth; - -import android.annotation.SdkConstant; -import android.annotation.SdkConstant.SdkConstantType; - -/** - * The Android Bluetooth API is not finalized, and *will* change. Use at your - * own risk. - * - * Manages the local Bluetooth device. Scan for devices, create bondings, - * power up and down the adapter. - * - * @hide - */ -public interface BluetoothIntent { - public static final String SCAN_MODE = - "android.bluetooth.intent.SCAN_MODE"; - public static final String ADDRESS = - "android.bluetooth.intent.ADDRESS"; - public static final String NAME = - "android.bluetooth.intent.NAME"; - public static final String ALIAS = - "android.bluetooth.intent.ALIAS"; - public static final String RSSI = - "android.bluetooth.intent.RSSI"; - public static final String CLASS = - "android.bluetooth.intent.CLASS"; - public static final String BLUETOOTH_STATE = - "android.bluetooth.intent.BLUETOOTH_STATE"; - public static final String BLUETOOTH_PREVIOUS_STATE = - "android.bluetooth.intent.BLUETOOTH_PREVIOUS_STATE"; - public static final String HEADSET_STATE = - "android.bluetooth.intent.HEADSET_STATE"; - public static final String HEADSET_PREVIOUS_STATE = - "android.bluetooth.intent.HEADSET_PREVIOUS_STATE"; - public static final String HEADSET_AUDIO_STATE = - "android.bluetooth.intent.HEADSET_AUDIO_STATE"; - public static final String BOND_STATE = - "android.bluetooth.intent.BOND_STATE"; - public static final String BOND_PREVIOUS_STATE = - "android.bluetooth.intent.BOND_PREVIOUS_STATE"; - public static final String REASON = - "android.bluetooth.intent.REASON"; - - /** Broadcast when the local Bluetooth device state changes, for example - * when Bluetooth is enabled. Will contain int extra's BLUETOOTH_STATE and - * BLUETOOTH_PREVIOUS_STATE. */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String BLUETOOTH_STATE_CHANGED_ACTION = - "android.bluetooth.intent.action.BLUETOOTH_STATE_CHANGED"; - - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String NAME_CHANGED_ACTION = - "android.bluetooth.intent.action.NAME_CHANGED"; - - /** - * Broadcast when the scan mode changes. Always contains an int extra - * named SCAN_MODE that contains the new scan mode. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String SCAN_MODE_CHANGED_ACTION = - "android.bluetooth.intent.action.SCAN_MODE_CHANGED"; - - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String DISCOVERY_STARTED_ACTION = - "android.bluetooth.intent.action.DISCOVERY_STARTED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String DISCOVERY_COMPLETED_ACTION = - "android.bluetooth.intent.action.DISCOVERY_COMPLETED"; - - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String PAIRING_REQUEST_ACTION = - "android.bluetooth.intent.action.PAIRING_REQUEST"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String PAIRING_CANCEL_ACTION = - "android.bluetooth.intent.action.PAIRING_CANCEL"; - - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_FOUND_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_FOUND"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_DISAPPEARED_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_DISAPPEARED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_CLASS_UPDATED_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_DISAPPEARED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_CONNECTED_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_CONNECTED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_DISCONNECT_REQUESTED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_DEVICE_DISCONNECTED_ACTION = - "android.bluetooth.intent.action.REMOTE_DEVICE_DISCONNECTED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_NAME_UPDATED_ACTION = - "android.bluetooth.intent.action.REMOTE_NAME_UPDATED"; - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String REMOTE_NAME_FAILED_ACTION = - "android.bluetooth.intent.action.REMOTE_NAME_FAILED"; - - /** - * Broadcast when the bond state of a remote device changes. - * Has string extra ADDRESS and int extras BOND_STATE and - * BOND_PREVIOUS_STATE. - * If BOND_STATE is BluetoothDevice.BOND_NOT_BONDED then will - * also have an int extra REASON with a value of: - * BluetoothDevice.BOND_RESULT_* - * */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String BOND_STATE_CHANGED_ACTION = - "android.bluetooth.intent.action.BOND_STATE_CHANGED_ACTION"; - - /** - * TODO(API release): Move into BluetoothHeadset - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String HEADSET_STATE_CHANGED_ACTION = - "android.bluetooth.intent.action.HEADSET_STATE_CHANGED"; - - /** - * TODO(API release): Consider incorporating as new state in - * HEADSET_STATE_CHANGED - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String HEADSET_AUDIO_STATE_CHANGED_ACTION = - "android.bluetooth.intent.action.HEADSET_ADUIO_STATE_CHANGED"; -} diff --git a/core/java/android/bluetooth/BluetoothOutputStream.java b/core/java/android/bluetooth/BluetoothOutputStream.java index 32e6d17..62242a2 100644 --- a/core/java/android/bluetooth/BluetoothOutputStream.java +++ b/core/java/android/bluetooth/BluetoothOutputStream.java @@ -24,7 +24,6 @@ import java.io.OutputStream; * * Used to read from a Bluetooth socket. * - * TODO: Implement bulk reads (instead of one byte at a time). * @hide */ /*package*/ final class BluetoothOutputStream extends OutputStream { @@ -52,6 +51,37 @@ import java.io.OutputStream; * @since Android 1.0 */ public void write(int oneByte) throws IOException { - mSocket.writeNative(oneByte); + byte b[] = new byte[1]; + b[0] = (byte)oneByte; + mSocket.write(b, 0, 1); + } + + /** + * Writes {@code count} bytes from the byte array {@code buffer} starting + * at position {@code offset} to this stream. + * + * @param b + * the buffer to be written. + * @param offset + * the start position in {@code buffer} from where to get bytes. + * @param count + * the number of bytes from {@code buffer} to write to this + * stream. + * @throws IOException + * if an error occurs while writing to this stream. + * @throws IndexOutOfBoundsException + * if {@code offset < 0} or {@code count < 0}, or if + * {@code offset + count} is bigger than the length of + * {@code buffer}. + * @since Android 1.0 + */ + public void write(byte[] b, int offset, int count) throws IOException { + if (b == null) { + throw new NullPointerException("buffer is null"); + } + if ((offset | count) < 0 || count > b.length - offset) { + throw new IndexOutOfBoundsException("invalid offset or length"); + } + mSocket.write(b, offset, count); } } diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java new file mode 100644 index 0000000..b48f48e --- /dev/null +++ b/core/java/android/bluetooth/BluetoothPbap.java @@ -0,0 +1,257 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.ServiceManager; +import android.util.Log; + +/** + * The Android Bluetooth API is not finalized, and *will* change. Use at your + * own risk. + * + * Public API for controlling the Bluetooth Pbap Service. This includes + * Bluetooth Phone book Access profile. + * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap + * Service via IPC. + * + * Creating a BluetoothPbap object will create a binding with the + * BluetoothPbap service. Users of this object should call close() when they + * are finished with the BluetoothPbap, so that this proxy object can unbind + * from the service. + * + * This BluetoothPbap object is not immediately bound to the + * BluetoothPbap service. Use the ServiceListener interface to obtain a + * notification when it is bound, this is especially important if you wish to + * immediately call methods on BluetoothPbap after construction. + * + * Android only supports one connected Bluetooth Pce at a time. + * + * @hide + */ +public class BluetoothPbap { + + private static final String TAG = "BluetoothPbap"; + private static final boolean DBG = false; + + /** int extra for PBAP_STATE_CHANGED_ACTION */ + public static final String PBAP_STATE = + "android.bluetooth.pbap.intent.PBAP_STATE"; + /** int extra for PBAP_STATE_CHANGED_ACTION */ + public static final String PBAP_PREVIOUS_STATE = + "android.bluetooth.pbap.intent.PBAP_PREVIOUS_STATE"; + + /** Indicates the state of an pbap connection state has changed. + * This intent will always contain PBAP_STATE, PBAP_PREVIOUS_STATE and + * BluetoothIntent.ADDRESS extras. + */ + public static final String PBAP_STATE_CHANGED_ACTION = + "android.bluetooth.pbap.intent.action.PBAP_STATE_CHANGED"; + + private IBluetoothPbap mService; + private final Context mContext; + private final ServiceListener mServiceListener; + + /** There was an error trying to obtain the state */ + public static final int STATE_ERROR = -1; + /** No client currently connected */ + public static final int STATE_DISCONNECTED = 0; + /** Connection attempt in progress */ + public static final int STATE_CONNECTING = 1; + /** Client is currently connected */ + public static final int STATE_CONNECTED = 2; + + public static final int RESULT_FAILURE = 0; + public static final int RESULT_SUCCESS = 1; + /** Connection canceled before completion. */ + public static final int RESULT_CANCELED = 2; + + /** + * An interface for notifying Bluetooth PCE IPC clients when they have + * been connected to the BluetoothPbap service. + */ + public interface ServiceListener { + /** + * Called to notify the client when this proxy object has been + * connected to the BluetoothPbap service. Clients must wait for + * this callback before making IPC calls on the BluetoothPbap + * service. + */ + public void onServiceConnected(); + + /** + * Called to notify the client that this proxy object has been + * disconnected from the BluetoothPbap service. Clients must not + * make IPC calls on the BluetoothPbap service after this callback. + * This callback will currently only occur if the application hosting + * the BluetoothPbap service, but may be called more often in future. + */ + public void onServiceDisconnected(); + } + + /** + * Create a BluetoothPbap proxy object. + */ + public BluetoothPbap(Context context, ServiceListener l) { + mContext = context; + mServiceListener = l; + if (!context.bindService(new Intent(IBluetoothPbap.class.getName()), mConnection, 0)) { + Log.e(TAG, "Could not bind to Bluetooth Pbap Service"); + } + } + + protected void finalize() throws Throwable { + try { + close(); + } finally { + super.finalize(); + } + } + + /** + * Close the connection to the backing service. + * Other public functions of BluetoothPbap will return default error + * results once close() has been called. Multiple invocations of close() + * are ok. + */ + public synchronized void close() { + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + } + } + + /** + * Get the current state of the BluetoothPbap service. + * @return One of the STATE_ return codes, or STATE_ERROR if this proxy + * object is currently not connected to the Pbap service. + */ + public int getState() { + if (DBG) log("getState()"); + if (mService != null) { + try { + return mService.getState(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return BluetoothPbap.STATE_ERROR; + } + + /** + * Get the currently connected remote Bluetooth device (PCE). + * @return The remote Bluetooth device, or null if not in connected or + * connecting state, or if this proxy object is not connected to + * the Pbap service. + */ + public BluetoothDevice getClient() { + if (DBG) log("getClient()"); + if (mService != null) { + try { + return mService.getClient(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return null; + } + + /** + * Returns true if the specified Bluetooth device is connected (does not + * include connecting). Returns false if not connected, or if this proxy + * object is not currently connected to the Pbap service. + */ + public boolean isConnected(BluetoothDevice device) { + if (DBG) log("isConnected(" + device + ")"); + if (mService != null) { + try { + return mService.isConnected(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnects the current Pbap client (PCE). Currently this call blocks, + * it may soon be made asynchornous. Returns false if this proxy object is + * not currently connected to the Pbap service. + */ + public boolean disconnect() { + if (DBG) log("disconnect()"); + if (mService != null) { + try { + mService.disconnect(); + return true; + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) log(Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Check class bits for possible PBAP support. + * This is a simple heuristic that tries to guess if a device with the + * given class bits might support PBAP. It is not accurate for all + * devices. It tries to err on the side of false positives. + * @return True if this device might support PBAP. + */ + public static boolean doesClassMatchSink(BluetoothClass btClass) { + // TODO optimize the rule + switch (btClass.getDeviceClass()) { + case BluetoothClass.Device.COMPUTER_DESKTOP: + case BluetoothClass.Device.COMPUTER_LAPTOP: + case BluetoothClass.Device.COMPUTER_SERVER: + case BluetoothClass.Device.COMPUTER_UNCATEGORIZED: + return true; + default: + return false; + } + } + + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + if (DBG) log("Proxy object connected"); + mService = IBluetoothPbap.Stub.asInterface(service); + if (mServiceListener != null) { + mServiceListener.onServiceConnected(); + } + } + public void onServiceDisconnected(ComponentName className) { + if (DBG) log("Proxy object disconnected"); + mService = null; + if (mServiceListener != null) { + mServiceListener.onServiceDisconnected(); + } + } + }; + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/BluetoothServerSocket.java b/core/java/android/bluetooth/BluetoothServerSocket.java index ca46701..1b23f6c 100644 --- a/core/java/android/bluetooth/BluetoothServerSocket.java +++ b/core/java/android/bluetooth/BluetoothServerSocket.java @@ -16,85 +16,72 @@ package android.bluetooth; +import android.os.Handler; + import java.io.Closeable; import java.io.IOException; /** - * Server (listening) Bluetooth Socket. + * A listening Bluetooth socket. + * + * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: + * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server + * side, use a {@link BluetoothServerSocket} to create a listening server + * socket. When a connection is accepted by the {@link BluetoothServerSocket}, + * it will return a new {@link BluetoothSocket} to manage the connection. + * On the client side, use a single {@link BluetoothSocket} to both intiate + * an outgoing connection and to manage the connection. + * + * <p>The most common type of Bluetooth socket is RFCOMM, which is the type + * supported by the Android APIs. RFCOMM is a connection-oriented, streaming + * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). * - * Currently only supports RFCOMM sockets. + * <p>To create a listenting {@link BluetoothServerSocket} that's ready for + * incoming connections, use + * {@link BluetoothAdapter#listenUsingRfcommWithServiceRecord + * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}. Then call + * {@link #accept()} to listen for incoming connection requests. This call + * will block until a connection is established, at which point, it will return + * a {@link BluetoothSocket} to manage the connection. * - * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is - * also known as the Serial Port Profile (SPP). + * <p>{@link BluetoothServerSocket} is thread + * safe. In particular, {@link #close} will always immediately abort ongoing + * operations and close the server socket. * - * TODO: Consider implementing SCO and L2CAP sockets. - * TODO: Clean up javadoc grammer and formatting. - * TODO: Remove @hide - * @hide + * <p class="note"><strong>Note:</strong> + * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. + * + * {@see BluetoothSocket} */ public final class BluetoothServerSocket implements Closeable { - private final BluetoothSocket mSocket; - /** - * Construct a listening, secure RFCOMM server socket. - * The remote device connecting to this socket will be authenticated and - * communication on this socket will be encrypted. - * Call #accept to retrieve connections to this socket. - * @return An RFCOMM BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or - * insufficient permissions. - */ - public static BluetoothServerSocket listenUsingRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = new BluetoothServerSocket(true, true); - try { - socket.mSocket.bindListenNative(port); - } catch (IOException e) { - try { - socket.close(); - } catch (IOException e2) { } - throw e; - } - return socket; - } - - /** - * Construct an unencrypted, unauthenticated, RFCOMM server socket. - * Call #accept to retrieve connections to this socket. - * @return An RFCOMM BluetoothServerSocket - * @throws IOException On error, for example Bluetooth not available, or - * insufficient permissions. - */ - public static BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException { - BluetoothServerSocket socket = new BluetoothServerSocket(false, false); - try { - socket.mSocket.bindListenNative(port); - } catch (IOException e) { - try { - socket.close(); - } catch (IOException e2) { } - throw e; - } - return socket; - } + /*package*/ final BluetoothSocket mSocket; + private Handler mHandler; + private int mMessage; /** * Construct a socket for incoming connections. - * @param auth Require the remote device to be authenticated - * @param encrypt Require the connection to be encrypted + * @param type type of socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param port remote port * @throws IOException On error, for example Bluetooth not available, or * insufficient priveleges */ - private BluetoothServerSocket(boolean auth, boolean encrypt) throws IOException { - mSocket = new BluetoothSocket(-1, auth, encrypt, null, -1); + /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port) + throws IOException { + mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null); } /** * Block until a connection is established. - * Returns a connected #BluetoothSocket. This server socket can be reused - * for subsequent incoming connections by calling #accept repeatedly. - * #close can be used to abort this call from another thread. - * @return A connected #BluetoothSocket - * @throws IOException On error, for example this call was aborted + * <p>Returns a connected {@link BluetoothSocket} on successful connection. + * <p>Once this call returns, it can be called again to accept subsequent + * incoming connections. + * <p>{@link #close} can be used to abort this call from another thread. + * @return a connected {@link BluetoothSocket} + * @throws IOException on error, for example this call was aborted, or + * timeout */ public BluetoothSocket accept() throws IOException { return accept(-1); @@ -102,23 +89,34 @@ public final class BluetoothServerSocket implements Closeable { /** * Block until a connection is established, with timeout. - * Returns a connected #BluetoothSocket. This server socket can be reused - * for subsequent incoming connections by calling #accept repeatedly. - * #close can be used to abort this call from another thread. - * @return A connected #BluetoothSocket - * @throws IOException On error, for example this call was aborted, or + * <p>Returns a connected {@link BluetoothSocket} on successful connection. + * <p>Once this call returns, it can be called again to accept subsequent + * incoming connections. + * <p>{@link #close} can be used to abort this call from another thread. + * @return a connected {@link BluetoothSocket} + * @throws IOException on error, for example this call was aborted, or * timeout */ public BluetoothSocket accept(int timeout) throws IOException { - return mSocket.acceptNative(timeout); + return mSocket.accept(timeout); } /** - * Closes this socket. - * This will cause other blocking calls on this socket to immediately + * Immediately close this socket, and release all associated resources. + * <p>Causes blocked calls on this socket in other threads to immediately * throw an IOException. */ public void close() throws IOException { - mSocket.closeNative(); + synchronized (this) { + if (mHandler != null) { + mHandler.obtainMessage(mMessage).sendToTarget(); + } + } + mSocket.close(); + } + + /*package*/ synchronized void setCloseHandler(Handler handler, int message) { + mHandler = handler; + mMessage = message; } } diff --git a/core/java/android/bluetooth/BluetoothSocket.java b/core/java/android/bluetooth/BluetoothSocket.java index fd8885e..dbcc758 100644 --- a/core/java/android/bluetooth/BluetoothSocket.java +++ b/core/java/android/bluetooth/BluetoothSocket.java @@ -16,83 +16,128 @@ package android.bluetooth; +import android.bluetooth.IBluetoothCallback; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * Represents a connected or connecting Bluetooth Socket. + * A connected or connecting Bluetooth socket. + * + * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets: + * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server + * side, use a {@link BluetoothServerSocket} to create a listening server + * socket. When a connection is accepted by the {@link BluetoothServerSocket}, + * it will return a new {@link BluetoothSocket} to manage the connection. + * On the client side, use a single {@link BluetoothSocket} to both intiate + * an outgoing connection and to manage the connection. + * + * <p>The most common type of Bluetooth socket is RFCOMM, which is the type + * supported by the Android APIs. RFCOMM is a connection-oriented, streaming + * transport over Bluetooth. It is also known as the Serial Port Profile (SPP). + * + * <p>To create a {@link BluetoothSocket} for connecting to a known device, use + * {@link BluetoothDevice#createRfcommSocketToServiceRecord + * BluetoothDevice.createRfcommSocketToServiceRecord()}. + * Then call {@link #connect()} to attempt a connection to the remote device. + * This call will block until a connection is established or the connection + * fails. + * + * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the + * {@link BluetoothServerSocket} documentation. + * + * <p>Once the socket is connected, whether initiated as a client or accepted + * as a server, open the IO streams by calling {@link #getInputStream} and + * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream} + * and {@link java.io.OutputStream} objects, respectively, which are + * automatically connected to the socket. * - * Currently only supports RFCOMM sockets. + * <p>{@link BluetoothSocket} is thread + * safe. In particular, {@link #close} will always immediately abort ongoing + * operations and close the socket. * - * RFCOMM is a connection orientated, streaming transport over Bluetooth. It is - * also known as the Serial Port Profile (SPP). + * <p class="note"><strong>Note:</strong> + * Requires the {@link android.Manifest.permission#BLUETOOTH} permission. * - * TODO: Consider implementing SCO and L2CAP sockets. - * TODO: Clean up javadoc grammer and formatting. - * TODO: Remove @hide - * @hide + * {@see BluetoothServerSocket} + * {@see java.io.InputStream} + * {@see java.io.OutputStream} */ public final class BluetoothSocket implements Closeable { - private final int mPort; + private static final String TAG = "BluetoothSocket"; + + /** @hide */ + public static final int MAX_RFCOMM_CHANNEL = 30; + + /** Keep TYPE_ fields in sync with BluetoothSocket.cpp */ + /*package*/ static final int TYPE_RFCOMM = 1; + /*package*/ static final int TYPE_SCO = 2; + /*package*/ static final int TYPE_L2CAP = 3; + + /*package*/ static final int EBADFD = 77; + /*package*/ static final int EADDRINUSE = 98; + + private final int mType; /* one of TYPE_RFCOMM etc */ + private final BluetoothDevice mDevice; /* remote device */ private final String mAddress; /* remote address */ private final boolean mAuth; private final boolean mEncrypt; private final BluetoothInputStream mInputStream; private final BluetoothOutputStream mOutputStream; + private final SdpHelper mSdp; - private int mSocketData; /* used by native code only */ + private int mPort; /* RFCOMM channel or L2CAP psm */ - /** - * Construct a secure RFCOMM socket ready to start an outgoing connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * The remote device will be authenticated and communication on this socket - * will be encrypted. - * @param address remote Bluetooth address that this socket can connect to - * @param port remote port - * @return an RFCOMM BluetoothSocket - * @throws IOException on error, for example Bluetooth not available, or - * insufficient permissions. - */ - public static BluetoothSocket createRfcommSocket(String address, int port) - throws IOException { - return new BluetoothSocket(-1, true, true, address, port); - } + /** prevents all native calls after destroyNative() */ + private boolean mClosed; - /** - * Construct an insecure RFCOMM socket ready to start an outgoing - * connection. - * Call #connect on the returned #BluetoothSocket to begin the connection. - * The remote device will not be authenticated and communication on this - * socket will not be encrypted. - * @param address remote Bluetooth address that this socket can connect to - * @param port remote port - * @return An RFCOMM BluetoothSocket - * @throws IOException On error, for example Bluetooth not available, or - * insufficient permissions. - */ - public static BluetoothSocket createInsecureRfcommSocket(String address, int port) - throws IOException { - return new BluetoothSocket(-1, false, false, address, port); - } + /** protects mClosed */ + private final ReentrantReadWriteLock mLock; + + /** used by native code only */ + private int mSocketData; /** - * Construct a Bluetooth. + * Construct a BluetoothSocket. + * @param type type of socket * @param fd fd to use for connected socket, or -1 for a new socket * @param auth require the remote device to be authenticated * @param encrypt require the connection to be encrypted - * @param address remote Bluetooth address that this socket can connect to + * @param device remote device that this socket can connect to * @param port remote port + * @param uuid SDP uuid * @throws IOException On error, for example Bluetooth not available, or * insufficient priveleges */ - /*package*/ BluetoothSocket(int fd, boolean auth, boolean encrypt, String address, int port) - throws IOException { + /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, + BluetoothDevice device, int port, ParcelUuid uuid) throws IOException { + if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1) { + if (port < 1 || port > MAX_RFCOMM_CHANNEL) { + throw new IOException("Invalid RFCOMM channel: " + port); + } + } + if (uuid == null) { + mPort = port; + mSdp = null; + } else { + mSdp = new SdpHelper(device, uuid); + mPort = -1; + } + mType = type; mAuth = auth; mEncrypt = encrypt; - mAddress = address; - mPort = port; + mDevice = device; + if (device == null) { + mAddress = null; + } else { + mAddress = device.getAddress(); + } if (fd == -1) { initSocketNative(); } else { @@ -100,8 +145,27 @@ public final class BluetoothSocket implements Closeable { } mInputStream = new BluetoothInputStream(this); mOutputStream = new BluetoothOutputStream(this); + mClosed = false; + mLock = new ReentrantReadWriteLock(); + } + + /** + * Construct a BluetoothSocket from address. Used by native code. + * @param type type of socket + * @param fd fd to use for connected socket, or -1 for a new socket + * @param auth require the remote device to be authenticated + * @param encrypt require the connection to be encrypted + * @param address remote device that this socket can connect to + * @param port remote port + * @throws IOException On error, for example Bluetooth not available, or + * insufficient priveleges + */ + private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address, + int port) throws IOException { + this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null); } + /** @hide */ @Override protected void finalize() throws Throwable { try { @@ -113,37 +177,67 @@ public final class BluetoothSocket implements Closeable { /** * Attempt to connect to a remote device. - * This method will block until a connection is made or the connection + * <p>This method will block until a connection is made or the connection * fails. If this method returns without an exception then this socket - * is now connected. #close can be used to abort this call from another - * thread. - * @throws IOException On error, for example connection failure + * is now connected. + * <p>{@link #close} can be used to abort this call from another thread. + * @throws IOException on error, for example connection failure */ public void connect() throws IOException { - connectNative(mAddress, mPort, -1); + mLock.readLock().lock(); + try { + if (mClosed) throw new IOException("socket closed"); + + if (mSdp != null) { + mPort = mSdp.doSdp(); // blocks + } + + connectNative(); // blocks + } finally { + mLock.readLock().unlock(); + } } /** - * Closes this socket. - * This will cause other blocking calls on this socket to immediately + * Immediately close this socket, and release all associated resources. + * <p>Causes blocked calls on this socket in other threads to immediately * throw an IOException. */ public void close() throws IOException { - closeNative(); + // abort blocking operations on the socket + mLock.readLock().lock(); + try { + if (mClosed) return; + if (mSdp != null) { + mSdp.cancel(); + } + abortNative(); + } finally { + mLock.readLock().unlock(); + } + + // all native calls are guaranteed to immediately return after + // abortNative(), so this lock should immediatley acquire + mLock.writeLock().lock(); + try { + mClosed = true; + destroyNative(); + } finally { + mLock.writeLock().unlock(); + } } /** - * Return the address we are connecting, or connected, to. - * @return Bluetooth address, or null if this socket has not yet attempted - * or established a connection. + * Get the remote device this socket is connecting, or connected, to. + * @return remote device */ - public String getAddress() { - return mAddress; + public BluetoothDevice getRemoteDevice() { + return mDevice; } /** * Get the input stream associated with this socket. - * The input stream will be returned even if the socket is not yet + * <p>The input stream will be returned even if the socket is not yet * connected, but operations on that stream will throw IOException until * the associated socket is connected. * @return InputStream @@ -154,7 +248,7 @@ public final class BluetoothSocket implements Closeable { /** * Get the output stream associated with this socket. - * The output stream will be returned even if the socket is not yet + * <p>The output stream will be returned even if the socket is not yet * connected, but operations on that stream will throw IOException until * the associated socket is connected. * @return OutputStream @@ -163,14 +257,131 @@ public final class BluetoothSocket implements Closeable { return mOutputStream; } - private native void initSocketNative(); - private native void initSocketFromFdNative(int fd); - private native void connectNative(String address, int port, int timeout); - /*package*/ native void bindListenNative(int port) throws IOException; - /*package*/ native BluetoothSocket acceptNative(int timeout) throws IOException; - /*package*/ native int availableNative(); - /*package*/ native int readNative(); - /*package*/ native void writeNative(int data); - /*package*/ native void closeNative(); - private native void destroyNative(); + /** + * Currently returns unix errno instead of throwing IOException, + * so that BluetoothAdapter can check the error code for EADDRINUSE + */ + /*package*/ int bindListen() { + mLock.readLock().lock(); + try { + if (mClosed) return EBADFD; + return bindListenNative(); + } finally { + mLock.readLock().unlock(); + } + } + + /*package*/ BluetoothSocket accept(int timeout) throws IOException { + mLock.readLock().lock(); + try { + if (mClosed) throw new IOException("socket closed"); + return acceptNative(timeout); + } finally { + mLock.readLock().unlock(); + } + } + + /*package*/ int available() throws IOException { + mLock.readLock().lock(); + try { + if (mClosed) throw new IOException("socket closed"); + return availableNative(); + } finally { + mLock.readLock().unlock(); + } + } + + /*package*/ int read(byte[] b, int offset, int length) throws IOException { + mLock.readLock().lock(); + try { + if (mClosed) throw new IOException("socket closed"); + return readNative(b, offset, length); + } finally { + mLock.readLock().unlock(); + } + } + + /*package*/ int write(byte[] b, int offset, int length) throws IOException { + mLock.readLock().lock(); + try { + if (mClosed) throw new IOException("socket closed"); + return writeNative(b, offset, length); + } finally { + mLock.readLock().unlock(); + } + } + + private native void initSocketNative() throws IOException; + private native void initSocketFromFdNative(int fd) throws IOException; + private native void connectNative() throws IOException; + private native int bindListenNative(); + private native BluetoothSocket acceptNative(int timeout) throws IOException; + private native int availableNative() throws IOException; + private native int readNative(byte[] b, int offset, int length) throws IOException; + private native int writeNative(byte[] b, int offset, int length) throws IOException; + private native void abortNative() throws IOException; + private native void destroyNative() throws IOException; + /** + * Throws an IOException for given posix errno. Done natively so we can + * use strerr to convert to string error. + */ + /*package*/ native void throwErrnoNative(int errno) throws IOException; + + /** + * Helper to perform blocking SDP lookup. + */ + private static class SdpHelper extends IBluetoothCallback.Stub { + private final IBluetooth service; + private final ParcelUuid uuid; + private final BluetoothDevice device; + private int channel; + private boolean canceled; + public SdpHelper(BluetoothDevice device, ParcelUuid uuid) { + service = BluetoothDevice.getService(); + this.device = device; + this.uuid = uuid; + canceled = false; + } + /** + * Returns the RFCOMM channel for the UUID, or throws IOException + * on failure. + */ + public synchronized int doSdp() throws IOException { + if (canceled) throw new IOException("Service discovery canceled"); + channel = -1; + + boolean inProgress = false; + try { + inProgress = service.fetchRemoteUuids(device.getAddress(), uuid, this); + } catch (RemoteException e) {Log.e(TAG, "", e);} + + if (!inProgress) throw new IOException("Unable to start Service Discovery"); + + try { + /* 12 second timeout as a precaution - onRfcommChannelFound + * should always occur before the timeout */ + wait(12000); // block + + } catch (InterruptedException e) {} + + if (canceled) throw new IOException("Service discovery canceled"); + if (channel < 1) throw new IOException("Service discovery failed"); + + return channel; + } + /** Object cannot be re-used after calling cancel() */ + public synchronized void cancel() { + if (!canceled) { + canceled = true; + channel = -1; + notifyAll(); // unblock + } + } + public synchronized void onRfcommChannelFound(int channel) { + if (!canceled) { + this.channel = channel; + notifyAll(); // unblock + } + } + } } diff --git a/core/java/android/bluetooth/BluetoothUuid.java b/core/java/android/bluetooth/BluetoothUuid.java new file mode 100644 index 0000000..4164a3d --- /dev/null +++ b/core/java/android/bluetooth/BluetoothUuid.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2009 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.bluetooth; + +import android.os.ParcelUuid; + +import java.util.Arrays; +import java.util.HashSet; + +/** +* Static helper methods and constants to decode the ParcelUuid of remote devices. +* @hide +*/ +public final class BluetoothUuid { + + /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs + * for the various services. + * + * The following 128 bit values are calculated as: + * uuid * 2^96 + BASE_UUID + */ + public static final ParcelUuid AudioSink = + ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid AudioSource = + ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid AdvAudioDist = + ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid HSP = + ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid Handsfree = + ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid AvrcpController = + ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid AvrcpTarget = + ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB"); + public static final ParcelUuid ObexObjectPush = + ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb"); + + public static final ParcelUuid[] RESERVED_UUIDS = { + AudioSink, AudioSource, AdvAudioDist, HSP, Handsfree, AvrcpController, AvrcpTarget, + ObexObjectPush}; + + public static boolean isAudioSource(ParcelUuid uuid) { + return uuid.equals(AudioSource); + } + + public static boolean isAudioSink(ParcelUuid uuid) { + return uuid.equals(AudioSink); + } + + public static boolean isAdvAudioDist(ParcelUuid uuid) { + return uuid.equals(AdvAudioDist); + } + + public static boolean isHandsfree(ParcelUuid uuid) { + return uuid.equals(Handsfree); + } + + public static boolean isHeadset(ParcelUuid uuid) { + return uuid.equals(HSP); + } + + public static boolean isAvrcpController(ParcelUuid uuid) { + return uuid.equals(AvrcpController); + } + + public static boolean isAvrcpTarget(ParcelUuid uuid) { + return uuid.equals(AvrcpTarget); + } + + /** + * Returns true if ParcelUuid is present in uuidArray + * + * @param uuidArray - Array of ParcelUuids + * @param uuid + */ + public static boolean isUuidPresent(ParcelUuid[] uuidArray, ParcelUuid uuid) { + if ((uuidArray == null || uuidArray.length == 0) && uuid == null) + return true; + + if (uuidArray == null) + return false; + + for (ParcelUuid element: uuidArray) { + if (element.equals(uuid)) return true; + } + return false; + } + + /** + * Returns true if there any common ParcelUuids in uuidA and uuidB. + * + * @param uuidA - List of ParcelUuids + * @param uuidB - List of ParcelUuids + * + */ + public static boolean containsAnyUuid(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { + if (uuidA == null && uuidB == null) return true; + + if (uuidA == null) { + return uuidB.length == 0 ? true : false; + } + + if (uuidB == null) { + return uuidA.length == 0 ? true : false; + } + + HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid> (Arrays.asList(uuidA)); + for (ParcelUuid uuid: uuidB) { + if (uuidSet.contains(uuid)) return true; + } + return false; + } + + /** + * Returns true if all the ParcelUuids in ParcelUuidB are present in + * ParcelUuidA + * + * @param uuidA - Array of ParcelUuidsA + * @param uuidB - Array of ParcelUuidsB + * + */ + public static boolean containsAllUuids(ParcelUuid[] uuidA, ParcelUuid[] uuidB) { + if (uuidA == null && uuidB == null) return true; + + if (uuidA == null) { + return uuidB.length == 0 ? true : false; + } + + if (uuidB == null) return true; + + HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid> (Arrays.asList(uuidA)); + for (ParcelUuid uuid: uuidB) { + if (!uuidSet.contains(uuid)) return false; + } + return true; + } + +} diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java index f987ffd..e2935c9 100644 --- a/core/java/android/bluetooth/HeadsetBase.java +++ b/core/java/android/bluetooth/HeadsetBase.java @@ -31,7 +31,7 @@ import android.util.Log; * * @hide */ -public class HeadsetBase { +public final class HeadsetBase { private static final String TAG = "Bluetooth HeadsetBase"; private static final boolean DBG = false; @@ -42,8 +42,9 @@ public class HeadsetBase { private static int sAtInputCount = 0; /* TODO: Consider not using a static variable */ - private final BluetoothDevice mBluetooth; - private final String mAddress; + private final BluetoothAdapter mAdapter; + private final BluetoothDevice mRemoteDevice; + private final String mAddress; // for native code private final int mRfcommChannel; private int mNativeData; private Thread mEventThread; @@ -73,12 +74,13 @@ public class HeadsetBase { private native void cleanupNativeDataNative(); - public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address, - int rfcommChannel) { + public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device, + int rfcommChannel) { mDirection = DIRECTION_OUTGOING; mConnectTimestamp = System.currentTimeMillis(); - mBluetooth = bluetooth; - mAddress = address; + mAdapter = adapter; + mRemoteDevice = device; + mAddress = device.getAddress(); mRfcommChannel = rfcommChannel; mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase"); mWakeLock.setReferenceCounted(false); @@ -88,12 +90,13 @@ public class HeadsetBase { } /* Create from an already exisiting rfcomm connection */ - public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address, int socketFd, - int rfcommChannel, Handler handler) { + public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device, + int socketFd, int rfcommChannel, Handler handler) { mDirection = DIRECTION_INCOMING; mConnectTimestamp = System.currentTimeMillis(); - mBluetooth = bluetooth; - mAddress = address; + mAdapter = adapter; + mRemoteDevice = device; + mAddress = device.getAddress(); mRfcommChannel = rfcommChannel; mEventThreadHandler = handler; mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase"); @@ -208,9 +211,10 @@ public class HeadsetBase { */ public boolean connectAsync() { - return connectAsyncNative(); + int ret = connectAsyncNative(); + return (ret == 0) ? true : false; } - private native boolean connectAsyncNative(); + private native int connectAsyncNative(); public int getRemainingAsyncConnectWaitingTimeMs() { return mTimeoutRemainingMs; @@ -252,12 +256,8 @@ public class HeadsetBase { return mEventThread != null; } - public String getAddress() { - return mAddress; - } - - public String getName() { - return mBluetooth.getRemoteName(mAddress); + public BluetoothDevice getRemoteDevice() { + return mRemoteDevice; } public int getDirection() { diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 6cd792e..7e752af 100644 --- a/core/java/android/bluetooth/IBluetoothDevice.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -16,14 +16,15 @@ package android.bluetooth; -import android.bluetooth.IBluetoothDeviceCallback; +import android.bluetooth.IBluetoothCallback; +import android.os.ParcelUuid; /** * System private API for talking with the Bluetooth service. * * {@hide} */ -interface IBluetoothDevice +interface IBluetooth { boolean isEnabled(); int getBluetoothState(); @@ -33,28 +34,16 @@ interface IBluetoothDevice String getAddress(); String getName(); boolean setName(in String name); - String getVersion(); - String getRevision(); - String getManufacturer(); - String getCompany(); int getScanMode(); - boolean setScanMode(int mode); + boolean setScanMode(int mode, int duration); int getDiscoverableTimeout(); boolean setDiscoverableTimeout(int timeout); - boolean startDiscovery(boolean resolveNames); + boolean startDiscovery(); boolean cancelDiscovery(); boolean isDiscovering(); - boolean startPeriodicDiscovery(); - boolean stopPeriodicDiscovery(); - boolean isPeriodicDiscovery(); - String[] listRemoteDevices(); - - String[] listAclConnections(); - boolean isAclConnected(in String address); - boolean disconnectRemoteDeviceAcl(in String address); boolean createBond(in String address); boolean cancelBondProcess(in String address); @@ -63,16 +52,19 @@ interface IBluetoothDevice int getBondState(in String address); String getRemoteName(in String address); - String getRemoteVersion(in String address); - String getRemoteRevision(in String address); int getRemoteClass(in String address); - String getRemoteManufacturer(in String address); - String getRemoteCompany(in String address); - boolean getRemoteServiceChannel(in String address, int uuid16, in IBluetoothDeviceCallback callback); - byte[] getRemoteFeatures(in String adddress); - String lastSeen(in String address); - String lastUsed(in String address); + ParcelUuid[] getRemoteUuids(in String address); + boolean fetchRemoteUuids(in String address, in ParcelUuid uuid, in IBluetoothCallback callback); + int getRemoteServiceChannel(in String address, in ParcelUuid uuid); boolean setPin(in String address, in byte[] pin); - boolean cancelPin(in String address); + boolean setPasskey(in String address, int passkey); + boolean setPairingConfirmation(in String address, boolean confirm); + boolean cancelPairingUserInput(in String address); + + boolean setTrust(in String address, in boolean value); + boolean getTrustState(in String address); + + int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b); + void removeServiceRecord(int handle); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 55ff27f..002cf4e 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -16,16 +16,20 @@ package android.bluetooth; +import android.bluetooth.BluetoothDevice; + /** * System private API for Bluetooth A2DP service * * {@hide} */ interface IBluetoothA2dp { - int connectSink(in String address); - int disconnectSink(in String address); - List<String> listConnectedSinks(); - int getSinkState(in String address); - int setSinkPriority(in String address, int priority); - int getSinkPriority(in String address); + boolean connectSink(in BluetoothDevice device); + boolean disconnectSink(in BluetoothDevice device); + boolean suspendSink(in BluetoothDevice device); + boolean resumeSink(in BluetoothDevice device); + BluetoothDevice[] getConnectedSinks(); // change to Set<> once AIDL supports + int getSinkState(in BluetoothDevice device); + boolean setSinkPriority(in BluetoothDevice device, int priority); + int getSinkPriority(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothCallback.aidl index d057093..8edb3f4 100644 --- a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl +++ b/core/java/android/bluetooth/IBluetoothCallback.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, The Android Open Source Project + * Copyright (C) 2009, 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. @@ -17,9 +17,11 @@ package android.bluetooth; /** + * System private API for Bluetooth service callbacks. + * * {@hide} */ -oneway interface IBluetoothDeviceCallback +interface IBluetoothCallback { - void onGetRemoteServiceChannelResult(in String address, int channel); + void onRfcommChannelFound(int channel); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 5f42fd6..6cccd50 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -16,6 +16,8 @@ package android.bluetooth; +import android.bluetooth.BluetoothDevice; + /** * System private API for Bluetooth Headset service * @@ -23,13 +25,13 @@ package android.bluetooth; */ interface IBluetoothHeadset { int getState(); - String getHeadsetAddress(); - boolean connectHeadset(in String address); + BluetoothDevice getCurrentHeadset(); + boolean connectHeadset(in BluetoothDevice device); void disconnectHeadset(); - boolean isConnected(in String address); + boolean isConnected(in BluetoothDevice device); boolean startVoiceRecognition(); boolean stopVoiceRecognition(); - boolean setPriority(in String address, int priority); - int getPriority(in String address); + boolean setPriority(in BluetoothDevice device, int priority); + int getPriority(in BluetoothDevice device); int getBatteryUsageHint(); } diff --git a/core/java/android/bluetooth/BluetoothError.java b/core/java/android/bluetooth/IBluetoothPbap.aidl index 2554bea..7cc77d1 100644 --- a/core/java/android/bluetooth/BluetoothError.java +++ b/core/java/android/bluetooth/IBluetoothPbap.aidl @@ -16,27 +16,17 @@ package android.bluetooth; +import android.bluetooth.BluetoothDevice; + /** - * Bluetooth API error codes. - * - * Errors are always negative. + * System private API for Bluetooth pbap service * - * @hide + * {@hide} */ -public class BluetoothError { - /** No error */ - public static final int SUCCESS = 0; - - /** Generic error */ - public static final int ERROR = -1000; - - /** Bluetooth currently disabled */ - public static final int ERROR_DISABLED = -1001; - - /** IPC is not ready, for example service is not yet bound */ - public static final int ERROR_IPC_NOT_READY = -1011; - - /** Some other IPC error, for example a RemoteException */ - public static final int ERROR_IPC = -1012; - +interface IBluetoothPbap { + int getState(); + BluetoothDevice getClient(); + boolean connect(in BluetoothDevice device); + void disconnect(); + boolean isConnected(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java index 1bf786f..116310a 100644 --- a/core/java/android/bluetooth/ScoSocket.java +++ b/core/java/android/bluetooth/ScoSocket.java @@ -87,7 +87,7 @@ public class ScoSocket { * Does not block. */ public synchronized boolean connect(String address) { - if (VDBG) log("connect() " + this); + if (DBG) log("connect() " + this); if (mState != STATE_READY) { if (DBG) log("connect(): Bad state"); return false; diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html index 79abf0c..4f0755e 100644 --- a/core/java/android/bluetooth/package.html +++ b/core/java/android/bluetooth/package.html @@ -1,13 +1,109 @@ <HTML> <BODY> -Provides classes that manage Bluetooth functionality on the device. -<p> -The Bluetooth APIs allow applications can connect and disconnect headsets, or scan -for other kinds of Bluetooth devices and pair them. Further control includes the -ability to write and modify the local Service Discovery Protocol (SDP) database, -query the SDP database of other Bluetooth devices, establish RFCOMM -channels/sockets on Android, and connect to specified sockets on other devices. +Provides classes that manage Bluetooth functionality, such as scanning for +devices, connecting with devices, and managing data transfer between devices. + +<p>The Bluetooth APIs let applications:</p> +<ul> + <li>Scan for other Bluetooth devices</li> + <li>Query the local Bluetooth adapter for paired Bluetooth devices</li> + <li>Establish RFCOMM channels/sockets</li> + <li>Connect to specified sockets on other devices</li> + <li>Transfer data to and from other devices</li> +</ul> + +<p class="note"><strong>Note:</strong> +To perform Bluetooth communication using these APIs, an application must +declare the {@link android.Manifest.permission#BLUETOOTH} permission. Some +additional functionality, such as requesting device discovery and +pairing also requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN} +permission. </p> -<p>Remember, not all Android devices are guaranteed to have Bluetooth functionality.</p> + +<h3>Overview</h3> + +<p>Here's a basic introduction to the Bluetooth classes:</p> +<dl> + <dt>{@link android.bluetooth.BluetoothAdapter}</dt> + <dd>This represents the local Bluetooth adapter, which is essentially the + entry-point to performing any interaction with Bluetooth. With it, you can + discover other Bluetooth devices, query a list of bonded (paired) devices, + initialize a {@link android.bluetooth.BluetoothDevice} using a known MAC + address, and create a {@link android.bluetooth.BluetoothServerSocket} to + listen for communications from other devices.</dd> + + <dt>{@link android.bluetooth.BluetoothDevice}</dt> + <dd>This represents a remote Bluetooth device. Use this to request a + connection with a remote device through a + {@link android.bluetooth.BluetoothSocket} + or query information about the device such as its name, address, class, and + bonding state.</dd> + + <dt>{@link android.bluetooth.BluetoothSocket}</dt> + <dd>This represents the interface for a Bluetooth socket + (similar to a TCP client-side {@link java.net.Socket}). This is the + connection point that allows an app to transfer data with another Bluetooth + device via {@link java.io.InputStream} and {@link java.io.OutputStream}.</dd> + <dt>{@link android.bluetooth.BluetoothServerSocket}</dt> + + <dd>This represents an open server socket that listens for incoming requests + (similar to a TCP server-side {@link java.net.ServerSocket}). + When attempting to connect two Android devices, one device will need to open + a server socket with this class. When a connection is accepted, a new + {@link android.bluetooth.BluetoothSocket} will be returned, + which can be used to manage the connection and transfer data.</dd> + + <dt>{@link android.bluetooth.BluetoothClass}</dt> + <dd>This represents the Bluetooth class for a device which describes general + characteristics and capabilities of a device. This class and its subclasses + don't provide any actual functionality. The sub-classes are entirely composed + of constants for the device and service class definitions.</dd> +</dl> + + +<h3>Example Procedure</h3> + +<p>For example, here's an pseudo-code procedure for discovering and +connecting a remote device, and transfering data:</p> + +<ol> + <li>Register a {@link android.content.BroadcastReceiver} that accepts the + {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent.</li> + <li>Call {@link android.bluetooth.BluetoothAdapter#getDefaultAdapter} to + retrieve the Android system's local + {@link android.bluetooth.BluetoothAdapter}.</li> + <li>Call {@link android.bluetooth.BluetoothAdapter#startDiscovery() + BluetoothAdapter.startDiscovery()} to scan for local devices. This is where + the BroadcastReceiver comes in; Android now scans for devices and will + broadcast the {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent + for each remote device discovered. The + {@link android.content.BroadcastReceiver} + you created will receive each Intent.</li> + <li>The {@link android.bluetooth.BluetoothDevice#ACTION_FOUND} Intent + includes the {@link android.bluetooth.BluetoothDevice#EXTRA_DEVICE} + Parcelable extra, which is a {@link android.bluetooth.BluetoothDevice} + object. Extract this from the Intent and call + {@link android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(java.util.UUID) + BluetoothDevice.createRfcommSocketToServiceRecord()} + to open a {@link android.bluetooth.BluetoothSocket} with a chosen + remote device.</li> + <li>Call {@link android.bluetooth.BluetoothSocket#connect() + BluetoothSocket.connect()} to connect with the remote device.</li> + <li>When successfully connected, call + {@link android.bluetooth.BluetoothSocket#getInputStream() + BluetoothSocket.getInputStream()} and/or + {@link android.bluetooth.BluetoothSocket#getOutputStream() + BluetoothSocket.getOutputStream()} to retreive an + {@link java.io.InputStream} and {@link java.io.OutputStream}, respectively, + which are hooked into the socket.</li> + <li>Use {@link java.io.InputStream#read(byte[]) InputStream.read()} and + {@link java.io.OutputStream#write(byte[]) OutputStream.write()} to transfer + data.</li> +</ol> + + + +<p class="note"><strong>Note:</strong> +Not all Android devices are guaranteed to have Bluetooth functionality.</p> </BODY> </HTML> |