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.os.Handler;
20import android.util.Log;
21
22import java.io.IOException;
23
24import javax.obex.ClientSession;
25import javax.obex.HeaderSet;
26import javax.obex.ObexTransport;
27import javax.obex.ResponseCodes;
28
29final class BluetoothPbapObexSession {
30    private static final String TAG = "BluetoothPbapObexSession";
31
32    private static final byte[] PBAP_TARGET = new byte[] {
33            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
34            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
35    };
36
37    final static int OBEX_SESSION_CONNECTED = 100;
38    final static int OBEX_SESSION_FAILED = 101;
39    final static int OBEX_SESSION_DISCONNECTED = 102;
40    final static int OBEX_SESSION_REQUEST_COMPLETED = 103;
41    final static int OBEX_SESSION_REQUEST_FAILED = 104;
42    final static int OBEX_SESSION_AUTHENTICATION_REQUEST = 105;
43    final static int OBEX_SESSION_AUTHENTICATION_TIMEOUT = 106;
44
45    private Handler mSessionHandler;
46    private final ObexTransport mTransport;
47    private ObexClientThread mObexClientThread;
48    private BluetoothPbapObexAuthenticator mAuth = null;
49
50    public BluetoothPbapObexSession(ObexTransport transport) {
51        mTransport = transport;
52    }
53
54    public void start(Handler handler) {
55        Log.d(TAG, "start");
56        mSessionHandler = handler;
57
58        mAuth = new BluetoothPbapObexAuthenticator(mSessionHandler);
59
60        mObexClientThread = new ObexClientThread();
61        mObexClientThread.start();
62    }
63
64    public void stop() {
65        Log.d(TAG, "stop");
66
67        if (mObexClientThread != null) {
68            try {
69                mObexClientThread.interrupt();
70                mObexClientThread.join();
71                mObexClientThread = null;
72            } catch (InterruptedException e) {
73            }
74        }
75    }
76
77    public void abort() {
78        Log.d(TAG, "abort");
79
80        if (mObexClientThread != null && mObexClientThread.mRequest != null) {
81            /*
82             * since abort may block until complete GET is processed inside OBEX
83             * session, let's run it in separate thread so it won't block UI
84             */
85            (new Thread() {
86                @Override
87                public void run() {
88                    mObexClientThread.mRequest.abort();
89                }
90            }).run();
91        }
92    }
93
94    public boolean schedule(BluetoothPbapRequest request) {
95        Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
96
97        if (mObexClientThread == null) {
98            Log.e(TAG, "OBEX session not started");
99            return false;
100        }
101
102        return mObexClientThread.schedule(request);
103    }
104
105    public boolean setAuthReply(String key) {
106        Log.d(TAG, "setAuthReply key=" + key);
107
108        if (mAuth == null) {
109            return false;
110        }
111
112        mAuth.setReply(key);
113
114        return true;
115    }
116
117    private class ObexClientThread extends Thread {
118
119        private static final String TAG = "ObexClientThread";
120
121        private ClientSession mClientSession;
122        private BluetoothPbapRequest mRequest;
123
124        private volatile boolean mRunning = true;
125
126        public ObexClientThread() {
127
128            mClientSession = null;
129            mRequest = null;
130        }
131
132        @Override
133        public void run() {
134            super.run();
135
136            if (!connect()) {
137                mSessionHandler.obtainMessage(OBEX_SESSION_FAILED).sendToTarget();
138                return;
139            }
140
141            mSessionHandler.obtainMessage(OBEX_SESSION_CONNECTED).sendToTarget();
142
143            while (mRunning) {
144                synchronized (this) {
145                    try {
146                        if (mRequest == null) {
147                            this.wait();
148                        }
149                    } catch (InterruptedException e) {
150                        mRunning = false;
151                        break;
152                    }
153                }
154
155                if (mRunning && mRequest != null) {
156                    try {
157                        mRequest.execute(mClientSession);
158                    } catch (IOException e) {
159                        // this will "disconnect" for cleanup
160                        mRunning = false;
161                    }
162
163                    if (mRequest.isSuccess()) {
164                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_COMPLETED, mRequest)
165                                .sendToTarget();
166                    } else {
167                        mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_FAILED, mRequest)
168                                .sendToTarget();
169                    }
170                }
171
172                mRequest = null;
173            }
174
175            disconnect();
176
177            mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
178        }
179
180        public synchronized boolean schedule(BluetoothPbapRequest request) {
181            Log.d(TAG, "schedule: " + request.getClass().getSimpleName());
182
183            if (mRequest != null) {
184                return false;
185            }
186
187            mRequest = request;
188            notify();
189
190            return true;
191        }
192
193        private boolean connect() {
194            Log.d(TAG, "connect");
195
196            try {
197                mClientSession = new ClientSession(mTransport);
198                mClientSession.setAuthenticator(mAuth);
199            } catch (IOException e) {
200                return false;
201            }
202
203            HeaderSet hs = new HeaderSet();
204            hs.setHeader(HeaderSet.TARGET, PBAP_TARGET);
205
206            try {
207                hs = mClientSession.connect(hs);
208
209                if (hs.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
210                    disconnect();
211                    return false;
212                }
213            } catch (IOException e) {
214                return false;
215            }
216
217            return true;
218        }
219
220        private void disconnect() {
221            Log.d(TAG, "disconnect");
222
223            if (mClientSession != null) {
224                try {
225                    mClientSession.disconnect(null);
226                    mClientSession.close();
227                } catch (IOException e) {
228                }
229            }
230        }
231    }
232}
233