BluetoothMnsObexClient.java revision 189cc4ca1c36018977e7b9ad62b07ddbefc7f2ba
1/* 2* Copyright (C) 2014 Samsung System LSI 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15package com.android.bluetooth.map; 16 17import android.bluetooth.BluetoothDevice; 18import android.bluetooth.BluetoothSocket; 19import android.bluetooth.SdpMnsRecord; 20import android.os.Handler; 21import android.os.HandlerThread; 22import android.os.Looper; 23import android.os.Message; 24import android.os.ParcelUuid; 25import android.util.Log; 26import android.util.SparseBooleanArray; 27 28import com.android.bluetooth.BluetoothObexTransport; 29 30import java.io.IOException; 31import java.io.OutputStream; 32 33import javax.obex.ClientOperation; 34import javax.obex.ClientSession; 35import javax.obex.HeaderSet; 36import javax.obex.ObexTransport; 37import javax.obex.ResponseCodes; 38 39/** 40 * The Message Notification Service class runs its own message handler thread, 41 * to avoid executing long operations on the MAP service Thread. 42 * This handler context is passed to the content observers, 43 * hence all call-backs (and thereby transmission of data) is executed 44 * from this thread. 45 */ 46public class BluetoothMnsObexClient { 47 48 private static final String TAG = "BluetoothMnsObexClient"; 49 private static final boolean D = BluetoothMapService.DEBUG; 50 private static final boolean V = BluetoothMapService.VERBOSE; 51 52 private ObexTransport mTransport; 53 public Handler mHandler = null; 54 private volatile boolean mWaitingForRemote; 55 private static final String TYPE_EVENT = "x-bt/MAP-event-report"; 56 private ClientSession mClientSession; 57 private boolean mConnected = false; 58 BluetoothDevice mRemoteDevice; 59 private SparseBooleanArray mRegisteredMasIds = new SparseBooleanArray(1); 60 61 private HeaderSet mHsConnect = null; 62 private Handler mCallback = null; 63 private final SdpMnsRecord mMnsRecord; 64 // Used by the MAS to forward notification registrations 65 public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1; 66 public static final int MSG_MNS_SEND_EVENT = 2; 67 68 69 public static final ParcelUuid BLUETOOTH_UUID_OBEX_MNS = 70 ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); 71 72 73 public BluetoothMnsObexClient(BluetoothDevice remoteDevice, 74 SdpMnsRecord mnsRecord, Handler callback) { 75 if (remoteDevice == null) { 76 throw new NullPointerException("Obex transport is null"); 77 } 78 mRemoteDevice = remoteDevice; 79 HandlerThread thread = new HandlerThread("BluetoothMnsObexClient"); 80 thread.start(); 81 /* This will block until the looper have started, hence it will be safe to use it, 82 when the constructor completes */ 83 Looper looper = thread.getLooper(); 84 mHandler = new MnsObexClientHandler(looper); 85 mCallback = callback; 86 mMnsRecord = mnsRecord; 87 } 88 89 public Handler getMessageHandler() { 90 return mHandler; 91 } 92 93 private final class MnsObexClientHandler extends Handler { 94 private MnsObexClientHandler(Looper looper) { 95 super(looper); 96 } 97 98 @Override 99 public void handleMessage(Message msg) { 100 switch (msg.what) { 101 case MSG_MNS_NOTIFICATION_REGISTRATION: 102 handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/); 103 break; 104 case MSG_MNS_SEND_EVENT: 105 sendEventHandler((byte[])msg.obj/*byte[]*/, msg.arg1 /*masId*/); 106 break; 107 default: 108 break; 109 } 110 } 111 } 112 113 public boolean isConnected() { 114 return mConnected; 115 } 116 117 /** 118 * Disconnect the connection to MNS server. 119 * Call this when the MAS client requests a de-registration on events. 120 */ 121 public synchronized void disconnect() { 122 try { 123 if (mClientSession != null) { 124 mClientSession.disconnect(null); 125 if (D) Log.d(TAG, "OBEX session disconnected"); 126 } 127 } catch (IOException e) { 128 Log.w(TAG, "OBEX session disconnect error " + e.getMessage()); 129 } 130 try { 131 if (mClientSession != null) { 132 if (D) Log.d(TAG, "OBEX session close mClientSession"); 133 mClientSession.close(); 134 mClientSession = null; 135 if (D) Log.d(TAG, "OBEX session closed"); 136 } 137 } catch (IOException e) { 138 Log.w(TAG, "OBEX session close error:" + e.getMessage()); 139 } 140 if (mTransport != null) { 141 try { 142 if (D) Log.d(TAG, "Close Obex Transport"); 143 mTransport.close(); 144 mTransport = null; 145 mConnected = false; 146 if (D) Log.d(TAG, "Obex Transport Closed"); 147 } catch (IOException e) { 148 Log.e(TAG, "mTransport.close error: " + e.getMessage()); 149 } 150 } 151 } 152 153 /** 154 * Shutdown the MNS. 155 */ 156 public synchronized void shutdown() { 157 /* should shutdown handler thread first to make sure 158 * handleRegistration won't be called when disconnect 159 */ 160 if (mHandler != null) { 161 // Shut down the thread 162 mHandler.removeCallbacksAndMessages(null); 163 Looper looper = mHandler.getLooper(); 164 if (looper != null) { 165 looper.quit(); 166 } 167 mHandler = null; 168 } 169 170 /* Disconnect if connected */ 171 disconnect(); 172 173 mRegisteredMasIds.clear(); 174 } 175 176 /** 177 * We store a list of registered MasIds only to control connect/disconnect 178 * @param masId 179 * @param notificationStatus 180 */ 181 public synchronized void handleRegistration(int masId, int notificationStatus){ 182 if(D) Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")"); 183 184 if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) { 185 mRegisteredMasIds.delete(masId); 186 } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { 187 /* Connect if we do not have a connection, and start the content observers providing 188 * this thread as Handler. 189 */ 190 if(isConnected() == false) { 191 if(D) Log.d(TAG, "handleRegistration: connect"); 192 connect(); 193 } 194 mRegisteredMasIds.put(masId, true); // We don't use the value for anything 195 } 196 if(mRegisteredMasIds.size() == 0) { 197 // No more registrations - disconnect 198 if(D) Log.d(TAG, "handleRegistration: disconnect"); 199 disconnect(); 200 } 201 } 202 203 public void connect() { 204 205 mConnected = true; 206 207 BluetoothSocket btSocket = null; 208 try { 209 // TODO: Do SDP record search again? 210 if(mMnsRecord != null && mMnsRecord.getL2capPsm() > 0) { 211 // Do L2CAP connect 212 btSocket = mRemoteDevice.createL2capSocket(mMnsRecord.getL2capPsm()); 213 214 } else if (mMnsRecord != null && mMnsRecord.getRfcommChannelNumber() > 0) { 215 // Do Rfcomm connect 216 btSocket = mRemoteDevice.createRfcommSocket(mMnsRecord.getRfcommChannelNumber()); 217 } else { 218 // This should not happen... 219 Log.e(TAG, "Invalid SDP content - attempt a connect to UUID..."); 220 // TODO: Why insecure? - is it because the link is already encrypted? 221 btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord( 222 BLUETOOTH_UUID_OBEX_MNS.getUuid()); 223 } 224 btSocket.connect(); 225 } catch (IOException e) { 226 Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e); 227 // TODO: do we need to report error somewhere? 228 mConnected = false; 229 return; 230 } 231 232 mTransport = new BluetoothObexTransport(btSocket); 233 234 try { 235 mClientSession = new ClientSession(mTransport); 236 } catch (IOException e1) { 237 Log.e(TAG, "OBEX session create error " + e1.getMessage()); 238 mConnected = false; 239 } 240 if (mConnected && mClientSession != null) { 241 boolean connected = false; 242 HeaderSet hs = new HeaderSet(); 243 // bb582b41-420c-11db-b0de-0800200c9a66 244 byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41, 245 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb, 246 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00, 247 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 }; 248 hs.setHeader(HeaderSet.TARGET, mnsTarget); 249 250 synchronized (this) { 251 mWaitingForRemote = true; 252 } 253 try { 254 mHsConnect = mClientSession.connect(hs); 255 if (D) Log.d(TAG, "OBEX session created"); 256 connected = true; 257 } catch (IOException e) { 258 Log.e(TAG, "OBEX session connect error " + e.getMessage()); 259 } 260 mConnected = connected; 261 } 262 synchronized (this) { 263 mWaitingForRemote = false; 264 } 265 } 266 267 /** 268 * Call this method to queue an event report to be send to the MNS server. 269 * @param eventBytes the encoded event data. 270 * @param masInstanceId the MasId of the instance sending the event. 271 */ 272 public void sendEvent(byte[] eventBytes, int masInstanceId) { 273 // We need to check for null, to handle shutdown. 274 if(mHandler != null) { 275 Message msg = mHandler.obtainMessage(MSG_MNS_SEND_EVENT, masInstanceId, 0, eventBytes); 276 if(msg != null) { 277 msg.sendToTarget(); 278 } 279 } 280 notifyUpdateWakeLock(); 281 } 282 283 private int sendEventHandler(byte[] eventBytes, int masInstanceId) { 284 285 boolean error = false; 286 int responseCode = -1; 287 HeaderSet request; 288 int maxChunkSize, bytesToWrite, bytesWritten = 0; 289 ClientSession clientSession = mClientSession; 290 291 if ((!mConnected) || (clientSession == null)) { 292 Log.w(TAG, "sendEvent after disconnect:" + mConnected); 293 return responseCode; 294 } 295 296 request = new HeaderSet(); 297 BluetoothMapAppParams appParams = new BluetoothMapAppParams(); 298 appParams.setMasInstanceId(masInstanceId); 299 300 ClientOperation putOperation = null; 301 OutputStream outputStream = null; 302 303 try { 304 request.setHeader(HeaderSet.TYPE, TYPE_EVENT); 305 request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams()); 306 307 if (mHsConnect.mConnectionID != null) { 308 request.mConnectionID = new byte[4]; 309 System.arraycopy(mHsConnect.mConnectionID, 0, request.mConnectionID, 0, 4); 310 } else { 311 Log.w(TAG, "sendEvent: no connection ID"); 312 } 313 314 synchronized (this) { 315 mWaitingForRemote = true; 316 } 317 // Send the header first and then the body 318 try { 319 if (V) Log.v(TAG, "Send headerset Event "); 320 putOperation = (ClientOperation)clientSession.put(request); 321 // TODO - Should this be kept or Removed 322 323 } catch (IOException e) { 324 Log.e(TAG, "Error when put HeaderSet " + e.getMessage()); 325 error = true; 326 } 327 synchronized (this) { 328 mWaitingForRemote = false; 329 } 330 if (!error) { 331 try { 332 if (V) Log.v(TAG, "Send headerset Event "); 333 outputStream = putOperation.openOutputStream(); 334 } catch (IOException e) { 335 Log.e(TAG, "Error when opening OutputStream " + e.getMessage()); 336 error = true; 337 } 338 } 339 340 if (!error) { 341 342 maxChunkSize = putOperation.getMaxPacketSize(); 343 344 while (bytesWritten < eventBytes.length) { 345 bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten); 346 outputStream.write(eventBytes, bytesWritten, bytesToWrite); 347 bytesWritten += bytesToWrite; 348 } 349 350 if (bytesWritten == eventBytes.length) { 351 Log.i(TAG, "SendEvent finished send length" + eventBytes.length); 352 } else { 353 error = true; 354 putOperation.abort(); 355 Log.i(TAG, "SendEvent interrupted"); 356 } 357 } 358 } catch (IOException e) { 359 handleSendException(e.toString()); 360 error = true; 361 } catch (IndexOutOfBoundsException e) { 362 handleSendException(e.toString()); 363 error = true; 364 } finally { 365 try { 366 if (outputStream != null) { 367 outputStream.close(); 368 } 369 } catch (IOException e) { 370 Log.e(TAG, "Error when closing stream after send " + e.getMessage()); 371 } 372 try { 373 if ((!error) && (putOperation != null)) { 374 responseCode = putOperation.getResponseCode(); 375 if (responseCode != -1) { 376 if (V) Log.v(TAG, "Put response code " + responseCode); 377 if (responseCode != ResponseCodes.OBEX_HTTP_OK) { 378 Log.i(TAG, "Response error code is " + responseCode); 379 } 380 } 381 } 382 if (putOperation != null) { 383 putOperation.close(); 384 } 385 } catch (IOException e) { 386 Log.e(TAG, "Error when closing stream after send " + e.getMessage()); 387 } 388 } 389 390 return responseCode; 391 } 392 393 private void handleSendException(String exception) { 394 Log.e(TAG, "Error when sending event: " + exception); 395 } 396 397 private void notifyUpdateWakeLock() { 398 if(mCallback != null) { 399 Message msg = Message.obtain(mCallback); 400 msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK; 401 msg.sendToTarget(); 402 } 403 } 404} 405