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