BluetoothMnsObexClient.java revision 9679a425747e95082e169b3bd3673ed6b5a27590
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 public void disconnect() { 116 /* should shutdown handler thread first to make sure 117 * handleRegistration won't be called when disconnet 118 */ 119 if (mHandler != null) { 120 // Shut down the thread 121 mHandler.removeCallbacksAndMessages(null); 122 Looper looper = mHandler.getLooper(); 123 if (looper != null) { 124 looper.quit(); 125 } 126 mHandler = null; 127 } 128 try { 129 if (mClientSession != null) { 130 mClientSession.disconnect(null); 131 if (D) Log.d(TAG, "OBEX session disconnected"); 132 } 133 } catch (IOException e) { 134 Log.w(TAG, "OBEX session disconnect error " + e.getMessage()); 135 } 136 try { 137 if (mClientSession != null) { 138 if (D) Log.d(TAG, "OBEX session close mClientSession"); 139 mClientSession.close(); 140 mClientSession = null; 141 if (D) Log.d(TAG, "OBEX session closed"); 142 } 143 } catch (IOException e) { 144 Log.w(TAG, "OBEX session close error:" + e.getMessage()); 145 } 146 if (mTransport != null) { 147 try { 148 if (D) Log.d(TAG, "Close Obex Transport"); 149 mTransport.close(); 150 mTransport = null; 151 mConnected = false; 152 if (D) Log.d(TAG, "Obex Transport Closed"); 153 } catch (IOException e) { 154 Log.e(TAG, "mTransport.close error: " + e.getMessage()); 155 } 156 } 157 if(mObserverRegistered) { 158 mObserver.unregisterObserver(); 159 mObserverRegistered = false; 160 } 161 if (mObserver != null) { 162 mObserver.deinit(); 163 mObserver = null; 164 } 165 } 166 167 private HeaderSet hsConnect = null; 168 169 public void handleRegistration(int masId, int notificationStatus){ 170 Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")"); 171 172 if((isConnected() == false) && 173 (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES)) { 174 Log.d(TAG, "handleRegistration: connect"); 175 connect(); 176 } 177 178 if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) { 179 // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this. 180 if(mObserverRegistered == true) { 181 mObserver.unregisterObserver(); 182 mObserverRegistered = false; 183 } 184 } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) { 185 /* Connect if we do not have a connection, and start the content observers providing 186 * this thread as Handler. 187 */ 188 if(mObserverRegistered == false) { 189 mObserver.registerObserver(this, masId); 190 mObserverRegistered = true; 191 } 192 } 193 } 194 195 public void connect() { 196 Log.d(TAG, "handleRegistration: connect 2"); 197 198 BluetoothSocket btSocket = null; 199 try { 200 btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord( 201 BluetoothUuid_ObexMns.getUuid()); 202 btSocket.connect(); 203 } catch (IOException e) { 204 Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e); 205 // TODO: do we need to report error somewhere? 206 return; 207 } 208 209 mTransport = new BluetoothMnsRfcommTransport(btSocket); 210 211 try { 212 mClientSession = new ClientSession(mTransport); 213 mConnected = true; 214 } catch (IOException e1) { 215 Log.e(TAG, "OBEX session create error " + e1.getMessage()); 216 } 217 if (mConnected && mClientSession != null) { 218 mConnected = false; 219 HeaderSet hs = new HeaderSet(); 220 // bb582b41-420c-11db-b0de-0800200c9a66 221 byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41, 222 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb, 223 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00, 224 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 }; 225 hs.setHeader(HeaderSet.TARGET, mnsTarget); 226 227 synchronized (this) { 228 mWaitingForRemote = true; 229 } 230 try { 231 hsConnect = mClientSession.connect(hs); 232 if (D) Log.d(TAG, "OBEX session created"); 233 mConnected = true; 234 } catch (IOException e) { 235 Log.e(TAG, "OBEX session connect error " + e.getMessage()); 236 } 237 } 238 synchronized (this) { 239 mWaitingForRemote = false; 240 } 241 } 242 243 public int sendEvent(byte[] eventBytes, int masInstanceId) { 244 245 boolean error = false; 246 int responseCode = -1; 247 HeaderSet request; 248 int maxChunkSize, bytesToWrite, bytesWritten = 0; 249 ClientSession clientSession = mClientSession; 250 251 if ((!mConnected) || (clientSession == null)) { 252 Log.w(TAG, "sendEvent after disconnect:" + mConnected); 253 return responseCode; 254 } 255 256 request = new HeaderSet(); 257 BluetoothMapAppParams appParams = new BluetoothMapAppParams(); 258 appParams.setMasInstanceId(masInstanceId); 259 260 ClientOperation putOperation = null; 261 OutputStream outputStream = null; 262 263 try { 264 request.setHeader(HeaderSet.TYPE, TYPE_EVENT); 265 request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams()); 266 267 if (hsConnect.mConnectionID != null) { 268 request.mConnectionID = new byte[4]; 269 System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4); 270 } else { 271 Log.w(TAG, "sendEvent: no connection ID"); 272 } 273 274 synchronized (this) { 275 mWaitingForRemote = true; 276 } 277 // Send the header first and then the body 278 try { 279 if (V) Log.v(TAG, "Send headerset Event "); 280 putOperation = (ClientOperation)clientSession.put(request); 281 // TODO - Should this be kept or Removed 282 283 } catch (IOException e) { 284 Log.e(TAG, "Error when put HeaderSet " + e.getMessage()); 285 error = true; 286 } 287 synchronized (this) { 288 mWaitingForRemote = false; 289 } 290 if (!error) { 291 try { 292 if (V) Log.v(TAG, "Send headerset Event "); 293 outputStream = putOperation.openOutputStream(); 294 } catch (IOException e) { 295 Log.e(TAG, "Error when opening OutputStream " + e.getMessage()); 296 error = true; 297 } 298 } 299 300 if (!error) { 301 302 maxChunkSize = putOperation.getMaxPacketSize(); 303 304 while (bytesWritten < eventBytes.length) { 305 bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten); 306 outputStream.write(eventBytes, bytesWritten, bytesToWrite); 307 bytesWritten += bytesToWrite; 308 } 309 310 if (bytesWritten == eventBytes.length) { 311 Log.i(TAG, "SendEvent finished send length" + eventBytes.length); 312 } else { 313 error = true; 314 putOperation.abort(); 315 Log.i(TAG, "SendEvent interrupted"); 316 } 317 } 318 } catch (IOException e) { 319 handleSendException(e.toString()); 320 error = true; 321 } catch (IndexOutOfBoundsException e) { 322 handleSendException(e.toString()); 323 error = true; 324 } finally { 325 try { 326 if (outputStream != null) { 327 outputStream.close(); 328 } 329 } catch (IOException e) { 330 Log.e(TAG, "Error when closing stream after send " + e.getMessage()); 331 } 332 try { 333 if ((!error) && (putOperation != null)) { 334 responseCode = putOperation.getResponseCode(); 335 if (responseCode != -1) { 336 if (V) Log.v(TAG, "Put response code " + responseCode); 337 if (responseCode != ResponseCodes.OBEX_HTTP_OK) { 338 Log.i(TAG, "Response error code is " + responseCode); 339 } 340 } 341 } 342 if (putOperation != null) { 343 putOperation.close(); 344 } 345 } catch (IOException e) { 346 Log.e(TAG, "Error when closing stream after send " + e.getMessage()); 347 } 348 } 349 350 return responseCode; 351 } 352 353 private void handleSendException(String exception) { 354 Log.e(TAG, "Error when sending event: " + exception); 355 } 356} 357