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