Home | History | Annotate | Download | only in bluetooth
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.bluetooth;
     18 
     19 import android.os.Handler;
     20 import android.os.PowerManager;
     21 import android.os.PowerManager.WakeLock;
     22 import android.util.Log;
     23 
     24 /**
     25  * The Android Bluetooth API is not finalized, and *will* change. Use at your
     26  * own risk.
     27  *
     28  * The base RFCOMM (service) connection for a headset or handsfree device.
     29  *
     30  * In the future this class will be removed.
     31  *
     32  * @hide
     33  */
     34 public final class HeadsetBase {
     35     private static final String TAG = "Bluetooth HeadsetBase";
     36     private static final boolean DBG = false;
     37 
     38     public static final int RFCOMM_DISCONNECTED = 1;
     39 
     40     public static final int DIRECTION_INCOMING = 1;
     41     public static final int DIRECTION_OUTGOING = 2;
     42 
     43     private static int sAtInputCount = 0;  /* TODO: Consider not using a static variable */
     44 
     45     private final BluetoothAdapter mAdapter;
     46     private final BluetoothDevice mRemoteDevice;
     47     private final String mAddress;  // for native code
     48     private final int mRfcommChannel;
     49     private int mNativeData;
     50     private Thread mEventThread;
     51     private volatile boolean mEventThreadInterrupted;
     52     private Handler mEventThreadHandler;
     53     private int mTimeoutRemainingMs;
     54     private final int mDirection;
     55     private final long mConnectTimestamp;
     56 
     57     protected AtParser mAtParser;
     58 
     59     private WakeLock mWakeLock;  // held while processing an AT command
     60 
     61     private native static void classInitNative();
     62     static {
     63         classInitNative();
     64     }
     65 
     66     protected void finalize() throws Throwable {
     67         try {
     68             cleanupNativeDataNative();
     69             releaseWakeLock();
     70         } finally {
     71             super.finalize();
     72         }
     73     }
     74 
     75     private native void cleanupNativeDataNative();
     76 
     77     public HeadsetBase(PowerManager pm, BluetoothAdapter adapter,
     78                        BluetoothDevice device, int rfcommChannel) {
     79         mDirection = DIRECTION_OUTGOING;
     80         mConnectTimestamp = System.currentTimeMillis();
     81         mAdapter = adapter;
     82         mRemoteDevice = device;
     83         mAddress = device.getAddress();
     84         mRfcommChannel = rfcommChannel;
     85         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
     86         mWakeLock.setReferenceCounted(false);
     87         initializeAtParser();
     88         // Must be called after this.mAddress is set.
     89         initializeNativeDataNative(-1);
     90     }
     91 
     92     /* Create from an existing rfcomm connection */
     93     public HeadsetBase(PowerManager pm, BluetoothAdapter adapter,
     94                        BluetoothDevice device,
     95                        int socketFd, int rfcommChannel, Handler handler) {
     96         mDirection = DIRECTION_INCOMING;
     97         mConnectTimestamp = System.currentTimeMillis();
     98         mAdapter = adapter;
     99         mRemoteDevice = device;
    100         mAddress = device.getAddress();
    101         mRfcommChannel = rfcommChannel;
    102         mEventThreadHandler = handler;
    103         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
    104         mWakeLock.setReferenceCounted(false);
    105         initializeAtParser();
    106         // Must be called after this.mAddress is set.
    107         initializeNativeDataNative(socketFd);
    108     }
    109 
    110     private native void initializeNativeDataNative(int socketFd);
    111 
    112     /* Process an incoming AT command line
    113      */
    114     protected void handleInput(String input) {
    115         acquireWakeLock();
    116         long timestamp;
    117 
    118         synchronized(HeadsetBase.class) {
    119             if (sAtInputCount == Integer.MAX_VALUE) {
    120                 sAtInputCount = 0;
    121             } else {
    122                 sAtInputCount++;
    123             }
    124         }
    125 
    126         if (DBG) timestamp = System.currentTimeMillis();
    127         AtCommandResult result = mAtParser.process(input);
    128         if (DBG) Log.d(TAG, "Processing " + input + " took " +
    129                        (System.currentTimeMillis() - timestamp) + " ms");
    130 
    131         if (result.getResultCode() == AtCommandResult.ERROR) {
    132             Log.i(TAG, "Error processing <" + input + ">");
    133         }
    134 
    135         sendURC(result.toString());
    136 
    137         releaseWakeLock();
    138     }
    139 
    140     /**
    141      * Register AT commands that are common to all Headset / Handsets. This
    142      * function is called by the HeadsetBase constructor.
    143      */
    144     protected void initializeAtParser() {
    145         mAtParser = new AtParser();
    146 
    147         //TODO(): Get rid of this as there are no parsers registered. But because of dependencies
    148         // it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
    149     }
    150 
    151     public AtParser getAtParser() {
    152         return mAtParser;
    153     }
    154 
    155     public void startEventThread() {
    156         mEventThread =
    157             new Thread("HeadsetBase Event Thread") {
    158                 public void run() {
    159                     int last_read_error;
    160                     while (!mEventThreadInterrupted) {
    161                         String input = readNative(500);
    162                         if (input != null) {
    163                             handleInput(input);
    164                         } else {
    165                             last_read_error = getLastReadStatusNative();
    166                             if (last_read_error != 0) {
    167                                 Log.i(TAG, "headset read error " + last_read_error);
    168                                 if (mEventThreadHandler != null) {
    169                                     mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
    170                                             .sendToTarget();
    171                                 }
    172                                 disconnectNative();
    173                                 break;
    174                             }
    175                         }
    176                     }
    177                 }
    178             };
    179         mEventThreadInterrupted = false;
    180         mEventThread.start();
    181     }
    182 
    183     private native String readNative(int timeout_ms);
    184     private native int getLastReadStatusNative();
    185 
    186     private void stopEventThread() {
    187         mEventThreadInterrupted = true;
    188         mEventThread.interrupt();
    189         try {
    190             mEventThread.join();
    191         } catch (java.lang.InterruptedException e) {
    192             // FIXME: handle this,
    193         }
    194         mEventThread = null;
    195     }
    196 
    197     public boolean connect(Handler handler) {
    198         if (mEventThread == null) {
    199             if (!connectNative()) return false;
    200             mEventThreadHandler = handler;
    201         }
    202         return true;
    203     }
    204     private native boolean connectNative();
    205 
    206     /*
    207      * Returns true when either the asynchronous connect is in progress, or
    208      * the connect is complete.  Call waitForAsyncConnect() to find out whether
    209      * the connect is actually complete, or disconnect() to cancel.
    210      */
    211 
    212     public boolean connectAsync() {
    213         int ret = connectAsyncNative();
    214         return (ret == 0) ? true : false;
    215     }
    216     private native int connectAsyncNative();
    217 
    218     public int getRemainingAsyncConnectWaitingTimeMs() {
    219         return mTimeoutRemainingMs;
    220     }
    221 
    222     /*
    223      * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
    224      * error.  On error, handler will be called, and you need to re-initiate
    225      * the async connect.
    226      */
    227     public int waitForAsyncConnect(int timeout_ms, Handler handler) {
    228         int res = waitForAsyncConnectNative(timeout_ms);
    229         if (res > 0) {
    230             mEventThreadHandler = handler;
    231         }
    232         return res;
    233     }
    234     private native int waitForAsyncConnectNative(int timeout_ms);
    235 
    236     public void disconnect() {
    237         if (mEventThread != null) {
    238             stopEventThread();
    239         }
    240         disconnectNative();
    241     }
    242     private native void disconnectNative();
    243 
    244 
    245     /*
    246      * Note that if a remote side disconnects, this method will still return
    247      * true until disconnect() is called.  You know when a remote side
    248      * disconnects because you will receive the intent
    249      * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION.  If, when you get
    250      * this intent, method isConnected() returns true, you know that the
    251      * disconnect was initiated by the remote device.
    252      */
    253 
    254     public boolean isConnected() {
    255         return mEventThread != null;
    256     }
    257 
    258     public BluetoothDevice getRemoteDevice() {
    259         return mRemoteDevice;
    260     }
    261 
    262     public int getDirection() {
    263         return mDirection;
    264     }
    265 
    266     public long getConnectTimestamp() {
    267         return mConnectTimestamp;
    268     }
    269 
    270     public synchronized boolean sendURC(String urc) {
    271         if (urc.length() > 0) {
    272             boolean ret = sendURCNative(urc);
    273             return ret;
    274         }
    275         return true;
    276     }
    277     private native boolean sendURCNative(String urc);
    278 
    279     private synchronized void acquireWakeLock() {
    280         if (!mWakeLock.isHeld()) {
    281             mWakeLock.acquire();
    282         }
    283     }
    284 
    285     private synchronized void releaseWakeLock() {
    286         if (mWakeLock.isHeld()) {
    287             mWakeLock.release();
    288         }
    289     }
    290 
    291     public static int getAtInputCount() {
    292         return sAtInputCount;
    293     }
    294 
    295     private static void log(String msg) {
    296         Log.d(TAG, msg);
    297     }
    298 }
    299