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
17package android.bluetooth;
18
19import android.os.Handler;
20import android.os.PowerManager;
21import android.os.PowerManager.WakeLock;
22import 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 */
34public 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