1/*
2 * Copyright (C) 2016 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 com.android.bluetooth.pbapclient;
18
19import android.os.Handler;
20import android.os.HandlerThread;
21import android.os.Looper;
22import android.os.Message;
23import android.util.Log;
24
25import java.io.IOException;
26import java.lang.ref.WeakReference;
27
28import javax.obex.ClientSession;
29import javax.obex.HeaderSet;
30import javax.obex.ObexTransport;
31import javax.obex.ResponseCodes;
32
33final class BluetoothPbapObexSession {
34    private static final boolean DBG = true;
35    private static final String TAG = "BluetoothPbapObexSession";
36
37    private static final byte[] PBAP_TARGET = new byte[] {
38            0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66,
39            0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
40    };
41
42    final static int OBEX_SESSION_CONNECTED = 100;
43    final static int OBEX_SESSION_FAILED = 101;
44    final static int OBEX_SESSION_DISCONNECTED = 102;
45    final static int OBEX_SESSION_REQUEST_COMPLETED = 103;
46    final static int OBEX_SESSION_REQUEST_FAILED = 104;
47    final static int OBEX_SESSION_AUTHENTICATION_REQUEST = 105;
48    final static int OBEX_SESSION_AUTHENTICATION_TIMEOUT = 106;
49
50    final static int MSG_CONNECT = 0;
51    final static int MSG_REQUEST = 1;
52
53    final static int CONNECTED = 0;
54    final static int CONNECTING = 1;
55    final static int DISCONNECTED = 2;
56
57    private Handler mSessionHandler;
58    private final ObexTransport mTransport;
59    // private ObexClientThread mObexClientThread;
60    private BluetoothPbapObexAuthenticator mAuth = null;
61    private HandlerThread mThread;
62    private Handler mHandler;
63    private ClientSession mClientSession;
64
65    private int mState = DISCONNECTED;
66
67    public BluetoothPbapObexSession(ObexTransport transport) {
68        mTransport = transport;
69    }
70
71    public synchronized boolean start(Handler handler) {
72        Log.d(TAG, "start");
73
74        if (mState == CONNECTED || mState == CONNECTING) {
75            return false;
76        }
77        mState = CONNECTING;
78        mSessionHandler = handler;
79
80        mAuth = new BluetoothPbapObexAuthenticator(mSessionHandler);
81
82        // Start the thread to process requests (see {@link schedule()}.
83        mThread = new HandlerThread("BluetoothPbapObexSessionThread");
84        mThread.start();
85        mHandler = new ObexClientHandler(mThread.getLooper(), this);
86
87        // Make connect call non blocking.
88        boolean status = mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECT));
89        if (!status) {
90            mState = DISCONNECTED;
91            return false;
92        } else {
93            return true;
94        }
95    }
96
97    public void stop() {
98        if (DBG) {
99            Log.d(TAG, "stop");
100        }
101
102        // This will essentially stop the handler and ignore any inflight requests.
103        mThread.quit();
104
105        // We clean up the state here.
106        disconnect(false /* no callback */);
107    }
108
109    public void abort() {
110        stop();
111    }
112
113    public boolean schedule(BluetoothPbapRequest request) {
114        if (DBG) {
115            Log.d(TAG, "schedule() called with: " + request);
116        }
117
118        boolean status = mHandler.sendMessage(mHandler.obtainMessage(MSG_REQUEST, request));
119        if (!status) {
120            Log.e(TAG, "Adding messages failed, obex must be disconnected.");
121            return false;
122        }
123        return true;
124    }
125
126    public int isConnected() {
127        return mState;
128    }
129
130    private void connect() {
131       if (DBG) {
132          Log.d(TAG, "connect()");
133       }
134
135       boolean success = true;
136       try {
137          mClientSession = new ClientSession(mTransport);
138          mClientSession.setAuthenticator(mAuth);
139       } catch (IOException e) {
140          Log.d(TAG, "connect() exception: " + e);
141          success = false;
142       }
143
144       HeaderSet hs = new HeaderSet();
145       hs.setHeader(HeaderSet.TARGET, PBAP_TARGET);
146       try {
147          hs = mClientSession.connect(hs);
148
149          if (hs.getResponseCode() != ResponseCodes.OBEX_HTTP_OK) {
150              disconnect(true  /* callback */);
151              success = false;
152          }
153       } catch (IOException e) {
154          success = false;
155       }
156
157       synchronized (this) {
158           if (success) {
159              mSessionHandler.obtainMessage(OBEX_SESSION_CONNECTED).sendToTarget();
160              mState = CONNECTED;
161           } else {
162              mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
163              mState = DISCONNECTED;
164           }
165       }
166    }
167
168    private synchronized void disconnect(boolean callback) {
169        if (DBG) {
170            Log.d(TAG, "disconnect()");
171        }
172
173        if (mState != DISCONNECTED) {
174            return;
175        }
176
177        if (mClientSession != null) {
178            try {
179                mClientSession.disconnect(null);
180                mClientSession.close();
181            } catch (IOException e) {
182            }
183        }
184
185        if (callback) {
186            mSessionHandler.obtainMessage(OBEX_SESSION_DISCONNECTED).sendToTarget();
187        }
188
189        mState = DISCONNECTED;
190    }
191
192    private void executeRequest(BluetoothPbapRequest req) {
193        try {
194            req.execute(mClientSession);
195        } catch (IOException e) {
196            Log.e(TAG, "Error during executeRequest " + e);
197            disconnect(true);
198        }
199
200        if (req.isSuccess()) {
201            mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_COMPLETED, req).sendToTarget();
202        } else {
203            mSessionHandler.obtainMessage(OBEX_SESSION_REQUEST_FAILED, req).sendToTarget();
204        }
205    }
206
207    public boolean setAuthReply(String key) {
208        Log.d(TAG, "setAuthReply key=" + key);
209
210        if (mAuth == null) {
211            return false;
212        }
213
214        mAuth.setReply(key);
215
216        return true;
217    }
218
219    private static class ObexClientHandler extends Handler {
220        WeakReference<BluetoothPbapObexSession> mInst;
221
222        ObexClientHandler(Looper looper, BluetoothPbapObexSession inst) {
223            super(looper);
224            mInst = new WeakReference<BluetoothPbapObexSession>(inst);
225        }
226
227        @Override
228        public void handleMessage(Message msg) {
229            BluetoothPbapObexSession inst = mInst.get();
230            if (inst == null) {
231                Log.e(TAG, "The instance class is no longer around; terminating.");
232                return;
233            }
234
235            if (inst.isConnected() != CONNECTED && msg.what != MSG_CONNECT) {
236                Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
237                return;
238            }
239
240            switch (msg.what) {
241                case MSG_CONNECT:
242                    inst.connect();
243                    break;
244                case MSG_REQUEST:
245                    inst.executeRequest((BluetoothPbapRequest) msg.obj);
246                    break;
247                default:
248                    Log.e(TAG, "Unknwown message type: " + msg.what);
249            }
250        }
251    }
252}
253
254