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