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