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