BluetoothMnsObexClient.java revision 70be005a18a35ec5fcb46152f0dfbe82156efa3a
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.Looper; 22import android.os.Message; 23import android.os.ParcelUuid; 24import android.util.Log; 25 26import java.io.BufferedInputStream; 27import java.io.File; 28import java.io.FileInputStream; 29import java.io.IOException; 30import java.io.OutputStream; 31 32import javax.obex.ApplicationParameter; 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 extends Thread{ 47 48 private static final String TAG = "BluetoothMnsObexClient"; 49 private static final boolean D = false; 50 private static final boolean V = false; 51 52 public final static int MSG_SESSION_ERROR = 1; 53 public final static int MSG_CONNECT_TIMEOUT = 2; 54 55 private ObexTransport mTransport; 56 private Context mContext; 57 public static Handler mHandler = null; 58 private volatile boolean mWaitingForRemote; 59 private static final String TYPE_EVENT = "x-bt/MAP-event-report"; 60 private ClientSession mClientSession; 61 private boolean mConnected = false; 62 BluetoothDevice mRemoteDevice; 63 private static BluetoothMapContentObserver mObserver; 64 private boolean mObserverRegistered = false; 65 66 private Looper mLooper = null; 67 // Used by the MAS to forward notification registrations 68 public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1; 69 70 71 public static final ParcelUuid BluetoothUuid_ObexMns = 72 ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB"); 73 74 75 public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice) { 76 if (remoteDevice == null) { 77 throw new NullPointerException("Obex transport is null"); 78 } 79 mContext = context; 80 mRemoteDevice = remoteDevice; 81 } 82 83 public static Handler getMessageHandler() { 84 // TODO: if mHandle is null, we should wait for it to be created. 85 return mHandler; 86 } 87 88 public static BluetoothMapContentObserver getContentObserver() { 89 return mObserver; 90 } 91 92 @Override 93 public void run() { 94 Looper.prepare(); 95 mLooper = Looper.myLooper(); 96 97 98 /* Create the context observer from within the thread to ensure the "content changed" 99 * events are handled in this thread. */ 100 mObserver = new BluetoothMapContentObserver(mContext); 101 mObserver.init(); 102 103 mHandler = new Handler() { 104 public void handleMessage(Message msg) { 105 switch (msg.what) { 106 case MSG_MNS_NOTIFICATION_REGISTRATION: 107 handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/); 108 break; 109 default: 110 break; 111 } 112 } 113 }; 114 Looper.loop(); 115 } 116 117 public boolean isConnected() { 118 return mConnected; 119 } 120 121 public 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 if(mObserverRegistered) { 152 mObserver.unregisterObserver(); 153 mObserverRegistered = false; 154 } 155 156 mObserver.deinit(); 157 // Shut down the thread 158 if(mLooper != null) 159 mLooper.quit(); 160 interrupt(); 161 try { 162 join(); 163 } catch (InterruptedException e) { 164 if(V) Log.w(TAG, "got interrupted. Probably a connection shutdown"); 165 } 166 mHandler = null; 167 mObserver = null; 168 } 169 170 private HeaderSet hsConnect = null; 171 172 public void handleRegistration(int masId, int notificationStatus){ 173 Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")"); 174 175 if(isConnected() == false) { 176 Log.d(TAG, "handleRegistration: connect"); 177 connect(); 178 } 179 180 if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) { 181 // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this. 182 if(mObserverRegistered == true) { 183 mObserver.unregisterObserver(); 184 mObserverRegistered = false; 185 } 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(mObserverRegistered == false) { 191 mObserver.registerObserver(this, masId); 192 mObserverRegistered = true; 193 } 194 } 195 } 196 197 public void connect() { 198 Log.d(TAG, "handleRegistration: connect 2"); 199 200 BluetoothSocket btSocket = null; 201 try { 202 btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord( 203 BluetoothUuid_ObexMns.getUuid()); 204 btSocket.connect(); 205 } catch (IOException e) { 206 Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e); 207 // TODO: do we need to report error somewhere? 208 return; 209 } 210 211 mTransport = new BluetoothMnsRfcommTransport(btSocket); 212 213 try { 214 mClientSession = new ClientSession(mTransport); 215 mConnected = true; 216 } catch (IOException e1) { 217 Log.e(TAG, "OBEX session create error " + e1.getMessage()); 218 } 219 if (mConnected && mClientSession != null) { 220 mConnected = false; 221 HeaderSet hs = new HeaderSet(); 222 // bb582b41-420c-11db-b0de-0800200c9a66 223 byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41, 224 (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb, 225 (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00, 226 (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 }; 227 hs.setHeader(HeaderSet.TARGET, mnsTarget); 228 229 synchronized (this) { 230 mWaitingForRemote = true; 231 } 232 try { 233 hsConnect = mClientSession.connect(hs); 234 if (D) Log.d(TAG, "OBEX session created"); 235 mConnected = true; 236 } catch (IOException e) { 237 Log.e(TAG, "OBEX session connect error " + e.getMessage()); 238 } 239 } 240 synchronized (this) { 241 mWaitingForRemote = false; 242 } 243 } 244 245 public int sendEvent(byte[] eventBytes, int masInstanceId) { 246 247 boolean error = false; 248 int responseCode = -1; 249 HeaderSet request; 250 int maxChunkSize, bytesToWrite, bytesWritten = 0; 251 request = new HeaderSet(); 252 BluetoothMapAppParams appParams = new BluetoothMapAppParams(); 253 appParams.setMasInstanceId(masInstanceId); 254 255 ClientOperation putOperation = null; 256 OutputStream outputStream = null; 257 258 try { 259 request.setHeader(HeaderSet.TYPE, TYPE_EVENT); 260 request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams()); 261 262 request.mConnectionID = new byte[4]; 263 System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4); 264 265 synchronized (this) { 266 mWaitingForRemote = true; 267 } 268 // Send the header first and then the body 269 try { 270 if (V) Log.v(TAG, "Send headerset Event "); 271 putOperation = (ClientOperation)mClientSession.put(request); 272 // TODO - Should this be kept or Removed 273 274 } catch (IOException e) { 275 Log.e(TAG, "Error when put HeaderSet " + e.getMessage()); 276 error = true; 277 } 278 synchronized (this) { 279 mWaitingForRemote = false; 280 } 281 if (!error) { 282 try { 283 if (V) Log.v(TAG, "Send headerset Event "); 284 outputStream = putOperation.openOutputStream(); 285 } catch (IOException e) { 286 Log.e(TAG, "Error when opening OutputStream " + e.getMessage()); 287 error = true; 288 } 289 } 290 291 if (!error) { 292 293 maxChunkSize = putOperation.getMaxPacketSize(); 294 295 while (bytesWritten < eventBytes.length) { 296 bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten); 297 outputStream.write(eventBytes, bytesWritten, bytesToWrite); 298 bytesWritten += bytesToWrite; 299 } 300 301 if (bytesWritten == eventBytes.length) { 302 Log.i(TAG, "SendEvent finished send length" + eventBytes.length); 303 outputStream.close(); 304 } else { 305 error = true; 306 outputStream.close(); 307 putOperation.abort(); 308 Log.i(TAG, "SendEvent interrupted"); 309 } 310 } 311 } catch (IOException e) { 312 handleSendException(e.toString()); 313 error = true; 314 } catch (IndexOutOfBoundsException e) { 315 handleSendException(e.toString()); 316 error = true; 317 } finally { 318 try { 319 if (!error) { 320 responseCode = putOperation.getResponseCode(); 321 if (responseCode != -1) { 322 if (V) Log.v(TAG, "Put response code " + responseCode); 323 if (responseCode != ResponseCodes.OBEX_HTTP_OK) { 324 Log.i(TAG, "Response error code is " + responseCode); 325 } 326 } 327 } 328 if (putOperation != null) { 329 putOperation.close(); 330 } 331 } catch (IOException e) { 332 Log.e(TAG, "Error when closing stream after send " + e.getMessage()); 333 } 334 } 335 336 return responseCode; 337 } 338 339 private void handleSendException(String exception) { 340 Log.e(TAG, "Error when sending event: " + exception); 341 } 342} 343