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