summaryrefslogtreecommitdiffstats
path: root/core/java/android/bluetooth
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/bluetooth
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz
frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/bluetooth')
-rw-r--r--core/java/android/bluetooth/AtCommandHandler.java93
-rw-r--r--core/java/android/bluetooth/AtCommandResult.java117
-rw-r--r--core/java/android/bluetooth/AtParser.java370
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java247
-rw-r--r--core/java/android/bluetooth/BluetoothAudioGateway.java190
-rw-r--r--core/java/android/bluetooth/BluetoothClass.java191
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java580
-rw-r--r--core/java/android/bluetooth/BluetoothError.java42
-rw-r--r--core/java/android/bluetooth/BluetoothHeadset.java353
-rw-r--r--core/java/android/bluetooth/BluetoothIntent.java128
-rw-r--r--core/java/android/bluetooth/Database.java200
-rw-r--r--core/java/android/bluetooth/HeadsetBase.java285
-rw-r--r--core/java/android/bluetooth/IBluetoothA2dp.aidl31
-rw-r--r--core/java/android/bluetooth/IBluetoothDevice.aidl77
-rw-r--r--core/java/android/bluetooth/IBluetoothDeviceCallback.aidl27
-rw-r--r--core/java/android/bluetooth/IBluetoothHeadset.aidl34
-rw-r--r--core/java/android/bluetooth/RfcommSocket.java674
-rw-r--r--core/java/android/bluetooth/ScoSocket.java194
-rw-r--r--core/java/android/bluetooth/package.html13
19 files changed, 3846 insertions, 0 deletions
diff --git a/core/java/android/bluetooth/AtCommandHandler.java b/core/java/android/bluetooth/AtCommandHandler.java
new file mode 100644
index 0000000..8de2133
--- /dev/null
+++ b/core/java/android/bluetooth/AtCommandHandler.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2007 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.bluetooth.AtCommandResult;
+
+/**
+ * Handler Interface for {@link AtParser}.<p>
+ * @hide
+ */
+public abstract class AtCommandHandler {
+
+ /**
+ * Handle Basic commands "ATA".<p>
+ * These are single letter commands such as ATA and ATD. Anything following
+ * the single letter command ('A' and 'D' respectively) will be passed as
+ * 'arg'.<p>
+ * For example, "ATDT1234" would result in the call
+ * handleBasicCommand("T1234").<p>
+ * @param arg Everything following the basic command character.
+ * @return The result of this command.
+ */
+ public AtCommandResult handleBasicCommand(String arg) {
+ return new AtCommandResult(AtCommandResult.ERROR);
+ }
+
+ /**
+ * Handle Actions command "AT+FOO".<p>
+ * Action commands are part of the Extended command syntax, and are
+ * typically used to signal an action on "FOO".<p>
+ * @return The result of this command.
+ */
+ public AtCommandResult handleActionCommand() {
+ return new AtCommandResult(AtCommandResult.ERROR);
+ }
+
+ /**
+ * Handle Read command "AT+FOO?".<p>
+ * Read commands are part of the Extended command syntax, and are
+ * typically used to read the value of "FOO".<p>
+ * @return The result of this command.
+ */
+ public AtCommandResult handleReadCommand() {
+ return new AtCommandResult(AtCommandResult.ERROR);
+ }
+
+ /**
+ * Handle Set command "AT+FOO=...".<p>
+ * Set commands are part of the Extended command syntax, and are
+ * typically used to set the value of "FOO". Multiple arguments can be
+ * sent.<p>
+ * AT+FOO=[<arg1>[,<arg2>[,...]]]<p>
+ * Each argument will be either numeric (Integer) or String.
+ * handleSetCommand is passed a generic Object[] array in which each
+ * element will be an Integer (if it can be parsed with parseInt()) or
+ * String.<p>
+ * Missing arguments ",," are set to empty Strings.<p>
+ * @param args Array of String and/or Integer's. There will always be at
+ * least one element in this array.
+ * @return The result of this command.
+ */
+ // Typically used to set this paramter
+ public AtCommandResult handleSetCommand(Object[] args) {
+ return new AtCommandResult(AtCommandResult.ERROR);
+ }
+
+ /**
+ * Handle Test command "AT+FOO=?".<p>
+ * Test commands are part of the Extended command syntax, and are typically
+ * used to request an indication of the range of legal values that "FOO"
+ * can take.<p>
+ * By defualt we return an OK result, to indicate that this command is at
+ * least recognized.<p>
+ * @return The result of this command.
+ */
+ public AtCommandResult handleTestCommand() {
+ return new AtCommandResult(AtCommandResult.OK);
+ }
+}
diff --git a/core/java/android/bluetooth/AtCommandResult.java b/core/java/android/bluetooth/AtCommandResult.java
new file mode 100644
index 0000000..638be2d
--- /dev/null
+++ b/core/java/android/bluetooth/AtCommandResult.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2007 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 java.util.*;
+
+/**
+ * The result of execution of an single AT command.<p>
+ *
+ *
+ * This class can represent the final response to an AT command line, and also
+ * intermediate responses to a single command within a chained AT command
+ * line.<p>
+ *
+ * The actual responses that are intended to be send in reply to the AT command
+ * line are stored in a string array. The final response is stored as an
+ * int enum, converted to a string when toString() is called. Only a single
+ * final response is sent from multiple commands chained into a single command
+ * line.<p>
+ * @hide
+ */
+public class AtCommandResult {
+ // Result code enumerations
+ public static final int OK = 0;
+ public static final int ERROR = 1;
+ public static final int UNSOLICITED = 2;
+
+ private static final String OK_STRING = "OK";
+ private static final String ERROR_STRING = "ERROR";
+
+ private int mResultCode; // Result code
+ private StringBuilder mResponse; // Response with CRLF line breaks
+
+ /**
+ * Construct a new AtCommandResult with given result code, and an empty
+ * response array.
+ * @param resultCode One of OK, ERROR or UNSOLICITED.
+ */
+ public AtCommandResult(int resultCode) {
+ mResultCode = resultCode;
+ mResponse = new StringBuilder();
+ }
+
+ /**
+ * Construct a new AtCommandResult with result code OK, and the specified
+ * single line response.
+ * @param response The single line response.
+ */
+ public AtCommandResult(String response) {
+ this(OK);
+ addResponse(response);
+ }
+
+ public int getResultCode() {
+ return mResultCode;
+ }
+
+ /**
+ * Add another line to the response.
+ */
+ public void addResponse(String response) {
+ appendWithCrlf(mResponse, response);
+ }
+
+ /**
+ * Add the given result into this AtCommandResult object.<p>
+ * Used to combine results from multiple commands in a single command line
+ * (command chaining).
+ * @param result The AtCommandResult to add to this result.
+ */
+ public void addResult(AtCommandResult result) {
+ if (result != null) {
+ appendWithCrlf(mResponse, result.mResponse.toString());
+ mResultCode = result.mResultCode;
+ }
+ }
+
+ /**
+ * Generate the string response ready to send
+ */
+ public String toString() {
+ StringBuilder result = new StringBuilder(mResponse.toString());
+ switch (mResultCode) {
+ case OK:
+ appendWithCrlf(result, OK_STRING);
+ break;
+ case ERROR:
+ appendWithCrlf(result, ERROR_STRING);
+ break;
+ }
+ return result.toString();
+ }
+
+ /** Append a string to a string builder, joining with a double
+ * CRLF. Used to create multi-line AT command replies
+ */
+ public static void appendWithCrlf(StringBuilder str1, String str2) {
+ if (str1.length() > 0 && str2.length() > 0) {
+ str1.append("\r\n\r\n");
+ }
+ str1.append(str2);
+ }
+};
diff --git a/core/java/android/bluetooth/AtParser.java b/core/java/android/bluetooth/AtParser.java
new file mode 100644
index 0000000..1ea3150
--- /dev/null
+++ b/core/java/android/bluetooth/AtParser.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2007 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.bluetooth.AtCommandHandler;
+import android.bluetooth.AtCommandResult;
+
+import java.util.*;
+
+/**
+ * An AT (Hayes command) Parser based on (a subset of) the ITU-T V.250 standard.
+ * <p>
+ *
+ * Conforment with the subset of V.250 required for implementation of the
+ * Bluetooth Headset and Handsfree Profiles, as per Bluetooth SIP
+ * specifications. Also implements some V.250 features not required by
+ * Bluetooth - such as chained commands.<p>
+ *
+ * Command handlers are registered with an AtParser object. These handlers are
+ * invoked when command lines are processed by AtParser's process() method.<p>
+ *
+ * The AtParser object accepts a new command line to parse via its process()
+ * method. It breaks each command line into one or more commands. Each command
+ * is parsed for name, type, and (optional) arguments, and an appropriate
+ * external handler method is called through the AtCommandHandler interface.
+ *
+ * The command types are<ul>
+ * <li>Basic Command. For example "ATDT1234567890". Basic command names are a
+ * single character (e.g. "D"), and everything following this character is
+ * passed to the handler as a string argument (e.g. "T1234567890").
+ * <li>Action Command. For example "AT+CIMI". The command name is "CIMI", and
+ * there are no arguments for action commands.
+ * <li>Read Command. For example "AT+VGM?". The command name is "VGM", and there
+ * are no arguments for get commands.
+ * <li>Set Command. For example "AT+VGM=14". The command name is "VGM", and
+ * there is a single integer argument in this case. In the general case then
+ * can be zero or more arguments (comma deliminated) each of integer or string
+ * form.
+ * <li>Test Command. For example "AT+VGM=?. No arguments.
+ * </ul>
+ *
+ * In V.250 the last four command types are known as Extended Commands, and
+ * they are used heavily in Bluetooth.<p>
+ *
+ * Basic commands cannot be chained in this implementation. For Bluetooth
+ * headset/handsfree use this is acceptable, because they only use the basic
+ * commands ATA and ATD, which are not allowed to be chained. For general V.250
+ * use we would need to improve this class to allow Basic command chaining -
+ * however its tricky to get right becuase there is no deliminator for Basic
+ * command chaining.<p>
+ *
+ * Extended commands can be chained. For example:<p>
+ * AT+VGM?;+VGM=14;+CIMI<p>
+ * This is equivalent to:<p>
+ * AT+VGM?
+ * AT+VGM=14
+ * AT+CIMI
+ * Except that only one final result code is return (although several
+ * intermediate responses may be returned), and as soon as one command in the
+ * chain fails the rest are abandonded.<p>
+ *
+ * Handlers are registered by there command name via register(Char c, ...) or
+ * register(String s, ...). Handlers for Basic command should be registered by
+ * the basic command character, and handlers for Extended commands should be
+ * registered by String.<p>
+ *
+ * Refer to:<ul>
+ * <li>ITU-T Recommendation V.250
+ * <li>ETSI TS 127.007 (AT Comannd set for User Equipment, 3GPP TS 27.007)
+ * <li>Bluetooth Headset Profile Spec (K6)
+ * <li>Bluetooth Handsfree Profile Spec (HFP 1.5)
+ * </ul>
+ * @hide
+ */
+public class AtParser {
+
+ // Extended command type enumeration, only used internally
+ private static final int TYPE_ACTION = 0; // AT+FOO
+ private static final int TYPE_READ = 1; // AT+FOO?
+ private static final int TYPE_SET = 2; // AT+FOO=
+ private static final int TYPE_TEST = 3; // AT+FOO=?
+
+ private HashMap<String, AtCommandHandler> mExtHandlers;
+ private HashMap<Character, AtCommandHandler> mBasicHandlers;
+
+ private String mLastInput; // for "A/" (repeat last command) support
+
+ /**
+ * Create a new AtParser.<p>
+ * No handlers are registered.
+ */
+ public AtParser() {
+ mBasicHandlers = new HashMap<Character, AtCommandHandler>();
+ mExtHandlers = new HashMap<String, AtCommandHandler>();
+ mLastInput = "";
+ }
+
+ /**
+ * Register a Basic command handler.<p>
+ * Basic command handlers are later called via their
+ * <code>handleBasicCommand(String args)</code> method.
+ * @param command Command name - a single character
+ * @param handler Handler to register
+ */
+ public void register(Character command, AtCommandHandler handler) {
+ mBasicHandlers.put(command, handler);
+ }
+
+ /**
+ * Register an Extended command handler.<p>
+ * Extended command handlers are later called via:<ul>
+ * <li><code>handleActionCommand()</code>
+ * <li><code>handleGetCommand()</code>
+ * <li><code>handleSetCommand()</code>
+ * <li><code>handleTestCommand()</code>
+ * </ul>
+ * Only one method will be called for each command processed.
+ * @param command Command name - can be multiple characters
+ * @param handler Handler to register
+ */
+ public void register(String command, AtCommandHandler handler) {
+ mExtHandlers.put(command, handler);
+ }
+
+
+ /**
+ * Strip input of whitespace and force Uppercase - except sections inside
+ * quotes. Also fixes unmatched quotes (by appending a quote). Double
+ * quotes " are the only quotes allowed by V.250
+ */
+ static private String clean(String input) {
+ StringBuilder out = new StringBuilder(input.length());
+
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (c == '"') {
+ int j = input.indexOf('"', i + 1 ); // search for closing "
+ if (j == -1) { // unmatched ", insert one.
+ out.append(input.substring(i, input.length()));
+ out.append('"');
+ break;
+ }
+ out.append(input.substring(i, j + 1));
+ i = j;
+ } else if (c != ' ') {
+ out.append(Character.toUpperCase(c));
+ }
+ }
+
+ return out.toString();
+ }
+
+ static private boolean isAtoZ(char c) {
+ return (c >= 'A' && c <= 'Z');
+ }
+
+ /**
+ * Find a character ch, ignoring quoted sections.
+ * Return input.length() if not found.
+ */
+ static private int findChar(char ch, String input, int fromIndex) {
+ for (int i = fromIndex; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (c == '"') {
+ i = input.indexOf('"', i + 1);
+ if (i == -1) {
+ return input.length();
+ }
+ } else if (c == ch) {
+ return i;
+ }
+ }
+ return input.length();
+ }
+
+ /**
+ * Break an argument string into individual arguments (comma deliminated).
+ * Integer arguments are turned into Integer objects. Otherwise a String
+ * object is used.
+ */
+ static private Object[] generateArgs(String input) {
+ int i = 0;
+ int j;
+ ArrayList<Object> out = new ArrayList<Object>();
+ while (i <= input.length()) {
+ j = findChar(',', input, i);
+
+ String arg = input.substring(i, j);
+ try {
+ out.add(new Integer(arg));
+ } catch (NumberFormatException e) {
+ out.add(arg);
+ }
+
+ i = j + 1; // move past comma
+ }
+ return out.toArray();
+ }
+
+ /**
+ * Return the index of the end of character after the last characeter in
+ * the extended command name. Uses the V.250 spec for allowed command
+ * names.
+ */
+ static private int findEndExtendedName(String input, int index) {
+ for (int i = index; i < input.length(); i++) {
+ char c = input.charAt(i);
+
+ // V.250 defines the following chars as legal extended command
+ // names
+ if (isAtoZ(c)) continue;
+ if (c >= '0' && c <= '9') continue;
+ switch (c) {
+ case '!':
+ case '%':
+ case '-':
+ case '.':
+ case '/':
+ case ':':
+ case '_':
+ continue;
+ default:
+ return i;
+ }
+ }
+ return input.length();
+ }
+
+ /**
+ * Processes an incoming AT command line.<p>
+ * This method will invoke zero or one command handler methods for each
+ * command in the command line.<p>
+ * @param raw_input The AT input, without EOL deliminator (e.g. <CR>).
+ * @return Result object for this command line. This can be
+ * converted to a String[] response with toStrings().
+ */
+ public AtCommandResult process(String raw_input) {
+ String input = clean(raw_input);
+
+ // Handle "A/" (repeat previous line)
+ if (input.regionMatches(0, "A/", 0, 2)) {
+ input = new String(mLastInput);
+ } else {
+ mLastInput = new String(input);
+ }
+
+ // Handle empty line - no response necessary
+ if (input.equals("")) {
+ // Return []
+ return new AtCommandResult(AtCommandResult.UNSOLICITED);
+ }
+
+ // Anything else deserves an error
+ if (!input.regionMatches(0, "AT", 0, 2)) {
+ // Return ["ERROR"]
+ return new AtCommandResult(AtCommandResult.ERROR);
+ }
+
+ // Ok we have a command that starts with AT. Process it
+ int index = 2;
+ AtCommandResult result =
+ new AtCommandResult(AtCommandResult.UNSOLICITED);
+ while (index < input.length()) {
+ char c = input.charAt(index);
+
+ if (isAtoZ(c)) {
+ // Option 1: Basic Command
+ // Pass the rest of the line as is to the handler. Do not
+ // look for any more commands on this line.
+ String args = input.substring(index + 1);
+ if (mBasicHandlers.containsKey((Character)c)) {
+ result.addResult(mBasicHandlers.get(
+ (Character)c).handleBasicCommand(args));
+ return result;
+ } else {
+ // no handler
+ result.addResult(
+ new AtCommandResult(AtCommandResult.ERROR));
+ return result;
+ }
+ // control never reaches here
+ }
+
+ if (c == '+') {
+ // Option 2: Extended Command
+ // Search for first non-name character. Shortcircuit if we dont
+ // handle this command name.
+ int i = findEndExtendedName(input, index + 1);
+ String commandName = input.substring(index, i);
+ if (!mExtHandlers.containsKey(commandName)) {
+ // no handler
+ result.addResult(
+ new AtCommandResult(AtCommandResult.ERROR));
+ return result;
+ }
+ AtCommandHandler handler = mExtHandlers.get(commandName);
+
+ // Search for end of this command - this is usually the end of
+ // line
+ int endIndex = findChar(';', input, index);
+
+ // Determine what type of command this is.
+ // Default to TYPE_ACTION if we can't find anything else
+ // obvious.
+ int type;
+
+ if (i >= endIndex) {
+ type = TYPE_ACTION;
+ } else if (input.charAt(i) == '?') {
+ type = TYPE_READ;
+ } else if (input.charAt(i) == '=') {
+ if (i + 1 < endIndex) {
+ if (input.charAt(i + 1) == '?') {
+ type = TYPE_TEST;
+ } else {
+ type = TYPE_SET;
+ }
+ } else {
+ type = TYPE_SET;
+ }
+ } else {
+ type = TYPE_ACTION;
+ }
+
+ // Call this command. Short-circuit as soon as a command fails
+ switch (type) {
+ case TYPE_ACTION:
+ result.addResult(handler.handleActionCommand());
+ break;
+ case TYPE_READ:
+ result.addResult(handler.handleReadCommand());
+ break;
+ case TYPE_TEST:
+ result.addResult(handler.handleTestCommand());
+ break;
+ case TYPE_SET:
+ Object[] args =
+ generateArgs(input.substring(i + 1, endIndex));
+ result.addResult(handler.handleSetCommand(args));
+ break;
+ }
+ if (result.getResultCode() != AtCommandResult.OK) {
+ return result; // short-circuit
+ }
+
+ index = endIndex;
+ } else {
+ // Can't tell if this is a basic or extended command.
+ // Push forwards and hope we hit something.
+ index++;
+ }
+ }
+ // Finished processing (and all results were ok)
+ return result;
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
new file mode 100644
index 0000000..b0b0154
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -0,0 +1,247 @@
+/*
+ * 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;
+import android.server.BluetoothA2dpService;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.RemoteException;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Public API for controlling the Bluetooth A2DP Profile Service.
+ *
+ * BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC.
+ *
+ * Creating a BluetoothA2dp object will initiate a binding with the
+ * BluetoothHeadset service. Users of this object should call close() when they
+ * are finished, so that this proxy object can unbind from the service.
+ *
+ * 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 {
+ private static final String TAG = "BluetoothA2dp";
+
+ /** 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";
+
+ /** Indicates the state of an A2DP audio sink has changed.
+ * This intent will always contain SINK_STATE, SINK_PREVIOUS_STATE and
+ * BluetoothIntent.ADDRESS 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 int STATE_DISCONNECTED = 0;
+ public static final int STATE_CONNECTING = 1;
+ public static final int STATE_CONNECTED = 2;
+ public static final int STATE_DISCONNECTING = 3;
+ /** Playing implies connected */
+ public static final int STATE_PLAYING = 4;
+
+ /** Default priority for a2dp devices that should allow incoming
+ * connections */
+ public static final int PRIORITY_AUTO = 100;
+ /** 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;
+
+ /**
+ * Create a BluetoothA2dp proxy object for interacting with the local
+ * Bluetooth A2DP service.
+ * @param c Context
+ */
+ 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!");
+ }
+ 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.
+ * @hide
+ */
+ public int connectSink(String address) {
+ try {
+ return mService.connectSink(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /** 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.
+ * @hide
+ */
+ public int disconnectSink(String address) {
+ try {
+ return mService.disconnectSink(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /** Check if a specified A2DP sink is connected.
+ * @param address Remote BT address.
+ * @return True if connected (or playing), false otherwise and on error.
+ * @hide
+ */
+ public boolean isSinkConnected(String address) {
+ int state = getSinkState(address);
+ 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.
+ * @hide
+ */
+ public List<String> listConnectedSinks() {
+ try {
+ return mService.listConnectedSinks();
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return null;
+ }
+ }
+
+ /** Get the state of an A2DP sink
+ * @param address Remote BT address.
+ * @return State code, or negative on error
+ * @hide
+ */
+ public int getSinkState(String address) {
+ try {
+ return mService.getSinkState(address);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /**
+ * Set priority of a2dp sink.
+ * Priority is a non-negative integer. By default paired sinks will have
+ * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
+ * 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 priority Integer priority, for example PRIORITY_AUTO or
+ * PRIORITY_NONE
+ * @return Result code, negative indicates an error
+ */
+ public int setSinkPriority(String address, int priority) {
+ try {
+ return mService.setSinkPriority(address, priority);
+ } catch (RemoteException e) {
+ Log.w(TAG, "", e);
+ return BluetoothError.ERROR_IPC;
+ }
+ }
+
+ /**
+ * Get priority of a2dp sink.
+ * @param address Sink
+ * @return non-negative priority, or negative error code on error.
+ */
+ public int getSinkPriority(String address) {
+ try {
+ return mService.getSinkPriority(address);
+ } 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;
+ }
+ }
+
+ /** Helper for converting a state to a string.
+ * For debug use only - strings are not internationalized.
+ * @hide
+ */
+ public static String stateToString(int state) {
+ switch (state) {
+ case STATE_DISCONNECTED:
+ return "disconnected";
+ case STATE_CONNECTING:
+ return "connecting";
+ case STATE_CONNECTED:
+ return "connected";
+ case STATE_DISCONNECTING:
+ return "disconnecting";
+ case STATE_PLAYING:
+ return "playing";
+ default:
+ return "<unknown state " + state + ">";
+ }
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothAudioGateway.java b/core/java/android/bluetooth/BluetoothAudioGateway.java
new file mode 100644
index 0000000..f3afd2a
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothAudioGateway.java
@@ -0,0 +1,190 @@
+package android.bluetooth;
+
+import java.lang.Thread;
+
+import android.os.Message;
+import android.os.Handler;
+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.
+ *
+ * @hide
+ */
+public 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 */
+ private String mConnectingHeadsetAddress;
+ private int mConnectingHeadsetRfcommChannel; /* -1 when not connected */
+ private int mConnectingHeadsetSocketFd;
+ private String mConnectingHandsfreeAddress;
+ private int mConnectingHandsfreeRfcommChannel; /* -1 when not connected */
+ private int mConnectingHandsfreeSocketFd;
+ private int mTimeoutRemainingMs; /* in/out */
+
+ 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(BluetoothDevice bluetooth,
+ int handsfreeAgRfcommChannel,
+ int headsetAgRfcommChannel) {
+ mBluetooth = bluetooth;
+ mHandsfreeAgRfcommChannel = handsfreeAgRfcommChannel;
+ mHeadsetAgRfcommChannel = headsetAgRfcommChannel;
+ initializeNativeDataNative();
+ }
+
+ private Thread mConnectThead;
+ private volatile boolean mInterrupted;
+ private static final int SELECT_WAIT_TIMEOUT = 1000;
+
+ private Handler mCallback;
+
+ public class IncomingConnectionInfo {
+ IncomingConnectionInfo(BluetoothDevice bluetooth, String address, int socketFd,
+ int rfcommChan) {
+ mBluetooth = bluetooth;
+ mAddress = address;
+ 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;
+ public static final int MSG_INCOMING_HANDSFREE_CONNECTION = 101;
+
+ public synchronized boolean start(Handler callback) {
+
+ if (mConnectThead == null) {
+ mCallback = callback;
+ mConnectThead = new Thread(TAG) {
+ public void run() {
+ if (DBG) log("Connect Thread starting");
+ while (!mInterrupted) {
+ //Log.i(TAG, "waiting for connect");
+ mConnectingHeadsetRfcommChannel = -1;
+ mConnectingHandsfreeRfcommChannel = -1;
+ if (waitForHandsfreeConnectNative(SELECT_WAIT_TIMEOUT) == false) {
+ if (mTimeoutRemainingMs > 0) {
+ try {
+ Log.i(TAG, "select thread timed out, but " +
+ mTimeoutRemainingMs + "ms of waiting remain.");
+ Thread.sleep(mTimeoutRemainingMs);
+ } catch (InterruptedException e) {
+ Log.i(TAG, "select thread was interrupted (2), exiting");
+ mInterrupted = true;
+ }
+ }
+ }
+ else {
+ Log.i(TAG, "connect notification!");
+ /* A device connected (most likely just one, but
+ it is possible for two separate devices, one
+ a headset and one a handsfree, to connect
+ simultaneously.
+ */
+ if (mConnectingHeadsetRfcommChannel >= 0) {
+ Log.i(TAG, "Incoming connection from headset " +
+ mConnectingHeadsetAddress + " on channel " +
+ mConnectingHeadsetRfcommChannel);
+ Message msg = Message.obtain(mCallback);
+ msg.what = MSG_INCOMING_HEADSET_CONNECTION;
+ msg.obj =
+ new IncomingConnectionInfo(
+ mBluetooth,
+ mConnectingHeadsetAddress,
+ mConnectingHeadsetSocketFd,
+ mConnectingHeadsetRfcommChannel);
+ msg.sendToTarget();
+ }
+ if (mConnectingHandsfreeRfcommChannel >= 0) {
+ Log.i(TAG, "Incoming connection from handsfree " +
+ mConnectingHandsfreeAddress + " on channel " +
+ mConnectingHandsfreeRfcommChannel);
+ Message msg = Message.obtain();
+ msg.setTarget(mCallback);
+ msg.what = MSG_INCOMING_HANDSFREE_CONNECTION;
+ msg.obj =
+ new IncomingConnectionInfo(
+ mBluetooth,
+ mConnectingHandsfreeAddress,
+ mConnectingHandsfreeSocketFd,
+ mConnectingHandsfreeRfcommChannel);
+ msg.sendToTarget();
+ }
+ }
+ }
+ if (DBG) log("Connect Thread finished");
+ }
+ };
+
+ if (setUpListeningSocketsNative() == false) {
+ Log.e(TAG, "Could not set up listening socket, exiting");
+ return false;
+ }
+
+ mInterrupted = false;
+ mConnectThead.start();
+ }
+
+ return true;
+ }
+
+ public synchronized void stop() {
+ if (mConnectThead != null) {
+ if (DBG) log("stopping Connect Thread");
+ mInterrupted = true;
+ try {
+ mConnectThead.interrupt();
+ if (DBG) log("waiting for thread to terminate");
+ mConnectThead.join();
+ mConnectThead = null;
+ mCallback = null;
+ tearDownListeningSocketsNative();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Interrupted waiting for Connect Thread to join");
+ }
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNativeDataNative();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private static native void classInitNative();
+ private native void initializeNativeDataNative();
+ private native void cleanupNativeDataNative();
+ private native boolean waitForHandsfreeConnectNative(int timeoutMs);
+ private native boolean setUpListeningSocketsNative();
+ private native void tearDownListeningSocketsNative();
+
+ private static void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/core/java/android/bluetooth/BluetoothClass.java b/core/java/android/bluetooth/BluetoothClass.java
new file mode 100644
index 0000000..88ce18b
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothClass.java
@@ -0,0 +1,191 @@
+/*
+ * 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;
+
+/**
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Each of these helper methods takes the 32-bit integer class as an argument.
+ *
+ * @hide
+ */
+public class BluetoothClass {
+ /** Indicates the Bluetooth API could not retrieve the class */
+ 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;
+
+ public static final int LIMITED_DISCOVERABILITY = 0x002000;
+ public static final int POSITIONING = 0x010000;
+ public static final int NETWORKING = 0x020000;
+ public static final int RENDER = 0x040000;
+ public static final int CAPTURE = 0x080000;
+ public static final int OBJECT_TRANSFER = 0x100000;
+ 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);
+ }
+ }
+
+ /** 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. */
+ public static class Device {
+ public static final int BITMASK = 0x1FFC;
+
+ public static class Major {
+ public static final int BITMASK = 0x1F00;
+
+ public static final int MISC = 0x0000;
+ public static final int COMPUTER = 0x0100;
+ public static final int PHONE = 0x0200;
+ public static final int NETWORKING = 0x0300;
+ public static final int AUDIO_VIDEO = 0x0400;
+ public static final int PERIPHERAL = 0x0500;
+ public static final int IMAGING = 0x0600;
+ public static final int WEARABLE = 0x0700;
+ 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
+ public static final int COMPUTER_UNCATEGORIZED = 0x0100;
+ public static final int COMPUTER_DESKTOP = 0x0104;
+ public static final int COMPUTER_SERVER = 0x0108;
+ public static final int COMPUTER_LAPTOP = 0x010C;
+ public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110;
+ public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114;
+ public static final int COMPUTER_WEARABLE = 0x0118;
+
+ // Devices in the PHONE major class
+ public static final int PHONE_UNCATEGORIZED = 0x0200;
+ public static final int PHONE_CELLULAR = 0x0204;
+ public static final int PHONE_CORDLESS = 0x0208;
+ public static final int PHONE_SMART = 0x020C;
+ public static final int PHONE_MODEM_OR_GATEWAY = 0x0210;
+ public static final int PHONE_ISDN = 0x0214;
+
+ // Minor classes for the AUDIO_VIDEO major class
+ public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400;
+ public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404;
+ public static final int AUDIO_VIDEO_HANDSFREE = 0x0408;
+ //public static final int AUDIO_VIDEO_RESERVED = 0x040C;
+ public static final int AUDIO_VIDEO_MICROPHONE = 0x0410;
+ public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414;
+ public static final int AUDIO_VIDEO_HEADPHONES = 0x0418;
+ public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C;
+ public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420;
+ public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424;
+ public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428;
+ public static final int AUDIO_VIDEO_VCR = 0x042C;
+ public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430;
+ public static final int AUDIO_VIDEO_CAMCORDER = 0x0434;
+ public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438;
+ public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C;
+ public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440;
+ //public static final int AUDIO_VIDEO_RESERVED = 0x0444;
+ public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448;
+
+ // Devices in the WEARABLE major class
+ public static final int WEARABLE_UNCATEGORIZED = 0x0700;
+ public static final int WEARABLE_WRIST_WATCH = 0x0704;
+ public static final int WEARABLE_PAGER = 0x0708;
+ public static final int WEARABLE_JACKET = 0x070C;
+ public static final int WEARABLE_HELMET = 0x0710;
+ public static final int WEARABLE_GLASSES = 0x0714;
+
+ // Devices in the TOY major class
+ public static final int TOY_UNCATEGORIZED = 0x0800;
+ public static final int TOY_ROBOT = 0x0804;
+ public static final int TOY_VEHICLE = 0x0808;
+ public static final int TOY_DOLL_ACTION_FIGURE = 0x080C;
+ public static final int TOY_CONTROLLER = 0x0810;
+ public static final int TOY_GAME = 0x0814;
+
+ // Devices in the HEALTH major class
+ public static final int HEALTH_UNCATEGORIZED = 0x0900;
+ public static final int HEALTH_BLOOD_PRESSURE = 0x0904;
+ public static final int HEALTH_THERMOMETER = 0x0908;
+ public static final int HEALTH_WEIGHING = 0x090C;
+ public static final int HEALTH_GLUCOSE = 0x0910;
+ 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 (btClass & Device.BITMASK);
+ }
+ }
+}
+
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
new file mode 100644
index 0000000..1ba1c1e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -0,0 +1,580 @@
+/*
+ * 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.os.RemoteException;
+import android.util.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * 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 class BluetoothDevice {
+ /** 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;
+
+ 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.
+ */
+ public BluetoothDevice(IBluetoothDevice service) {
+ mService = service;
+ }
+
+ /**
+ * Get the current status of Bluetooth hardware.
+ *
+ * @return true if Bluetooth enabled, false otherwise.
+ */
+ public boolean isEnabled() {
+ try {
+ return mService.isEnabled();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Enable the Bluetooth device.
+ * Turn on the underlying hardware.
+ * This is an asynchronous call, BluetoothIntent.ENABLED_ACTION will be
+ * sent if and when the device is successfully 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.
+ */
+ public boolean enable() {
+ return enable(null);
+ }
+
+ /**
+ * Enable the Bluetooth device.
+ * Turns on the underlying hardware.
+ * This is an asynchronous call. onEnableResult() of your callback will be
+ * called when the call is complete, with either RESULT_SUCCESS or
+ * RESULT_FAILURE.
+ *
+ * Your callback will be called from a binder thread, not the main thread.
+ *
+ * In addition to the callback, BluetoothIntent.ENABLED_ACTION will be
+ * broadcast if the device is successfully enabled.
+ *
+ * @param callback Your callback, null is ok.
+ * @return true if your callback was successfully registered, or false if
+ * there was an error, implying your callback will never be called.
+ */
+ public boolean enable(IBluetoothDeviceCallback callback) {
+ try {
+ return mService.enable(callback);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Disable the Bluetooth device.
+ * This turns off the underlying hardware.
+ *
+ * @return true if successful, false otherwise.
+ */
+ public boolean disable() {
+ try {
+ return mService.disable();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ public String getAddress() {
+ try {
+ return mService.getAddress();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public String getName() {
+ try {
+ return mService.getName();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public boolean setName(String name) {
+ try {
+ return mService.setName(name);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ 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;
+ }
+
+ /**
+ * 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
+ */
+ public int getScanMode() {
+ try {
+ return mService.getScanMode();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return BluetoothError.ERROR_IPC;
+ }
+
+ /**
+ * Set the current scan mode.
+ * Used to make the local device connectable and/or discoverable
+ * @param scanMode One of SCAN_MODE_*
+ */
+ public void setScanMode(int scanMode) {
+ try {
+ mService.setScanMode(scanMode);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ 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);}
+ }
+
+ public boolean startDiscovery() {
+ return startDiscovery(true);
+ }
+ public boolean startDiscovery(boolean resolveNames) {
+ try {
+ return mService.startDiscovery(resolveNames);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ public void cancelDiscovery() {
+ try {
+ mService.cancelDiscovery();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ }
+
+ public boolean isDiscovering() {
+ try {
+ return mService.isDiscovering();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ public boolean startPeriodicDiscovery() {
+ try {
+ return mService.startPeriodicDiscovery();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+ 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 String[] listRemoteDevices() {
+ try {
+ return mService.listRemoteDevices();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ /**
+ * 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.
+ *
+ * In most cases you probably want to test if a higher level protocol is
+ * connected, rather than testing ACL connections.
+ *
+ * @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.
+ */
+ public String[] listAclConnections() {
+ try {
+ return mService.listAclConnections();
+ } 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.
+ *
+ * @param address the Bluetooth hardware address you want to check.
+ * @return true if there is an ACL connection, false otherwise and on
+ * error.
+ */
+ public boolean isAclConnected(String address) {
+ try {
+ return mService.isAclConnected(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Perform a low level (ACL) disconnection of a remote device.
+ *
+ * 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.
+ */
+ public boolean disconnectRemoteDeviceAcl(String address) {
+ try {
+ return mService.disconnectRemoteDeviceAcl(address);
+ } 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.
+ *
+ * @param address the remote device Bluetooth address.
+ * @return false If there was an immediate problem creating the bonding,
+ * true otherwise.
+ */
+ public boolean createBond(String address) {
+ try {
+ return mService.createBond(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Cancel an in-progress bonding request started with createBond.
+ */
+ public boolean cancelBondProcess(String address) {
+ try {
+ return mService.cancelBondProcess(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Remove an already exisiting bonding (delete the link key).
+ */
+ public boolean removeBond(String address) {
+ try {
+ return mService.removeBond(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * 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.
+ */
+ public String[] listBonds() {
+ try {
+ return mService.listBonds();
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ /**
+ * 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
+ */
+ public int getBondState(String address) {
+ try {
+ return mService.getBondState(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return BluetoothError.ERROR_IPC;
+ }
+
+ public String getRemoteName(String address) {
+ try {
+ return mService.getRemoteName(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ public String getRemoteVersion(String address) {
+ try {
+ return mService.getRemoteVersion(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+ public String getRemoteRevision(String address) {
+ try {
+ return mService.getRemoteRevision(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+ public String getRemoteManufacturer(String address) {
+ try {
+ return mService.getRemoteManufacturer(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+ public String getRemoteCompany(String address) {
+ try {
+ return mService.getRemoteCompany(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return null;
+ }
+
+ /**
+ * 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) {
+ try {
+ return mService.getRemoteServiceChannel(address, uuid16, callback);
+ } 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.
+ */
+ public int getRemoteClass(String address) {
+ try {
+ return mService.getRemoteClass(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return BluetoothClass.ERROR;
+ }
+
+ 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;
+ }
+
+ public boolean setPin(String address, byte[] pin) {
+ try {
+ return mService.setPin(address, pin);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+ public boolean cancelPin(String address) {
+ try {
+ return mService.cancelPin(address);
+ } catch (RemoteException e) {Log.e(TAG, "", e);}
+ return false;
+ }
+
+ /**
+ * Check that a pin is valid and convert to byte array.
+ *
+ * Bluetooth pin's are 1 to 16 bytes of UTF8 characters.
+ * @param pin pin as java String
+ * @return the pin code as a UTF8 byte array, or null if it is an invalid
+ * Bluetooth pin.
+ */
+ public static byte[] convertPinToBytes(String pin) {
+ if (pin == null) {
+ return null;
+ }
+ byte[] pinBytes;
+ try {
+ pinBytes = pin.getBytes("UTF8");
+ } catch (UnsupportedEncodingException uee) {
+ Log.e(TAG, "UTF8 not supported?!?"); // this should not happen
+ return null;
+ }
+ if (pinBytes.length <= 0 || pinBytes.length > 16) {
+ return null;
+ }
+ 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/BluetoothError.java b/core/java/android/bluetooth/BluetoothError.java
new file mode 100644
index 0000000..2554bea
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothError.java
@@ -0,0 +1,42 @@
+/*
+ * 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;
+
+/**
+ * Bluetooth API error codes.
+ *
+ * Errors are always negative.
+ *
+ * @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;
+
+}
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
new file mode 100644
index 0000000..34196bf
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -0,0 +1,353 @@
+/*
+ * 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.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Public API for controlling the Bluetooth Headset Service. This includes both
+ * Bluetooth Headset and Handsfree (v1.5) profiles. The Headset service will
+ * attempt a handsfree connection first, and fall back to headset.
+ *
+ * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * Service via IPC.
+ *
+ * Creating a BluetoothHeadset object will create a binding with the
+ * BluetoothHeadset service. Users of this object should call close() when they
+ * are finished with the BluetoothHeadset, so that this proxy object can unbind
+ * from the service.
+ *
+ * This BluetoothHeadset object is not immediately bound to the
+ * BluetoothHeadset 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 BluetootHeadset after construction.
+ *
+ * Android only supports one connected Bluetooth Headset at a time.
+ *
+ * @hide
+ */
+public class BluetoothHeadset {
+
+ private static final String TAG = "BluetoothHeadset";
+ private static final boolean DBG = false;
+
+ private IBluetoothHeadset 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 headset currently connected */
+ public static final int STATE_DISCONNECTED = 0;
+ /** Connection attempt in progress */
+ public static final int STATE_CONNECTING = 1;
+ /** A headset 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 completetion. */
+ public static final int RESULT_CANCELED = 2;
+
+ /** Default priority for headsets that should be auto-connected */
+ public static final int PRIORITY_AUTO = 100;
+ /** Default priority for headsets that should not be auto-connected */
+ public static final int PRIORITY_OFF = 0;
+
+ /**
+ * An interface for notifying BluetoothHeadset IPC clients when they have
+ * been connected to the BluetoothHeadset service.
+ */
+ public interface ServiceListener {
+ /**
+ * Called to notify the client when this proxy object has been
+ * connected to the BluetoothHeadset service. Clients must wait for
+ * this callback before making IPC calls on the BluetoothHeadset
+ * service.
+ */
+ public void onServiceConnected();
+
+ /**
+ * Called to notify the client that this proxy object has been
+ * disconnected from the BluetoothHeadset service. Clients must not
+ * make IPC calls on the BluetoothHeadset service after this callback.
+ * This callback will currently only occur if the application hosting
+ * the BluetoothHeadset service, but may be called more often in future.
+ */
+ public void onServiceDisconnected();
+ }
+
+ /**
+ * Create a BluetoothHeadset proxy object.
+ */
+ public BluetoothHeadset(Context context, ServiceListener l) {
+ mContext = context;
+ mServiceListener = l;
+ if (!context.bindService(new Intent(IBluetoothHeadset.class.getName()), mConnection, 0)) {
+ Log.e(TAG, "Could not bind to Bluetooth Headset Service");
+ }
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Close the connection to the backing service.
+ * Other public functions of BluetoothHeadset 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 Bluetooth Headset service.
+ * @return One of the STATE_ return codes, or STATE_ERROR if this proxy
+ * object is currently not connected to the Headset service.
+ */
+ public int 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.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return BluetoothHeadset.STATE_ERROR;
+ }
+
+ /**
+ * Get the Bluetooth address of the current headset.
+ * @return The Bluetooth address, 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 (mService != null) {
+ try {
+ return mService.getHeadsetAddress();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return null;
+ }
+
+ /**
+ * 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
+ * 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.
+ */
+ public boolean connectHeadset(String address) {
+ if (mService != null) {
+ try {
+ if (mService.connectHeadset(address)) {
+ return true;
+ }
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the specified headset is connected (does not include
+ * 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 (mService != null) {
+ try {
+ return mService.isConnected(address);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Disconnects the current headset. Currently this call blocks, it may soon
+ * be made asynchornous. Returns false if this proxy object is
+ * not currently connected to the Headset service.
+ */
+ public boolean disconnectHeadset() {
+ if (mService != null) {
+ try {
+ mService.disconnectHeadset();
+ return true;
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Start BT Voice Recognition mode, and set up Bluetooth audio path.
+ * Returns false if there is no headset connected, or if the
+ * connected headset does not support voice recognition, or on
+ * error.
+ */
+ public boolean startVoiceRecognition() {
+ if (mService != null) {
+ try {
+ return mService.startVoiceRecognition();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Stop BT Voice Recognition mode, and shut down Bluetooth audio path.
+ * Returns false if there is no headset connected, or the connected
+ * headset is not in voice recognition mode, or on error.
+ */
+ public boolean stopVoiceRecognition() {
+ if (mService != null) {
+ try {
+ return mService.stopVoiceRecognition();
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Set priority of headset.
+ * Priority is a non-negative integer. By default paired headsets will have
+ * a priority of PRIORITY_AUTO, and unpaired headset PRIORITY_NONE (0).
+ * Headsets with priority greater than zero will be auto-connected, and
+ * incoming connections will be accepted (if no other headset is
+ * connected).
+ * Auto-connection occurs at the following events: boot, incoming phone
+ * call, outgoing phone call.
+ * Headsets with priority equal to zero, or that are unpaired, are not
+ * auto-connected.
+ * Incoming connections are ignored regardless of priority if there is
+ * already a headset connected.
+ * @param address Paired headset
+ * @param priority Integer priority, for example PRIORITY_AUTO or
+ * PRIORITY_NONE
+ * @return True if successful, false if there was some error.
+ */
+ public boolean setPriority(String address, int priority) {
+ if (mService != null) {
+ try {
+ return mService.setPriority(address, priority);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ return false;
+ }
+
+ /**
+ * Get priority of headset.
+ * @param address Headset
+ * @return non-negative priority, or negative error code on error.
+ */
+ public int getPriority(String address) {
+ if (mService != null) {
+ try {
+ return mService.getPriority(address);
+ } catch (RemoteException e) {Log.e(TAG, e.toString());}
+ } else {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+ }
+ 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");
+ mService = IBluetoothHeadset.Stub.asInterface(service);
+ if (mServiceListener != null) {
+ mServiceListener.onServiceConnected();
+ }
+ }
+ public void onServiceDisconnected(ComponentName className) {
+ if (DBG) Log.d(TAG, "Proxy object disconnected");
+ mService = null;
+ if (mServiceListener != null) {
+ mServiceListener.onServiceDisconnected();
+ }
+ }
+ };
+}
diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java
new file mode 100644
index 0000000..b66b06e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothIntent.java
@@ -0,0 +1,128 @@
+/*
+ * 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 HEADSET_STATE =
+ "android.bluetooth.intent.HEADSET_STATE";
+ public static final String HEADSET_PREVIOUS_STATE =
+ "android.bluetooth.intent.HEADSET_PREVIOUS_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";
+
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ENABLED_ACTION =
+ "android.bluetooth.intent.action.ENABLED";
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String DISABLED_ACTION =
+ "android.bluetooth.intent.action.DISABLED";
+
+ @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";
+
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String HEADSET_STATE_CHANGED_ACTION =
+ "android.bluetooth.intent.action.HEADSET_STATE_CHANGED";
+}
diff --git a/core/java/android/bluetooth/Database.java b/core/java/android/bluetooth/Database.java
new file mode 100644
index 0000000..fef641a
--- /dev/null
+++ b/core/java/android/bluetooth/Database.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2007 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.bluetooth.RfcommSocket;
+
+import android.util.Log;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * A low-level API to the Service Discovery Protocol (SDP) Database.
+ *
+ * Allows service records to be added to the local SDP database. Once added,
+ * these services will be advertised to remote devices when they make SDP
+ * queries on this device.
+ *
+ * Currently this API is a thin wrapper to the bluez SDP Database API. See:
+ * http://wiki.bluez.org/wiki/Database
+ * http://wiki.bluez.org/wiki/HOWTO/ManagingServiceRecords
+ * @hide
+ */
+public final class Database {
+ private static Database mInstance;
+
+ private static final String sLogName = "android.bluetooth.Database";
+
+ /**
+ * Class load time initialization
+ */
+ static {
+ classInitNative();
+ }
+ private native static void classInitNative();
+
+ /**
+ * Private to enforce singleton property
+ */
+ private Database() {
+ initializeNativeDataNative();
+ }
+ private native void initializeNativeDataNative();
+
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNativeDataNative();
+ } finally {
+ super.finalize();
+ }
+ }
+ private native void cleanupNativeDataNative();
+
+ /**
+ * Singelton accessor
+ * @return The singleton instance of Database
+ */
+ public static synchronized Database getInstance() {
+ if (mInstance == null) {
+ mInstance = new Database();
+ }
+ return mInstance;
+ }
+
+ /**
+ * Advertise a service with an RfcommSocket.
+ *
+ * This adds the service the SDP Database with the following attributes
+ * set: Service Name, Protocol Descriptor List, Service Class ID List
+ * TODO: Construct a byte[] record directly, rather than via XML.
+ * @param socket The rfcomm socket to advertise (by channel).
+ * @param serviceName A short name for this service
+ * @param uuid
+ * Unique identifier for this service, by which clients
+ * can search for your service
+ * @return Handle to the new service record
+ */
+ public int advertiseRfcommService(RfcommSocket socket,
+ String serviceName,
+ UUID uuid) throws IOException {
+ String xmlRecord =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
+ "<record>\n" +
+ " <attribute id=\"0x0001\">\n" + // ServiceClassIDList
+ " <sequence>\n" +
+ " <uuid value=\""
+ + uuid.toString() + // UUID for this service
+ "\"/>\n" +
+ " </sequence>\n" +
+ " </attribute>\n" +
+ " <attribute id=\"0x0004\">\n" + // ProtocolDescriptorList
+ " <sequence>\n" +
+ " <sequence>\n" +
+ " <uuid value=\"0x0100\"/>\n" + // L2CAP
+ " </sequence>\n" +
+ " <sequence>\n" +
+ " <uuid value=\"0x0003\"/>\n" + // RFCOMM
+ " <uint8 value=\"" +
+ socket.getPort() + // RFCOMM port
+ "\" name=\"channel\"/>\n" +
+ " </sequence>\n" +
+ " </sequence>\n" +
+ " </attribute>\n" +
+ " <attribute id=\"0x0100\">\n" + // ServiceName
+ " <text value=\"" + serviceName + "\"/>\n" +
+ " </attribute>\n" +
+ "</record>\n";
+ Log.i(sLogName, xmlRecord);
+ return addServiceRecordFromXml(xmlRecord);
+ }
+
+
+ /**
+ * Add a new service record.
+ * @param record The byte[] record
+ * @return A handle to the new record
+ */
+ public synchronized int addServiceRecord(byte[] record) throws IOException {
+ int handle = addServiceRecordNative(record);
+ Log.i(sLogName, "Added SDP record: " + Integer.toHexString(handle));
+ return handle;
+ }
+ private native int addServiceRecordNative(byte[] record)
+ throws IOException;
+
+ /**
+ * Add a new service record, using XML.
+ * @param record The record as an XML string
+ * @return A handle to the new record
+ */
+ public synchronized int addServiceRecordFromXml(String record) throws IOException {
+ int handle = addServiceRecordFromXmlNative(record);
+ Log.i(sLogName, "Added SDP record: " + Integer.toHexString(handle));
+ return handle;
+ }
+ private native int addServiceRecordFromXmlNative(String record)
+ throws IOException;
+
+ /**
+ * Update an exisiting service record.
+ * @param handle Handle to exisiting record
+ * @param record The updated byte[] record
+ */
+ public synchronized void updateServiceRecord(int handle, byte[] record) {
+ try {
+ updateServiceRecordNative(handle, record);
+ } catch (IOException e) {
+ Log.e(getClass().toString(), e.getMessage());
+ }
+ }
+ private native void updateServiceRecordNative(int handle, byte[] record)
+ throws IOException;
+
+ /**
+ * Update an exisiting record, using XML.
+ * @param handle Handle to exisiting record
+ * @param record The record as an XML string.
+ */
+ public synchronized void updateServiceRecordFromXml(int handle, String record) {
+ try {
+ updateServiceRecordFromXmlNative(handle, record);
+ } catch (IOException e) {
+ Log.e(getClass().toString(), e.getMessage());
+ }
+ }
+ private native void updateServiceRecordFromXmlNative(int handle, String record)
+ throws IOException;
+
+ /**
+ * Remove a service record.
+ * It is only possible to remove service records that were added by the
+ * current connection.
+ * @param handle Handle to exisiting record to be removed
+ */
+ public synchronized void removeServiceRecord(int handle) {
+ try {
+ removeServiceRecordNative(handle);
+ } catch (IOException e) {
+ Log.e(getClass().toString(), e.getMessage());
+ }
+ }
+ private native void removeServiceRecordNative(int handle) throws IOException;
+}
diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java
new file mode 100644
index 0000000..fd2d2ab
--- /dev/null
+++ b/core/java/android/bluetooth/HeadsetBase.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2007 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.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * The base RFCOMM (service) connection for a headset or handsfree device.
+ *
+ * In the future this class will be removed.
+ *
+ * @hide
+ */
+public class HeadsetBase {
+ private static final String TAG = "Bluetooth HeadsetBase";
+ private static final boolean DBG = false;
+
+ public static final int RFCOMM_DISCONNECTED = 1;
+
+ public static final int DIRECTION_INCOMING = 1;
+ public static final int DIRECTION_OUTGOING = 2;
+
+ private final BluetoothDevice mBluetooth;
+ private final String mAddress;
+ private final int mRfcommChannel;
+ private int mNativeData;
+ private Thread mEventThread;
+ private volatile boolean mEventThreadInterrupted;
+ private Handler mEventThreadHandler;
+ private int mTimeoutRemainingMs;
+ private final int mDirection;
+ private final long mConnectTimestamp;
+
+ protected AtParser mAtParser;
+
+ private WakeLock mWakeLock; // held while processing an AT command
+
+ private native static void classInitNative();
+ static {
+ classInitNative();
+ }
+
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNativeDataNative();
+ releaseWakeLock();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native void cleanupNativeDataNative();
+
+ public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address,
+ int rfcommChannel) {
+ mDirection = DIRECTION_OUTGOING;
+ mConnectTimestamp = System.currentTimeMillis();
+ mBluetooth = bluetooth;
+ mAddress = address;
+ mRfcommChannel = rfcommChannel;
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
+ mWakeLock.setReferenceCounted(false);
+ initializeAtParser();
+ // Must be called after this.mAddress is set.
+ initializeNativeDataNative(-1);
+ }
+
+ /* Create from an already exisiting rfcomm connection */
+ public HeadsetBase(PowerManager pm, BluetoothDevice bluetooth, String address, int socketFd,
+ int rfcommChannel, Handler handler) {
+ mDirection = DIRECTION_INCOMING;
+ mConnectTimestamp = System.currentTimeMillis();
+ mBluetooth = bluetooth;
+ mAddress = address;
+ mRfcommChannel = rfcommChannel;
+ mEventThreadHandler = handler;
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
+ mWakeLock.setReferenceCounted(false);
+ initializeAtParser();
+ // Must be called after this.mAddress is set.
+ initializeNativeDataNative(socketFd);
+ }
+
+ private native void initializeNativeDataNative(int socketFd);
+
+ /* Process an incoming AT command line
+ */
+ protected synchronized void handleInput(String input) {
+ acquireWakeLock();
+ long timestamp;
+
+ if (DBG) timestamp = System.currentTimeMillis();
+ AtCommandResult result = mAtParser.process(input);
+ if (DBG) Log.d(TAG, "Processing " + input + " took " +
+ (System.currentTimeMillis() - timestamp) + " ms");
+
+ if (result.getResultCode() == AtCommandResult.ERROR) {
+ Log.i(TAG, "Error pocessing <" + input + ">");
+ }
+
+ sendURC(result.toString());
+
+ releaseWakeLock();
+ }
+
+ /**
+ * Register AT commands that are common to all Headset / Handsets. This
+ * function is called by the HeadsetBase constructor.
+ */
+ protected void initializeAtParser() {
+ mAtParser = new AtParser();
+ //TODO(): Get rid of this as there are no parsers registered. But because of dependencies,
+ //it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
+ }
+
+ public AtParser getAtParser() {
+ return mAtParser;
+ }
+
+ public void startEventThread() {
+ mEventThread =
+ new Thread("HeadsetBase Event Thread") {
+ public void run() {
+ int last_read_error;
+ while (!mEventThreadInterrupted) {
+ String input = readNative(500);
+ if (input != null) {
+ handleInput(input);
+ }
+ else {
+ last_read_error = getLastReadStatusNative();
+ if (last_read_error != 0) {
+ Log.i(TAG, "headset read error " + last_read_error);
+ if (mEventThreadHandler != null) {
+ mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
+ .sendToTarget();
+ }
+ disconnectNative();
+ break;
+ }
+ }
+ }
+ }
+ };
+ mEventThreadInterrupted = false;
+ mEventThread.start();
+ }
+
+
+
+ private native String readNative(int timeout_ms);
+ private native int getLastReadStatusNative();
+
+ private void stopEventThread() {
+ mEventThreadInterrupted = true;
+ mEventThread.interrupt();
+ try {
+ mEventThread.join();
+ } catch (java.lang.InterruptedException e) {
+ // FIXME: handle this,
+ }
+ mEventThread = null;
+ }
+
+ public boolean connect(Handler handler) {
+ if (mEventThread == null) {
+ if (!connectNative()) return false;
+ mEventThreadHandler = handler;
+ }
+ return true;
+ }
+ private native boolean connectNative();
+
+ /*
+ * Returns true when either the asynchronous connect is in progress, or
+ * the connect is complete. Call waitForAsyncConnect() to find out whether
+ * the connect is actually complete, or disconnect() to cancel.
+ */
+
+ public boolean connectAsync() {
+ return connectAsyncNative();
+ }
+ private native boolean connectAsyncNative();
+
+ public int getRemainingAsyncConnectWaitingTimeMs() {
+ return mTimeoutRemainingMs;
+ }
+
+ /*
+ * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
+ * error. On error, handler will be called, and you need to re-initiate
+ * the async connect.
+ */
+ public int waitForAsyncConnect(int timeout_ms, Handler handler) {
+ int res = waitForAsyncConnectNative(timeout_ms);
+ if (res > 0) {
+ mEventThreadHandler = handler;
+ }
+ return res;
+ }
+ private native int waitForAsyncConnectNative(int timeout_ms);
+
+ public void disconnect() {
+ if (mEventThread != null) {
+ stopEventThread();
+ }
+ disconnectNative();
+ }
+ private native void disconnectNative();
+
+
+ /*
+ * Note that if a remote side disconnects, this method will still return
+ * true until disconnect() is called. You know when a remote side
+ * disconnects because you will receive the intent
+ * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION. If, when you get
+ * this intent, method isConnected() returns true, you know that the
+ * disconnect was initiated by the remote device.
+ */
+
+ public boolean isConnected() {
+ return mEventThread != null;
+ }
+
+ public String getAddress() {
+ return mAddress;
+ }
+
+ public String getName() {
+ return mBluetooth.getRemoteName(mAddress);
+ }
+
+ public int getDirection() {
+ return mDirection;
+ }
+
+ public long getConnectTimestamp() {
+ return mConnectTimestamp;
+ }
+
+ public synchronized boolean sendURC(String urc) {
+ if (urc.length() > 0) {
+ boolean ret = sendURCNative(urc);
+ return ret;
+ }
+ return true;
+ }
+ private native boolean sendURCNative(String urc);
+
+ private void acquireWakeLock() {
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ }
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock.isHeld()) {
+ mWakeLock.release();
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl
new file mode 100644
index 0000000..55ff27f
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+/**
+ * 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);
+}
diff --git a/core/java/android/bluetooth/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl
new file mode 100644
index 0000000..4351d2e
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDevice.aidl
@@ -0,0 +1,77 @@
+/*
+ * 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.bluetooth.IBluetoothDeviceCallback;
+
+/**
+ * System private API for talking with the Bluetooth service.
+ *
+ * {@hide}
+ */
+interface IBluetoothDevice
+{
+ boolean isEnabled();
+ boolean enable(in IBluetoothDeviceCallback callback); // async
+ boolean disable();
+
+ String getAddress();
+ String getName();
+ boolean setName(in String name);
+ String getVersion();
+ String getRevision();
+ String getManufacturer();
+ String getCompany();
+
+ int getScanMode();
+ boolean setScanMode(int mode);
+
+ int getDiscoverableTimeout();
+ boolean setDiscoverableTimeout(int timeout);
+
+ boolean startDiscovery(boolean resolveNames);
+ 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);
+ boolean removeBond(in String address);
+ String[] listBonds();
+ 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);
+
+ boolean setPin(in String address, in byte[] pin);
+ boolean cancelPin(in String address);
+}
diff --git a/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl
new file mode 100644
index 0000000..d25bd56
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * {@hide}
+ */
+oneway interface IBluetoothDeviceCallback
+{
+ void onGetRemoteServiceChannelResult(in String address, int channel);
+
+ void onEnableResult(int result);
+}
diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl
new file mode 100644
index 0000000..582d4e3
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+/**
+ * System private API for Bluetooth Headset service
+ *
+ * {@hide}
+ */
+interface IBluetoothHeadset {
+ int getState();
+ String getHeadsetAddress();
+ boolean connectHeadset(in String address);
+ void disconnectHeadset();
+ boolean isConnected(in String address);
+ boolean startVoiceRecognition();
+ boolean stopVoiceRecognition();
+ boolean setPriority(in String address, int priority);
+ int getPriority(in String address);
+}
diff --git a/core/java/android/bluetooth/RfcommSocket.java b/core/java/android/bluetooth/RfcommSocket.java
new file mode 100644
index 0000000..a33263f
--- /dev/null
+++ b/core/java/android/bluetooth/RfcommSocket.java
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2007 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 java.io.IOException;
+import java.io.FileOutputStream;
+import java.io.FileInputStream;
+import java.io.OutputStream;
+import java.io.InputStream;
+import java.io.FileDescriptor;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * This class implements an API to the Bluetooth RFCOMM layer. An RFCOMM socket
+ * is similar to a normal socket in that it takes an address and a port number.
+ * The difference is of course that the address is a Bluetooth-device address,
+ * and the port number is an RFCOMM channel. The API allows for the
+ * establishment of listening sockets via methods
+ * {@link #bind(String, int) bind}, {@link #listen(int) listen}, and
+ * {@link #accept(RfcommSocket, int) accept}, as well as for the making of
+ * outgoing connections with {@link #connect(String, int) connect},
+ * {@link #connectAsync(String, int) connectAsync}, and
+ * {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
+ *
+ * After constructing a socket, you need to {@link #create() create} it and then
+ * {@link #destroy() destroy} it when you are done using it. Both
+ * {@link #create() create} and {@link #accept(RfcommSocket, int) accept} return
+ * a {@link java.io.FileDescriptor FileDescriptor} for the actual data.
+ * Alternatively, you may call {@link #getInputStream() getInputStream} and
+ * {@link #getOutputStream() getOutputStream} to retrieve the respective streams
+ * without going through the FileDescriptor.
+ *
+ * @hide
+ */
+public class RfcommSocket {
+
+ /**
+ * Used by the native implementation of the class.
+ */
+ private int mNativeData;
+
+ /**
+ * Used by the native implementation of the class.
+ */
+ private int mPort;
+
+ /**
+ * Used by the native implementation of the class.
+ */
+ private String mAddress;
+
+ /**
+ * We save the return value of {@link #create() create} and
+ * {@link #accept(RfcommSocket,int) accept} in this variable, and use it to
+ * retrieve the I/O streams.
+ */
+ private FileDescriptor mFd;
+
+ /**
+ * After a call to {@link #waitForAsyncConnect(int) waitForAsyncConnect},
+ * if the return value is zero, then, the the remaining time left to wait is
+ * written into this variable (by the native implementation). It is possible
+ * that {@link #waitForAsyncConnect(int) waitForAsyncConnect} returns before
+ * the user-specified timeout expires, which is why we save the remaining
+ * time in this member variable for the user to retrieve by calling method
+ * {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}.
+ */
+ private int mTimeoutRemainingMs;
+
+ /**
+ * Set to true when an asynchronous (nonblocking) connect is in progress.
+ * {@see #connectAsync(String,int)}.
+ */
+ private boolean mIsConnecting;
+
+ /**
+ * Set to true after a successful call to {@link #bind(String,int) bind} and
+ * used for error checking in {@link #listen(int) listen}. Reset to false
+ * on {@link #destroy() destroy}.
+ */
+ private boolean mIsBound = false;
+
+ /**
+ * Set to true after a successful call to {@link #listen(int) listen} and
+ * used for error checking in {@link #accept(RfcommSocket,int) accept}.
+ * Reset to false on {@link #destroy() destroy}.
+ */
+ private boolean mIsListening = false;
+
+ /**
+ * Used to store the remaining time after an accept with a non-negative
+ * timeout returns unsuccessfully. It is possible that a blocking
+ * {@link #accept(int) accept} may wait for less than the time specified by
+ * the user, which is why we store the remainder in this member variable for
+ * it to be retrieved with method
+ * {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}.
+ */
+ private int mAcceptTimeoutRemainingMs;
+
+ /**
+ * Maintained by {@link #getInputStream() getInputStream}.
+ */
+ protected FileInputStream mInputStream;
+
+ /**
+ * Maintained by {@link #getOutputStream() getOutputStream}.
+ */
+ protected FileOutputStream mOutputStream;
+
+ private native void initializeNativeDataNative();
+
+ /**
+ * Constructor.
+ */
+ public RfcommSocket() {
+ initializeNativeDataNative();
+ }
+
+ private native void cleanupNativeDataNative();
+
+ /**
+ * Called by the GC to clean up the native data that we set up when we
+ * construct the object.
+ */
+ protected void finalize() throws Throwable {
+ try {
+ cleanupNativeDataNative();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ private native static void classInitNative();
+
+ static {
+ classInitNative();
+ }
+
+ /**
+ * Creates a socket. You need to call this method before performing any
+ * other operation on a socket.
+ *
+ * @return FileDescriptor for the data stream.
+ * @throws IOException
+ * @see #destroy()
+ */
+ public FileDescriptor create() throws IOException {
+ if (mFd == null) {
+ mFd = createNative();
+ }
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ return mFd;
+ }
+
+ private native FileDescriptor createNative();
+
+ /**
+ * Destroys a socket created by {@link #create() create}. Call this
+ * function when you no longer use the socket in order to release the
+ * underlying OS resources.
+ *
+ * @see #create()
+ */
+ public void destroy() {
+ synchronized (this) {
+ destroyNative();
+ mFd = null;
+ mIsBound = false;
+ mIsListening = false;
+ }
+ }
+
+ private native void destroyNative();
+
+ /**
+ * Returns the {@link java.io.FileDescriptor FileDescriptor} of the socket.
+ *
+ * @return the FileDescriptor
+ * @throws IOException
+ * when the socket has not been {@link #create() created}.
+ */
+ public FileDescriptor getFileDescriptor() throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ return mFd;
+ }
+
+ /**
+ * Retrieves the input stream from the socket. Alternatively, you can do
+ * that from the FileDescriptor returned by {@link #create() create} or
+ * {@link #accept(RfcommSocket, int) accept}.
+ *
+ * @return InputStream
+ * @throws IOException
+ * if you have not called {@link #create() create} on the
+ * socket.
+ */
+ public InputStream getInputStream() throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+
+ synchronized (this) {
+ if (mInputStream == null) {
+ mInputStream = new FileInputStream(mFd);
+ }
+
+ return mInputStream;
+ }
+ }
+
+ /**
+ * Retrieves the output stream from the socket. Alternatively, you can do
+ * that from the FileDescriptor returned by {@link #create() create} or
+ * {@link #accept(RfcommSocket, int) accept}.
+ *
+ * @return OutputStream
+ * @throws IOException
+ * if you have not called {@link #create() create} on the
+ * socket.
+ */
+ public OutputStream getOutputStream() throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+
+ synchronized (this) {
+ if (mOutputStream == null) {
+ mOutputStream = new FileOutputStream(mFd);
+ }
+
+ return mOutputStream;
+ }
+ }
+
+ /**
+ * Starts a blocking connect to a remote RFCOMM socket. It takes the address
+ * of a device and the RFCOMM channel (port) to which to connect.
+ *
+ * @param address
+ * is the Bluetooth address of the remote device.
+ * @param port
+ * is the RFCOMM channel
+ * @return true on success, false on failure
+ * @throws IOException
+ * if {@link #create() create} has not been called.
+ * @see #connectAsync(String, int)
+ */
+ public boolean connect(String address, int port) throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ return connectNative(address, port);
+ }
+ }
+
+ private native boolean connectNative(String address, int port);
+
+ /**
+ * Starts an asynchronous (nonblocking) connect to a remote RFCOMM socket.
+ * It takes the address of the device to connect to, as well as the RFCOMM
+ * channel (port). On successful return (return value is true), you need to
+ * call method {@link #waitForAsyncConnect(int) waitForAsyncConnect} to
+ * block for up to a specified number of milliseconds while waiting for the
+ * asyncronous connect to complete.
+ *
+ * @param address
+ * of remote device
+ * @param port
+ * the RFCOMM channel
+ * @return true when the asynchronous connect has successfully started,
+ * false if there was an error.
+ * @throws IOException
+ * is you have not called {@link #create() create}
+ * @see #waitForAsyncConnect(int)
+ * @see #getRemainingAsyncConnectWaitingTimeMs()
+ * @see #connect(String, int)
+ */
+ public boolean connectAsync(String address, int port) throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ mIsConnecting = connectAsyncNative(address, port);
+ return mIsConnecting;
+ }
+ }
+
+ private native boolean connectAsyncNative(String address, int port);
+
+ /**
+ * Interrupts an asynchronous connect in progress. This method does nothing
+ * when there is no asynchronous connect in progress.
+ *
+ * @throws IOException
+ * if you have not called {@link #create() create}.
+ * @see #connectAsync(String, int)
+ */
+ public void interruptAsyncConnect() throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ if (mIsConnecting) {
+ mIsConnecting = !interruptAsyncConnectNative();
+ }
+ }
+ }
+
+ private native boolean interruptAsyncConnectNative();
+
+ /**
+ * Tells you whether there is an asynchronous connect in progress. This
+ * method returns an undefined value when there is a synchronous connect in
+ * progress.
+ *
+ * @return true if there is an asyc connect in progress, false otherwise
+ * @see #connectAsync(String, int)
+ */
+ public boolean isConnecting() {
+ return mIsConnecting;
+ }
+
+ /**
+ * Blocks for a specified amount of milliseconds while waiting for an
+ * asynchronous connect to complete. Returns an integer value to indicate
+ * one of the following: the connect succeeded, the connect is still in
+ * progress, or the connect failed. It is possible for this method to block
+ * for less than the time specified by the user, and still return zero
+ * (i.e., async connect is still in progress.) For this reason, if the
+ * return value is zero, you need to call method
+ * {@link #getRemainingAsyncConnectWaitingTimeMs() getRemainingAsyncConnectWaitingTimeMs}
+ * to retrieve the remaining time.
+ *
+ * @param timeoutMs
+ * the time to block while waiting for the async connect to
+ * complete.
+ * @return a positive value if the connect succeeds; zero, if the connect is
+ * still in progress, and a negative value if the connect failed.
+ *
+ * @throws IOException
+ * @see #getRemainingAsyncConnectWaitingTimeMs()
+ * @see #connectAsync(String, int)
+ */
+ public int waitForAsyncConnect(int timeoutMs) throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ int ret = waitForAsyncConnectNative(timeoutMs);
+ if (ret != 0) {
+ mIsConnecting = false;
+ }
+ return ret;
+ }
+ }
+
+ private native int waitForAsyncConnectNative(int timeoutMs);
+
+ /**
+ * Returns the number of milliseconds left to wait after the last call to
+ * {@link #waitForAsyncConnect(int) waitForAsyncConnect}.
+ *
+ * It is possible that waitForAsyncConnect() waits for less than the time
+ * specified by the user, and still returns zero (i.e., async connect is
+ * still in progress.) For this reason, if the return value is zero, you
+ * need to call this method to retrieve the remaining time before you call
+ * waitForAsyncConnect again.
+ *
+ * @return the remaining timeout in milliseconds.
+ * @see #waitForAsyncConnect(int)
+ * @see #connectAsync(String, int)
+ */
+ public int getRemainingAsyncConnectWaitingTimeMs() {
+ return mTimeoutRemainingMs;
+ }
+
+ /**
+ * Shuts down both directions on a socket.
+ *
+ * @return true on success, false on failure; if the return value is false,
+ * the socket might be left in a patially shut-down state (i.e. one
+ * direction is shut down, but the other is still open.) In this
+ * case, you should {@link #destroy() destroy} and then
+ * {@link #create() create} the socket again.
+ * @throws IOException
+ * is you have not caled {@link #create() create}.
+ * @see #shutdownInput()
+ * @see #shutdownOutput()
+ */
+ public boolean shutdown() throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ if (shutdownNative(true)) {
+ return shutdownNative(false);
+ }
+
+ return false;
+ }
+ }
+
+ /**
+ * Shuts down the input stream of the socket, but leaves the output stream
+ * in its current state.
+ *
+ * @return true on success, false on failure
+ * @throws IOException
+ * is you have not called {@link #create() create}
+ * @see #shutdown()
+ * @see #shutdownOutput()
+ */
+ public boolean shutdownInput() throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ return shutdownNative(true);
+ }
+ }
+
+ /**
+ * Shut down the output stream of the socket, but leaves the input stream in
+ * its current state.
+ *
+ * @return true on success, false on failure
+ * @throws IOException
+ * is you have not called {@link #create() create}
+ * @see #shutdown()
+ * @see #shutdownInput()
+ */
+ public boolean shutdownOutput() throws IOException {
+ synchronized (this) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ return shutdownNative(false);
+ }
+ }
+
+ private native boolean shutdownNative(boolean shutdownInput);
+
+ /**
+ * Tells you whether a socket is connected to another socket. This could be
+ * for input or output or both.
+ *
+ * @return true if connected, false otherwise.
+ * @see #isInputConnected()
+ * @see #isOutputConnected()
+ */
+ public boolean isConnected() {
+ return isConnectedNative() > 0;
+ }
+
+ /**
+ * Determines whether input is connected (i.e., whether you can receive data
+ * on this socket.)
+ *
+ * @return true if input is connected, false otherwise.
+ * @see #isConnected()
+ * @see #isOutputConnected()
+ */
+ public boolean isInputConnected() {
+ return (isConnectedNative() & 1) != 0;
+ }
+
+ /**
+ * Determines whether output is connected (i.e., whether you can send data
+ * on this socket.)
+ *
+ * @return true if output is connected, false otherwise.
+ * @see #isConnected()
+ * @see #isInputConnected()
+ */
+ public boolean isOutputConnected() {
+ return (isConnectedNative() & 2) != 0;
+ }
+
+ private native int isConnectedNative();
+
+ /**
+ * Binds a listening socket to the local device, or a non-listening socket
+ * to a remote device. The port is automatically selected as the first
+ * available port in the range 12 to 30.
+ *
+ * NOTE: Currently we ignore the device parameter and always bind the socket
+ * to the local device, assuming that it is a listening socket.
+ *
+ * TODO: Use bind(0) in native code to have the kernel select an unused
+ * port.
+ *
+ * @param device
+ * Bluetooth address of device to bind to (currently ignored).
+ * @return true on success, false on failure
+ * @throws IOException
+ * if you have not called {@link #create() create}
+ * @see #listen(int)
+ * @see #accept(RfcommSocket,int)
+ */
+ public boolean bind(String device) throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ for (int port = 12; port <= 30; port++) {
+ if (bindNative(device, port)) {
+ mIsBound = true;
+ return true;
+ }
+ }
+ mIsBound = false;
+ return false;
+ }
+
+ /**
+ * Binds a listening socket to the local device, or a non-listening socket
+ * to a remote device.
+ *
+ * NOTE: Currently we ignore the device parameter and always bind the socket
+ * to the local device, assuming that it is a listening socket.
+ *
+ * @param device
+ * Bluetooth address of device to bind to (currently ignored).
+ * @param port
+ * RFCOMM channel to bind socket to.
+ * @return true on success, false on failure
+ * @throws IOException
+ * if you have not called {@link #create() create}
+ * @see #listen(int)
+ * @see #accept(RfcommSocket,int)
+ */
+ public boolean bind(String device, int port) throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ mIsBound = bindNative(device, port);
+ return mIsBound;
+ }
+
+ private native boolean bindNative(String device, int port);
+
+ /**
+ * Starts listening for incoming connections on this socket, after it has
+ * been bound to an address and RFCOMM channel with
+ * {@link #bind(String,int) bind}.
+ *
+ * @param backlog
+ * the number of pending incoming connections to queue for
+ * {@link #accept(RfcommSocket, int) accept}.
+ * @return true on success, false on failure
+ * @throws IOException
+ * if you have not called {@link #create() create} or if the
+ * socket has not been bound to a device and RFCOMM channel.
+ */
+ public boolean listen(int backlog) throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ if (!mIsBound) {
+ throw new IOException("socket not bound");
+ }
+ mIsListening = listenNative(backlog);
+ return mIsListening;
+ }
+
+ private native boolean listenNative(int backlog);
+
+ /**
+ * Accepts incoming-connection requests for a listening socket bound to an
+ * RFCOMM channel. The user may provide a time to wait for an incoming
+ * connection.
+ *
+ * Note that this method may return null (i.e., no incoming connection)
+ * before the user-specified timeout expires. For this reason, on a null
+ * return value, you need to call
+ * {@link #getRemainingAcceptWaitingTimeMs() getRemainingAcceptWaitingTimeMs}
+ * in order to see how much time is left to wait, before you call this
+ * method again.
+ *
+ * @param newSock
+ * is set to the new socket that is created as a result of a
+ * successful accept.
+ * @param timeoutMs
+ * time (in milliseconds) to block while waiting to an
+ * incoming-connection request. A negative value is an infinite
+ * wait.
+ * @return FileDescriptor of newSock on success, null on failure. Failure
+ * occurs if the timeout expires without a successful connect.
+ * @throws IOException
+ * if the socket has not been {@link #create() create}ed, is
+ * not bound, or is not a listening socket.
+ * @see #bind(String, int)
+ * @see #listen(int)
+ * @see #getRemainingAcceptWaitingTimeMs()
+ */
+ public FileDescriptor accept(RfcommSocket newSock, int timeoutMs)
+ throws IOException {
+ synchronized (newSock) {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ if (mIsListening == false) {
+ throw new IOException("not listening on socket");
+ }
+ newSock.mFd = acceptNative(newSock, timeoutMs);
+ return newSock.mFd;
+ }
+ }
+
+ /**
+ * Returns the number of milliseconds left to wait after the last call to
+ * {@link #accept(RfcommSocket, int) accept}.
+ *
+ * Since accept() may return null (i.e., no incoming connection) before the
+ * user-specified timeout expires, you need to call this method in order to
+ * see how much time is left to wait, and wait for that amount of time
+ * before you call accept again.
+ *
+ * @return the remaining time, in milliseconds.
+ */
+ public int getRemainingAcceptWaitingTimeMs() {
+ return mAcceptTimeoutRemainingMs;
+ }
+
+ private native FileDescriptor acceptNative(RfcommSocket newSock,
+ int timeoutMs);
+
+ /**
+ * Get the port (rfcomm channel) associated with this socket.
+ *
+ * This is only valid if the port has been set via a successful call to
+ * {@link #bind(String, int)}, {@link #connect(String, int)}
+ * or {@link #connectAsync(String, int)}. This can be checked
+ * with {@link #isListening()} and {@link #isConnected()}.
+ * @return Port (rfcomm channel)
+ */
+ public int getPort() throws IOException {
+ if (mFd == null) {
+ throw new IOException("socket not created");
+ }
+ if (!mIsListening && !isConnected()) {
+ throw new IOException("not listening or connected on socket");
+ }
+ return mPort;
+ }
+
+ /**
+ * Return true if this socket is listening ({@link #listen(int)}
+ * has been called successfully).
+ */
+ public boolean isListening() {
+ return mIsListening;
+ }
+}
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
new file mode 100644
index 0000000..a43a08b
--- /dev/null
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -0,0 +1,194 @@
+/*
+ * 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.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.util.Log;
+
+/**
+ * The Android Bluetooth API is not finalized, and *will* change. Use at your
+ * own risk.
+ *
+ * Simple SCO Socket.
+ * Currently in Android, there is no support for sending data over a SCO
+ * socket - this is managed by the hardware link to the Bluetooth Chip. This
+ * class is instead intended for management of the SCO socket lifetime,
+ * and is tailored for use with the headset / handsfree profiles.
+ * @hide
+ */
+public class ScoSocket {
+ private static final String TAG = "ScoSocket";
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false; // even more logging
+
+ public static final int STATE_READY = 1; // Ready for use. No threads or sockets
+ public static final int STATE_ACCEPT = 2; // accept() thread running
+ public static final int STATE_CONNECTING = 3; // connect() thread running
+ public static final int STATE_CONNECTED = 4; // connected, waiting for close()
+ public static final int STATE_CLOSED = 5; // was connected, now closed.
+
+ private int mState;
+ private int mNativeData;
+ private Handler mHandler;
+ private int mAcceptedCode;
+ private int mConnectedCode;
+ private int mClosedCode;
+
+ private WakeLock mWakeLock; // held while in STATE_CONNECTING
+
+ static {
+ classInitNative();
+ }
+ private native static void classInitNative();
+
+ public ScoSocket(PowerManager pm, Handler handler, int acceptedCode, int connectedCode,
+ int closedCode) {
+ initNative();
+ mState = STATE_READY;
+ mHandler = handler;
+ mAcceptedCode = acceptedCode;
+ mConnectedCode = connectedCode;
+ mClosedCode = closedCode;
+ mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ScoSocket");
+ mWakeLock.setReferenceCounted(false);
+ if (VDBG) log(this + " SCO OBJECT CTOR");
+ }
+ private native void initNative();
+
+ protected void finalize() throws Throwable {
+ try {
+ if (VDBG) log(this + " SCO OBJECT DTOR");
+ destroyNative();
+ releaseWakeLock();
+ } finally {
+ super.finalize();
+ }
+ }
+ private native void destroyNative();
+
+ /** Connect this SCO socket to the given BT address.
+ * Does not block.
+ */
+ public synchronized boolean connect(String address) {
+ if (VDBG) log("connect() " + this);
+ if (mState != STATE_READY) {
+ if (DBG) log("connect(): Bad state");
+ return false;
+ }
+ acquireWakeLock();
+ if (connectNative(address)) {
+ mState = STATE_CONNECTING;
+ return true;
+ } else {
+ mState = STATE_CLOSED;
+ releaseWakeLock();
+ return false;
+ }
+ }
+ private native boolean connectNative(String address);
+
+ /** Accept incoming SCO connections.
+ * Does not block.
+ */
+ public synchronized boolean accept() {
+ if (VDBG) log("accept() " + this);
+ if (mState != STATE_READY) {
+ if (DBG) log("Bad state");
+ return false;
+ }
+ if (acceptNative()) {
+ mState = STATE_ACCEPT;
+ return true;
+ } else {
+ mState = STATE_CLOSED;
+ return false;
+ }
+ }
+ private native boolean acceptNative();
+
+ public synchronized void close() {
+ if (DBG) log(this + " SCO OBJECT close() mState = " + mState);
+ acquireWakeLock();
+ mState = STATE_CLOSED;
+ closeNative();
+ releaseWakeLock();
+ }
+ private native void closeNative();
+
+ public synchronized int getState() {
+ return mState;
+ }
+
+ private synchronized void onConnected(int result) {
+ if (VDBG) log(this + " onConnected() mState = " + mState + " " + this);
+ if (mState != STATE_CONNECTING) {
+ if (DBG) log("Strange state, closing " + mState + " " + this);
+ return;
+ }
+ if (result >= 0) {
+ mState = STATE_CONNECTED;
+ } else {
+ mState = STATE_CLOSED;
+ }
+ mHandler.obtainMessage(mConnectedCode, mState, -1, this).sendToTarget();
+ releaseWakeLock();
+ }
+
+ private synchronized void onAccepted(int result) {
+ if (VDBG) log("onAccepted() " + this);
+ if (mState != STATE_ACCEPT) {
+ if (DBG) log("Strange state " + this);
+ return;
+ }
+ if (result >= 0) {
+ mState = STATE_CONNECTED;
+ } else {
+ mState = STATE_CLOSED;
+ }
+ mHandler.obtainMessage(mAcceptedCode, mState, -1, this).sendToTarget();
+ }
+
+ private synchronized void onClosed() {
+ if (DBG) log("onClosed() " + this);
+ if (mState != STATE_CLOSED) {
+ mState = STATE_CLOSED;
+ mHandler.obtainMessage(mClosedCode, mState, -1, this).sendToTarget();
+ releaseWakeLock();
+ }
+ }
+
+ private void acquireWakeLock() {
+ if (!mWakeLock.isHeld()) {
+ mWakeLock.acquire();
+ if (VDBG) log("mWakeLock.acquire() " + this);
+ }
+ }
+
+ private void releaseWakeLock() {
+ if (mWakeLock.isHeld()) {
+ if (VDBG) log("mWakeLock.release() " + this);
+ mWakeLock.release();
+ }
+ }
+
+ private void log(String msg) {
+ Log.d(TAG, msg);
+ }
+}
diff --git a/core/java/android/bluetooth/package.html b/core/java/android/bluetooth/package.html
new file mode 100644
index 0000000..79abf0c
--- /dev/null
+++ b/core/java/android/bluetooth/package.html
@@ -0,0 +1,13 @@
+<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.
+</p>
+<p>Remember, not all Android devices are guaranteed to have Bluetooth functionality.</p>
+</BODY>
+</HTML>