diff options
| author | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 | 
|---|---|---|
| committer | The Android Open Source Project <initial-contribution@android.com> | 2008-10-21 07:00:00 -0700 | 
| commit | 54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch) | |
| tree | 35051494d2af230dce54d6b31c6af8fc24091316 /core/java/android/bluetooth | |
| download | frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2 | |
Initial Contribution
Diffstat (limited to 'core/java/android/bluetooth')
17 files changed, 3397 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/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/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java new file mode 100644 index 0000000..0b24db6 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -0,0 +1,601 @@ +/* + * 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 { +    public static final int MODE_UNKNOWN = -1; +    public static final int MODE_OFF = 0; +    public static final int MODE_CONNECTABLE = 1; +    public static final int MODE_DISCOVERABLE = 2; + +    public static final int RESULT_FAILURE = -1; +    public static final int RESULT_SUCCESS = 0; + +    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 getMajorClass() { +        try { +            return mService.getMajorClass(); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } +    public String getMinorClass() { +        try { +            return mService.getMinorClass(); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } +    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; +    } + +    public int getMode() { +        try { +            return mService.getMode(); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return MODE_UNKNOWN; +    } +    public void setMode(int mode) { +        try { +            mService.setMode(mode); +        } 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. BluetoothIntent.BONDING_CREATED_ACTION +     * will be broadcast if and when the remote device is successfully bonded. +     * +     * @param address the remote device Bluetooth address. +     * @return false if we cannot create a bonding to that device, true if +     * there were no problems beginning the bonding process. +     */ +    public boolean createBonding(String address) { +        return createBonding(address, null); +    } + +    /** +     * Create a bonding with a remote bluetooth device. +     * +     * This is an asynchronous call. onCreateBondingResult() of your callback +     * will be called when the call is complete, with either RESULT_SUCCESS or +     * RESULT_FAILURE. +     * +     * In addition to the callback, BluetoothIntent.BONDING_CREATED_ACTION will +     * be broadcast if the remote device is successfully bonded. +     * +     * @param address The remote device Bluetooth address. +     * @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 createBonding(String address, IBluetoothDeviceCallback callback) { +        try { +            return mService.createBonding(address, callback); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return false; +    } + +    public boolean cancelBondingProcess(String address) { +        try { +            return mService.cancelBondingProcess(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. +     * +     * @return bluetooth hardware addresses of remote devices that are +     *         bonded. Array size is 0 if no devices are bonded. Null on error. +     */ +    public String[] listBondings() { +        try { +            return mService.listBondings(); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } + +    /** +     * Check if a remote device is 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 checks if we have a link key with the remote device. 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 +     * a 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. +     * +     * @param address Bluetooth hardware address of the remote device to check. +     * @return true if bonded, false otherwise and on error. +     */ +    public boolean hasBonding(String address) { +        try { +            return mService.hasBonding(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return false; +    } + +    public boolean removeBonding(String address) { +        try { +            return mService.removeBonding(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return false; +    } + +    public String getRemoteName(String address) { +        try { +            return mService.getRemoteName(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } + +    public String getRemoteAlias(String address) { +        try { +            return mService.getRemoteAlias(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } +    public boolean setRemoteAlias(String address, String alias) { +        try { +            return mService.setRemoteAlias(address, alias); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return false; +    } +    public boolean clearRemoteAlias(String address) { +        try { +            return mService.clearRemoteAlias(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return false; +    } +    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; +    } +    public String getRemoteMajorClass(String address) { +        try { +            return mService.getRemoteMajorClass(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } +    public String getRemoteMinorClass(String address) { +        try { +            return mService.getRemoteMinorClass(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return null; +    } +    public String[] getRemoteServiceClasses(String address) { +        try { +            return mService.getRemoteServiceClasses(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; +    } + +    public int getRemoteClass(String address) { +        try { +            return mService.getRemoteClass(address); +        } catch (RemoteException e) {Log.e(TAG, "", e);} +        return DeviceClass.CLASS_UNKNOWN; +    } +    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; +    } +     + +    /* Sanity check a bluetooth address, such as "00:43:A8:23:10:F0" */ +    private static final int ADDRESS_LENGTH = 17; +    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/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java new file mode 100644 index 0000000..90db39b --- /dev/null +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -0,0 +1,198 @@ +/* + * 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. + * + * BluetoothHeadset is a proxy object for controlling the Bluetooth Headset + * Service. + * + * 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. + * + * BlueoothHeadset objects are not guarenteed to be connected to the + * BluetoothHeadsetService at all times. Calls on this object while not + * connected to the service will result in default error return values. Even + * after object construction, there is a short delay (~10ms) before this proxy + * object is actually connected to the Service. + * + * Android only supports one connected Bluetooth Headset at a time. + * + * Note that in this context, Headset includes both Bluetooth Headset's and + * Handsfree devices. + * + * @hide + */ +public class BluetoothHeadset { + +    private final static String TAG = "BluetoothHeadset"; + +    private final Context mContext; +    private IBluetoothHeadset mService; + +    /** 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 cancelled before completetion. */ +    public static final int RESULT_CANCELLED = 2; + +    private ServiceConnection mConnection = new ServiceConnection() { +        public void onServiceConnected(ComponentName className, IBinder service) { +            mService = IBluetoothHeadset.Stub.asInterface(service); +            Log.i(TAG, "Proxy object is now connected to Bluetooth Headset Service"); +        } +        public void onServiceDisconnected(ComponentName className) { +            mService = null; +        } +    }; + +    /** +     * Create a BluetoothHeadset proxy object. +     * Remeber to call close() when you are done with this object, so that it +     * can unbind from the BluetoothHeadsetService. +     */ +    public BluetoothHeadset(Context context) { +        mContext = context; +        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());} +        } +        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());} +        } +        return null; +    } + +    /** +     * Request to initiate a connection to a headset. +     * This call does not block. Fails if a headset is already connecting +     * or connected. +     * Will connect to the last connected headset if address is null. +     * @param address The Bluetooth Address to connect to, or null to connect +     *                to the last connected headset. +     * @param callback A callback with onCreateBondingResult() defined, or +     *                 null. +     * @return        False if there was a problem initiating the connection +     *                procedure, and your callback will not be used. True if +     *                the connection procedure was initiated, in which case +     *                your callback is guarenteed to be called. +     */ +    public boolean connectHeadset(String address, IBluetoothHeadsetCallback callback) { +        if (mService != null) { +            try { +                return mService.connectHeadset(address, callback); +            } catch (RemoteException e) {Log.e(TAG, e.toString());} +        } +        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());} +        } +        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(); +            } catch (RemoteException e) {Log.e(TAG, e.toString());} +        } +        return false; +    } +} diff --git a/core/java/android/bluetooth/BluetoothIntent.java b/core/java/android/bluetooth/BluetoothIntent.java new file mode 100644 index 0000000..8e22791 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothIntent.java @@ -0,0 +1,118 @@ +/* + * 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 MODE = +        "android.bluetooth.intent.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"; + +    @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"; +    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) +    public static final String MODE_CHANGED_ACTION         = +        "android.bluetooth.intent.action.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"; +    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) +    public static final String REMOTE_ALIAS_CHANGED_ACTION       = +        "android.bluetooth.intent.action.REMOTE_ALIAS_CHANGED"; +    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) +    public static final String REMOTE_ALIAS_CLEARED_ACTION       = +        "android.bluetooth.intent.action.REMOTE_ALIAS_CLEARED"; + +    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) +    public static final String BONDING_CREATED_ACTION            = +        "android.bluetooth.intent.action.BONDING_CREATED"; +    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) +    public static final String BONDING_REMOVED_ACTION            = +        "android.bluetooth.intent.action.BONDING_REMOVED"; + +    @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/DeviceClass.java b/core/java/android/bluetooth/DeviceClass.java new file mode 100644 index 0000000..36035ca --- /dev/null +++ b/core/java/android/bluetooth/DeviceClass.java @@ -0,0 +1,131 @@ +/* + * 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 device + * class. This is actually a bit vector, the format 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), Major Class, and Minor Class are encoded + * in a 32-bit device class. + * + * Each of the helper methods takes the 32-bit integer device class as an + * argument. + * + * @hide + */ +public class DeviceClass { + +    // Baseband class information +    // See http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm + +    public static final int SERVICE_CLASS_BITMASK                 = 0xFFE000; +    public static final int SERVICE_CLASS_LIMITED_DISCOVERABILITY = 0x002000; +    public static final int SERVICE_CLASS_POSITIONING             = 0x010000; +    public static final int SERVICE_CLASS_NETWORKING              = 0x020000; +    public static final int SERVICE_CLASS_RENDER                  = 0x040000; +    public static final int SERVICE_CLASS_CAPTURE                 = 0x080000; +    public static final int SERVICE_CLASS_OBJECT_TRANSFER         = 0x100000; +    public static final int SERVICE_CLASS_AUDIO                   = 0x200000; +    public static final int SERVICE_CLASS_TELEPHONY               = 0x400000; +    public static final int SERVICE_CLASS_INFORMATION             = 0x800000; + +    public static final int MAJOR_CLASS_BITMASK           = 0x001F00; +    public static final int MAJOR_CLASS_MISC              = 0x000000; +    public static final int MAJOR_CLASS_COMPUTER          = 0x000100; +    public static final int MAJOR_CLASS_PHONE             = 0x000200; +    public static final int MAJOR_CLASS_NETWORKING        = 0x000300; +    public static final int MAJOR_CLASS_AUDIO_VIDEO       = 0x000400; +    public static final int MAJOR_CLASS_PERIPHERAL        = 0x000500; +    public static final int MAJOR_CLASS_IMAGING           = 0x000600; +    public static final int MAJOR_CLASS_WEARABLE          = 0x000700; +    public static final int MAJOR_CLASS_TOY               = 0x000800; +    public static final int MAJOR_CLASS_MEDICAL           = 0x000900; +    public static final int MAJOR_CLASS_UNCATEGORIZED     = 0x001F00; + +    // Minor classes for the AUDIO_VIDEO major class +    public static final int MINOR_CLASS_AUDIO_VIDEO_BITMASK                       = 0x0000FC; +    public static final int MINOR_CLASS_AUDIO_VIDEO_UNCATEGORIZED                 = 0x000000; +    public static final int MINOR_CLASS_AUDIO_VIDEO_HEADSET                       = 0x000004; +    public static final int MINOR_CLASS_AUDIO_VIDEO_HANDSFREE                     = 0x000008; +    public static final int MINOR_CLASS_AUDIO_VIDEO_MICROPHONE                    = 0x000010; +    public static final int MINOR_CLASS_AUDIO_VIDEO_LOUDSPEAKER                   = 0x000014; +    public static final int MINOR_CLASS_AUDIO_VIDEO_HEADPHONES                    = 0x000018; +    public static final int MINOR_CLASS_AUDIO_VIDEO_PORTABLE_AUDIO                = 0x00001C; +    public static final int MINOR_CLASS_AUDIO_VIDEO_CAR_AUDIO                     = 0x000020; +    public static final int MINOR_CLASS_AUDIO_VIDEO_SET_TOP_BOX                   = 0x000024; +    public static final int MINOR_CLASS_AUDIO_VIDEO_HIFI_AUDIO                    = 0x000028; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VCR                           = 0x00002C; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CAMERA                  = 0x000030; +    public static final int MINOR_CLASS_AUDIO_VIDEO_CAMCORDER                     = 0x000034; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_MONITOR                 = 0x000038; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x00003C; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_CONFERENCING            = 0x000040; +    public static final int MINOR_CLASS_AUDIO_VIDEO_VIDEO_GAMING_TOY              = 0x000048; + +    // Indicates the Bluetooth API could not retrieve the class +    public static final int CLASS_UNKNOWN = 0xFF000000; + +    /** Returns true if the given device class supports the given Service Class. +     * A bluetooth device can claim to support zero or more service classes. +     * @param deviceClass      The bluetooth device class. +     * @param serviceClassType The service class constant to test for. For +     *                         example, DeviceClass.SERVICE_CLASS_AUDIO. This +     *                         must be one of the SERVICE_CLASS_xxx constants, +     *                         results of this function are undefined +     *                         otherwise. +     * @return If the deviceClass claims to support the serviceClassType. +     */ +    public static boolean hasServiceClass(int deviceClass, int serviceClassType) { +        if (deviceClass == CLASS_UNKNOWN) { +            return false; +        } +        return ((deviceClass & SERVICE_CLASS_BITMASK & serviceClassType) != 0); +    } + +    /** Returns the Major Class of a bluetooth device class. +     * Values returned from this function can be compared with the constants +     * MAJOR_CLASS_xxx. A bluetooth device can only be associated +     * with one major class. +     */ +    public static int getMajorClass(int deviceClass) { +        if (deviceClass == CLASS_UNKNOWN) { +            return CLASS_UNKNOWN; +        } +        return (deviceClass & MAJOR_CLASS_BITMASK); +    } + +    /** Returns the Minor Class of a bluetooth device class. +     * Values returned from this function can be compared with the constants +     * MINOR_CLASS_xxx_yyy, where xxx is the Major Class. A bluetooth +     * device can only be associated with one minor class within its major +     * class. +     */ +    public static int getMinorClass(int deviceClass) { +        if (deviceClass == CLASS_UNKNOWN) { +            return CLASS_UNKNOWN; +        } +        return (deviceClass & MINOR_CLASS_AUDIO_VIDEO_BITMASK); +    } +} diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java new file mode 100644 index 0000000..bce3388 --- /dev/null +++ b/core/java/android/bluetooth/HeadsetBase.java @@ -0,0 +1,310 @@ +/* + * 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; + +import java.io.IOException; +import java.lang.Thread; + +/** + * 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(); + +        // Microphone Gain +        mAtParser.register("+VGM", new AtCommandHandler() { +            @Override +            public AtCommandResult handleSetCommand(Object[] args) { +                // AT+VGM=<gain>    in range [0,15] +                // Headset/Handsfree is reporting its current gain setting +                //TODO: sync to android UI +                //TODO: Send unsolicited +VGM when volume changed on AG +                return new AtCommandResult(AtCommandResult.OK); +            } +        }); + +        // Speaker Gain +        mAtParser.register("+VGS", new AtCommandHandler() { +            @Override +            public AtCommandResult handleSetCommand(Object[] args) { +                // AT+VGS=<gain>    in range [0,15] +                // Headset/Handsfree is reporting its current gain to Android +                //TODO: sync to AG UI +                //TODO: Send unsolicited +VGS when volume changed on AG +                return new AtCommandResult(AtCommandResult.OK); +            } +        }); +    } + +    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/IBluetoothDevice.aidl b/core/java/android/bluetooth/IBluetoothDevice.aidl new file mode 100644 index 0000000..e7cc8ed --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothDevice.aidl @@ -0,0 +1,85 @@ +/* + * 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 getMajorClass(); +    String getMinorClass(); +    String getVersion(); +    String getRevision(); +    String getManufacturer(); +    String getCompany(); + +    int getMode(); +    boolean setMode(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 createBonding(in String address, in IBluetoothDeviceCallback callback); +    boolean cancelBondingProcess(in String address); +    String[] listBondings(); +    boolean hasBonding(in String address); +    boolean removeBonding(in String address); + +    String getRemoteName(in String address); +    String getRemoteAlias(in String address); +    boolean setRemoteAlias(in String address, in String alias); +    boolean clearRemoteAlias(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); +    String getRemoteMajorClass(in String address); +    String getRemoteMinorClass(in String address); +    String[] getRemoteServiceClasses(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..86f44dd --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothDeviceCallback.aidl @@ -0,0 +1,28 @@ +/* + * 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 onCreateBondingResult(in String address, int result); +    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..7b6030b --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -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; + +import android.bluetooth.IBluetoothHeadsetCallback; + +/** + * System private API for Bluetooth Headset service + * + * {@hide} + */ +interface IBluetoothHeadset { +    int getState(); + +    String getHeadsetAddress(); + +    // Request that the given headset be connected +    // Assumes the given headset is already bonded +    // Will disconnect any currently connected headset +    // returns false if cannot start a connection (for example, there is +    // already a pending connect). callback will always be called iff this +    // returns true +    boolean connectHeadset(in String address, in IBluetoothHeadsetCallback callback); + +    boolean isConnected(in String address); + +    void disconnectHeadset(); +} diff --git a/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl b/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl new file mode 100644 index 0000000..03e884b --- /dev/null +++ b/core/java/android/bluetooth/IBluetoothHeadsetCallback.aidl @@ -0,0 +1,25 @@ +/* + * 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 IBluetoothHeadsetCallback +{ +    void onConnectHeadsetResult(in String address, int resultCode); +} 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..75b3329 --- /dev/null +++ b/core/java/android/bluetooth/ScoSocket.java @@ -0,0 +1,201 @@ +/* + * 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.Context; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.util.Log; + +import java.io.IOException; +import java.lang.Thread; + + +/** + * 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 STATE_CONNECTING or STATE_CONNECTED + +    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); +        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(); +        if (result < 0) { +            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) { +            acquireWakeLock(); +            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..ccd8fec --- /dev/null +++ b/core/java/android/bluetooth/package.html @@ -0,0 +1,14 @@ +<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> +{@hide} +</BODY> +</HTML> | 
