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, BluetoothDevice device,
     78             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 already existing rfcomm connection */
     93     public HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device,
     94             int socketFd, int rfcommChannel, Handler handler) {
     95         mDirection = DIRECTION_INCOMING;
     96         mConnectTimestamp = System.currentTimeMillis();
     97         mAdapter = adapter;
     98         mRemoteDevice = device;
     99         mAddress = device.getAddress();
    100         mRfcommChannel = rfcommChannel;
    101         mEventThreadHandler = handler;
    102         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "HeadsetBase");
    103         mWakeLock.setReferenceCounted(false);
    104         initializeAtParser();
    105         // Must be called after this.mAddress is set.
    106         initializeNativeDataNative(socketFd);
    107     }
    108 
    109     private native void initializeNativeDataNative(int socketFd);
    110 
    111     /* Process an incoming AT command line
    112      */
    113     protected void handleInput(String input) {
    114         acquireWakeLock();
    115         long timestamp;
    116 
    117         synchronized(HeadsetBase.class) {
    118             if (sAtInputCount == Integer.MAX_VALUE) {
    119                 sAtInputCount = 0;
    120             } else {
    121                 sAtInputCount++;
    122             }
    123         }
    124 
    125         if (DBG) timestamp = System.currentTimeMillis();
    126         AtCommandResult result = mAtParser.process(input);
    127         if (DBG) Log.d(TAG, "Processing " + input + " took " +
    128                        (System.currentTimeMillis() - timestamp) + " ms");
    129 
    130         if (result.getResultCode() == AtCommandResult.ERROR) {
    131             Log.i(TAG, "Error processing <" + input + ">");
    132         }
    133 
    134         sendURC(result.toString());
    135 
    136         releaseWakeLock();
    137     }
    138 
    139     /**
    140      * Register AT commands that are common to all Headset / Handsets. This
    141      * function is called by the HeadsetBase constructor.
    142      */
    143     protected void initializeAtParser() {
    144         mAtParser = new AtParser();
    145         //TODO(): Get rid of this as there are no parsers registered. But because of dependencies,
    146         //it needs to be done as part of refactoring HeadsetBase and BluetoothHandsfree
    147     }
    148 
    149     public AtParser getAtParser() {
    150         return mAtParser;
    151     }
    152 
    153     public void startEventThread() {
    154         mEventThread =
    155             new Thread("HeadsetBase Event Thread") {
    156                 public void run() {
    157                     int last_read_error;
    158                     while (!mEventThreadInterrupted) {
    159                         String input = readNative(500);
    160                         if (input != null) {
    161                             handleInput(input);
    162                         }
    163                         else {
    164                             last_read_error = getLastReadStatusNative();
    165                             if (last_read_error != 0) {
    166                                 Log.i(TAG, "headset read error " + last_read_error);
    167                                 if (mEventThreadHandler != null) {
    168                                     mEventThreadHandler.obtainMessage(RFCOMM_DISCONNECTED)
    169                                             .sendToTarget();
    170                                 }
    171                                 disconnectNative();
    172                                 break;
    173                             }
    174                         }
    175                     }
    176                 }
    177             };
    178         mEventThreadInterrupted = false;
    179         mEventThread.start();
    180     }
    181 
    182 
    183 
    184     private native String readNative(int timeout_ms);
    185     private native int getLastReadStatusNative();
    186 
    187     private void stopEventThread() {
    188         mEventThreadInterrupted = true;
    189         mEventThread.interrupt();
    190         try {
    191             mEventThread.join();
    192         } catch (java.lang.InterruptedException e) {
    193             // FIXME: handle this,
    194         }
    195         mEventThread = null;
    196     }
    197 
    198     public boolean connect(Handler handler) {
    199         if (mEventThread == null) {
    200             if (!connectNative()) return false;
    201             mEventThreadHandler = handler;
    202         }
    203         return true;
    204     }
    205     private native boolean connectNative();
    206 
    207     /*
    208      * Returns true when either the asynchronous connect is in progress, or
    209      * the connect is complete.  Call waitForAsyncConnect() to find out whether
    210      * the connect is actually complete, or disconnect() to cancel.
    211      */
    212 
    213     public boolean connectAsync() {
    214         int ret = connectAsyncNative();
    215         return (ret == 0) ? true : false;
    216     }
    217     private native int connectAsyncNative();
    218 
    219     public int getRemainingAsyncConnectWaitingTimeMs() {
    220         return mTimeoutRemainingMs;
    221     }
    222 
    223     /*
    224      * Returns 1 when an async connect is complete, 0 on timeout, and -1 on
    225      * error.  On error, handler will be called, and you need to re-initiate
    226      * the async connect.
    227      */
    228     public int waitForAsyncConnect(int timeout_ms, Handler handler) {
    229         int res = waitForAsyncConnectNative(timeout_ms);
    230         if (res > 0) {
    231             mEventThreadHandler = handler;
    232         }
    233         return res;
    234     }
    235     private native int waitForAsyncConnectNative(int timeout_ms);
    236 
    237     public void disconnect() {
    238         if (mEventThread != null) {
    239             stopEventThread();
    240         }
    241         disconnectNative();
    242     }
    243     private native void disconnectNative();
    244 
    245 
    246     /*
    247      * Note that if a remote side disconnects, this method will still return
    248      * true until disconnect() is called.  You know when a remote side
    249      * disconnects because you will receive the intent
    250      * IBluetoothService.REMOTE_DEVICE_DISCONNECTED_ACTION.  If, when you get
    251      * this intent, method isConnected() returns true, you know that the
    252      * disconnect was initiated by the remote device.
    253      */
    254 
    255     public boolean isConnected() {
    256         return mEventThread != null;
    257     }
    258 
    259     public BluetoothDevice getRemoteDevice() {
    260         return mRemoteDevice;
    261     }
    262 
    263     public int getDirection() {
    264         return mDirection;
    265     }
    266 
    267     public long getConnectTimestamp() {
    268         return mConnectTimestamp;
    269     }
    270 
    271     public synchronized boolean sendURC(String urc) {
    272         if (urc.length() > 0) {
    273             boolean ret = sendURCNative(urc);
    274             return ret;
    275         }
    276         return true;
    277     }
    278     private native boolean sendURCNative(String urc);
    279 
    280     private synchronized void acquireWakeLock() {
    281         if (!mWakeLock.isHeld()) {
    282             mWakeLock.acquire();
    283         }
    284     }
    285 
    286     private synchronized void releaseWakeLock() {
    287         if (mWakeLock.isHeld()) {
    288             mWakeLock.release();
    289         }
    290     }
    291 
    292     public static int getAtInputCount() {
    293         return sAtInputCount;
    294     }
    295 
    296     private static void log(String msg) {
    297         Log.d(TAG, msg);
    298     }
    299 }
    300