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