blob: e2935c95d324a423c2550c519b1e53912c4f5880 [file] [log] [blame]
/*
* 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 final 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 static int sAtInputCount = 0; /* TODO: Consider not using a static variable */
private final BluetoothAdapter mAdapter;
private final BluetoothDevice mRemoteDevice;
private final String mAddress; // for native code
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, BluetoothAdapter adapter, BluetoothDevice device,
int rfcommChannel) {
mDirection = DIRECTION_OUTGOING;
mConnectTimestamp = System.currentTimeMillis();
mAdapter = adapter;
mRemoteDevice = device;
mAddress = device.getAddress();
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, BluetoothAdapter adapter, BluetoothDevice device,
int socketFd, int rfcommChannel, Handler handler) {
mDirection = DIRECTION_INCOMING;
mConnectTimestamp = System.currentTimeMillis();
mAdapter = adapter;
mRemoteDevice = device;
mAddress = device.getAddress();
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 void handleInput(String input) {
acquireWakeLock();
long timestamp;
synchronized(HeadsetBase.class) {
if (sAtInputCount == Integer.MAX_VALUE) {
sAtInputCount = 0;
} else {
sAtInputCount++;
}
}
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() {
int ret = connectAsyncNative();
return (ret == 0) ? true : false;
}
private native int 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 BluetoothDevice getRemoteDevice() {
return mRemoteDevice;
}
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 synchronized void acquireWakeLock() {
if (!mWakeLock.isHeld()) {
mWakeLock.acquire();
}
}
private synchronized void releaseWakeLock() {
if (mWakeLock.isHeld()) {
mWakeLock.release();
}
}
public static int getAtInputCount() {
return sAtInputCount;
}
private static void log(String msg) {
Log.d(TAG, msg);
}
}