1/*
2 * Copyright (C) 2014 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.client.pbap;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothDevice;
21import android.bluetooth.BluetoothSocket;
22import android.os.Handler;
23import android.os.Handler.Callback;
24import android.os.HandlerThread;
25import android.os.Message;
26import android.os.Process;
27import android.util.Log;
28
29import java.io.IOException;
30import java.util.UUID;
31
32class BluetoothPbapSession implements Callback {
33    private static final String TAG = "android.bluetooth.client.pbap.BluetoothPbapSession";
34
35    /* local use only */
36    private static final int RFCOMM_CONNECTED = 1;
37    private static final int RFCOMM_FAILED = 2;
38
39    /* to BluetoothPbapClient */
40    public static final int REQUEST_COMPLETED = 3;
41    public static final int REQUEST_FAILED = 4;
42    public static final int SESSION_CONNECTING = 5;
43    public static final int SESSION_CONNECTED = 6;
44    public static final int SESSION_DISCONNECTED = 7;
45    public static final int AUTH_REQUESTED = 8;
46    public static final int AUTH_TIMEOUT = 9;
47
48    public static final int ACTION_LISTING = 14;
49    public static final int ACTION_VCARD = 15;
50    public static final int ACTION_PHONEBOOK_SIZE = 16;
51
52    private static final String PBAP_UUID =
53            "0000112f-0000-1000-8000-00805f9b34fb";
54
55    private final BluetoothAdapter mAdapter;
56    private final BluetoothDevice mDevice;
57
58    private final Handler mParentHandler;
59
60    private final HandlerThread mHandlerThread;
61    private final Handler mSessionHandler;
62
63    private RfcommConnectThread mConnectThread;
64    private BluetoothPbapObexTransport mTransport;
65
66    private BluetoothPbapObexSession mObexSession;
67
68    private BluetoothPbapRequest mPendingRequest = null;
69
70    public BluetoothPbapSession(BluetoothDevice device, Handler handler) {
71
72        mAdapter = BluetoothAdapter.getDefaultAdapter();
73        if (mAdapter == null) {
74            throw new NullPointerException("No Bluetooth adapter in the system");
75        }
76
77        mDevice = device;
78        mParentHandler = handler;
79        mConnectThread = null;
80        mTransport = null;
81        mObexSession = null;
82
83        mHandlerThread = new HandlerThread("PBAP session handler",
84                Process.THREAD_PRIORITY_BACKGROUND);
85        mHandlerThread.start();
86        mSessionHandler = new Handler(mHandlerThread.getLooper(), this);
87    }
88
89    @Override
90    public boolean handleMessage(Message msg) {
91        Log.d(TAG, "Handler: msg: " + msg.what);
92
93        switch (msg.what) {
94            case RFCOMM_FAILED:
95                mConnectThread = null;
96
97                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
98
99                if (mPendingRequest != null) {
100                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
101                    mPendingRequest = null;
102                }
103                break;
104
105            case RFCOMM_CONNECTED:
106                mConnectThread = null;
107                mTransport = (BluetoothPbapObexTransport) msg.obj;
108                startObexSession();
109                break;
110
111            case BluetoothPbapObexSession.OBEX_SESSION_FAILED:
112                stopObexSession();
113
114                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
115
116                if (mPendingRequest != null) {
117                    mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
118                    mPendingRequest = null;
119                }
120                break;
121
122            case BluetoothPbapObexSession.OBEX_SESSION_CONNECTED:
123                mParentHandler.obtainMessage(SESSION_CONNECTED).sendToTarget();
124
125                if (mPendingRequest != null) {
126                    mObexSession.schedule(mPendingRequest);
127                    mPendingRequest = null;
128                }
129                break;
130
131            case BluetoothPbapObexSession.OBEX_SESSION_DISCONNECTED:
132                mParentHandler.obtainMessage(SESSION_DISCONNECTED).sendToTarget();
133                stopRfcomm();
134                break;
135
136            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_COMPLETED:
137                /* send to parent, process there */
138                mParentHandler.obtainMessage(REQUEST_COMPLETED, msg.obj).sendToTarget();
139                break;
140
141            case BluetoothPbapObexSession.OBEX_SESSION_REQUEST_FAILED:
142                /* send to parent, process there */
143                mParentHandler.obtainMessage(REQUEST_FAILED, msg.obj).sendToTarget();
144                break;
145
146            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_REQUEST:
147                /* send to parent, process there */
148                mParentHandler.obtainMessage(AUTH_REQUESTED).sendToTarget();
149
150                mSessionHandler
151                        .sendMessageDelayed(
152                                mSessionHandler
153                                        .obtainMessage(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT),
154                                30000);
155                break;
156
157            case BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT:
158                /* stop authentication */
159                setAuthResponse(null);
160
161                mParentHandler.obtainMessage(AUTH_TIMEOUT).sendToTarget();
162                break;
163
164            default:
165                return false;
166        }
167
168        return true;
169    }
170
171    public void start() {
172        Log.d(TAG, "start");
173
174        startRfcomm();
175    }
176
177    public void stop() {
178        Log.d(TAG, "Stop");
179
180        stopObexSession();
181        stopRfcomm();
182    }
183
184    public void abort() {
185        Log.d(TAG, "abort");
186
187        /* fail pending request immediately */
188        if (mPendingRequest != null) {
189            mParentHandler.obtainMessage(REQUEST_FAILED, mPendingRequest).sendToTarget();
190            mPendingRequest = null;
191        }
192
193        if (mObexSession != null) {
194            mObexSession.abort();
195        }
196    }
197
198    public boolean makeRequest(BluetoothPbapRequest request) {
199        Log.v(TAG, "makeRequest: " + request.getClass().getSimpleName());
200
201        if (mPendingRequest != null) {
202            Log.w(TAG, "makeRequest: request already queued, exiting");
203            return false;
204        }
205
206        if (mObexSession == null) {
207            mPendingRequest = request;
208
209            /*
210             * since there is no pending request and no session it's safe to
211             * assume that RFCOMM does not exist either and we should start
212             * connecting it
213             */
214            startRfcomm();
215
216            return true;
217        }
218
219        return mObexSession.schedule(request);
220    }
221
222    public boolean setAuthResponse(String key) {
223        Log.d(TAG, "setAuthResponse key=" + key);
224
225        mSessionHandler
226                .removeMessages(BluetoothPbapObexSession.OBEX_SESSION_AUTHENTICATION_TIMEOUT);
227
228        /* does not make sense to set auth response when OBEX session is down */
229        if (mObexSession == null) {
230            return false;
231        }
232
233        return mObexSession.setAuthReply(key);
234    }
235
236    private void startRfcomm() {
237        Log.d(TAG, "startRfcomm");
238
239        if (mConnectThread == null && mObexSession == null) {
240            mParentHandler.obtainMessage(SESSION_CONNECTING).sendToTarget();
241
242            mConnectThread = new RfcommConnectThread();
243            mConnectThread.start();
244        }
245
246        /*
247         * don't care if mConnectThread is not null - it means RFCOMM is being
248         * connected anyway
249         */
250    }
251
252    private void stopRfcomm() {
253        Log.d(TAG, "stopRfcomm");
254
255        if (mConnectThread != null) {
256            try {
257                mConnectThread.join();
258            } catch (InterruptedException e) {
259            }
260
261            mConnectThread = null;
262        }
263
264        if (mTransport != null) {
265            try {
266                mTransport.close();
267            } catch (IOException e) {
268            }
269
270            mTransport = null;
271        }
272    }
273
274    private void startObexSession() {
275        Log.d(TAG, "startObexSession");
276
277        mObexSession = new BluetoothPbapObexSession(mTransport);
278        mObexSession.start(mSessionHandler);
279    }
280
281    private void stopObexSession() {
282        Log.d(TAG, "stopObexSession");
283
284        if (mObexSession != null) {
285            mObexSession.stop();
286            mObexSession = null;
287        }
288    }
289
290    private class RfcommConnectThread extends Thread {
291        private static final String TAG = "RfcommConnectThread";
292
293        private BluetoothSocket mSocket;
294
295        public RfcommConnectThread() {
296            super("RfcommConnectThread");
297        }
298
299        @Override
300        public void run() {
301            if (mAdapter.isDiscovering()) {
302                mAdapter.cancelDiscovery();
303            }
304
305            try {
306                mSocket = mDevice.createRfcommSocketToServiceRecord(UUID.fromString(PBAP_UUID));
307                mSocket.connect();
308
309                BluetoothPbapObexTransport transport;
310                transport = new BluetoothPbapObexTransport(mSocket);
311
312                mSessionHandler.obtainMessage(RFCOMM_CONNECTED, transport).sendToTarget();
313            } catch (IOException e) {
314                closeSocket();
315                mSessionHandler.obtainMessage(RFCOMM_FAILED).sendToTarget();
316            }
317
318        }
319
320        private void closeSocket() {
321            try {
322                if (mSocket != null) {
323                    mSocket.close();
324                }
325            } catch (IOException e) {
326                Log.e(TAG, "Error when closing socket", e);
327            }
328        }
329    }
330}
331