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 17 package com.android.bluetooth.pbapclient; 18 19 import android.os.Handler; 20 import android.os.HandlerThread; 21 import android.os.Looper; 22 import android.os.Message; 23 import android.util.Log; 24 25 import java.io.IOException; 26 import java.lang.ref.WeakReference; 27 28 import javax.obex.ClientSession; 29 import javax.obex.HeaderSet; 30 import javax.obex.ObexTransport; 31 import javax.obex.ResponseCodes; 32 33 final 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 BluetoothPbapObexSession(ObexTransport transport)67 public BluetoothPbapObexSession(ObexTransport transport) { 68 mTransport = transport; 69 } 70 start(Handler handler)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 stop()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 abort()109 public void abort() { 110 stop(); 111 } 112 schedule(BluetoothPbapRequest request)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 isConnected()126 public int isConnected() { 127 return mState; 128 } 129 connect()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 disconnect(boolean callback)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 executeRequest(BluetoothPbapRequest req)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 setAuthReply(String key)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 ObexClientHandler(Looper looper, BluetoothPbapObexSession inst)222 ObexClientHandler(Looper looper, BluetoothPbapObexSession inst) { 223 super(looper); 224 mInst = new WeakReference<BluetoothPbapObexSession>(inst); 225 } 226 227 @Override handleMessage(Message msg)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