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 classInitNative()61 private native static void classInitNative(); 62 static { classInitNative()63 classInitNative(); 64 } 65 finalize()66 protected void finalize() throws Throwable { 67 try { 68 cleanupNativeDataNative(); 69 releaseWakeLock(); 70 } finally { 71 super.finalize(); 72 } 73 } 74 cleanupNativeDataNative()75 private native void cleanupNativeDataNative(); 76 HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device, int rfcommChannel)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 */ HeadsetBase(PowerManager pm, BluetoothAdapter adapter, BluetoothDevice device, int socketFd, int rfcommChannel, Handler handler)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 initializeNativeDataNative(int socketFd)109 private native void initializeNativeDataNative(int socketFd); 110 111 /* Process an incoming AT command line 112 */ handleInput(String input)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 */ initializeAtParser()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 getAtParser()149 public AtParser getAtParser() { 150 return mAtParser; 151 } 152 startEventThread()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 readNative(int timeout_ms)184 private native String readNative(int timeout_ms); getLastReadStatusNative()185 private native int getLastReadStatusNative(); 186 stopEventThread()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 connect(Handler handler)198 public boolean connect(Handler handler) { 199 if (mEventThread == null) { 200 if (!connectNative()) return false; 201 mEventThreadHandler = handler; 202 } 203 return true; 204 } connectNative()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 connectAsync()213 public boolean connectAsync() { 214 int ret = connectAsyncNative(); 215 return (ret == 0) ? true : false; 216 } connectAsyncNative()217 private native int connectAsyncNative(); 218 getRemainingAsyncConnectWaitingTimeMs()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 */ waitForAsyncConnect(int timeout_ms, Handler handler)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 } waitForAsyncConnectNative(int timeout_ms)235 private native int waitForAsyncConnectNative(int timeout_ms); 236 disconnect()237 public void disconnect() { 238 if (mEventThread != null) { 239 stopEventThread(); 240 } 241 disconnectNative(); 242 } disconnectNative()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 isConnected()255 public boolean isConnected() { 256 return mEventThread != null; 257 } 258 getRemoteDevice()259 public BluetoothDevice getRemoteDevice() { 260 return mRemoteDevice; 261 } 262 getDirection()263 public int getDirection() { 264 return mDirection; 265 } 266 getConnectTimestamp()267 public long getConnectTimestamp() { 268 return mConnectTimestamp; 269 } 270 sendURC(String urc)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 } sendURCNative(String urc)278 private native boolean sendURCNative(String urc); 279 acquireWakeLock()280 private synchronized void acquireWakeLock() { 281 if (!mWakeLock.isHeld()) { 282 mWakeLock.acquire(); 283 } 284 } 285 releaseWakeLock()286 private synchronized void releaseWakeLock() { 287 if (mWakeLock.isHeld()) { 288 mWakeLock.release(); 289 } 290 } 291 getAtInputCount()292 public static int getAtInputCount() { 293 return sAtInputCount; 294 } 295 log(String msg)296 private static void log(String msg) { 297 Log.d(TAG, msg); 298 } 299 } 300