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,
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