• 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 import com.android.obex.ClientOperation;
30 import com.android.obex.ClientSession;
31 import com.android.obex.HeaderSet;
32 import com.android.obex.ObexTransport;
33 import com.android.obex.ResponseCodes;
34 
35 import java.io.IOException;
36 import java.io.OutputStream;
37 
38 /**
39  * The Message Notification Service class runs its own message handler thread,
40  * to avoid executing long operations on the MAP service Thread.
41  * This handler context is passed to the content observers,
42  * hence all call-backs (and thereby transmission of data) is executed
43  * from this thread.
44  */
45 public class BluetoothMnsObexClient {
46 
47     private static final String TAG = "BluetoothMnsObexClient";
48     private static final boolean D = BluetoothMapService.DEBUG;
49     private static final boolean V = BluetoothMapService.VERBOSE;
50 
51     private ObexTransport mTransport;
52     public Handler mHandler = null;
53     private volatile boolean mWaitingForRemote;
54     private static final String TYPE_EVENT = "x-bt/MAP-event-report";
55     private ClientSession mClientSession;
56     private boolean mConnected = false;
57     BluetoothDevice mRemoteDevice;
58     private SparseBooleanArray mRegisteredMasIds = new SparseBooleanArray(1);
59 
60     private HeaderSet mHsConnect = null;
61     private Handler mCallback = null;
62     private SdpMnsRecord mMnsRecord;
63     // Used by the MAS to forward notification registrations
64     public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
65     public static final int MSG_MNS_SEND_EVENT = 2;
66     public static final int MSG_MNS_SDP_SEARCH_REGISTRATION = 3;
67 
68     //Copy SdpManager.SDP_INTENT_DELAY - The timeout to wait for reply from native.
69     private static final int MNS_SDP_SEARCH_DELAY = 6000;
70     public MnsSdpSearchInfo mMnsLstRegRqst = null;
71     private static final int MNS_NOTIFICATION_DELAY = 10;
72     public static final ParcelUuid BLUETOOTH_UUID_OBEX_MNS =
73             ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
74 
75 
BluetoothMnsObexClient(BluetoothDevice remoteDevice, SdpMnsRecord mnsRecord, Handler callback)76     public BluetoothMnsObexClient(BluetoothDevice remoteDevice, SdpMnsRecord mnsRecord,
77             Handler callback) {
78         if (remoteDevice == null) {
79             throw new NullPointerException("Obex transport is null");
80         }
81         mRemoteDevice = remoteDevice;
82         HandlerThread thread = new HandlerThread("BluetoothMnsObexClient");
83         thread.start();
84         /* This will block until the looper have started, hence it will be safe to use it,
85            when the constructor completes */
86         Looper looper = thread.getLooper();
87         mHandler = new MnsObexClientHandler(looper);
88         mCallback = callback;
89         mMnsRecord = mnsRecord;
90     }
91 
getMessageHandler()92     public Handler getMessageHandler() {
93         return mHandler;
94     }
95 
96     class MnsSdpSearchInfo {
97         private boolean mIsSearchInProgress;
98         public int lastMasId;
99         public int lastNotificationStatus;
100 
MnsSdpSearchInfo(boolean isSearchON, int masId, int notification)101         MnsSdpSearchInfo(boolean isSearchON, int masId, int notification) {
102             mIsSearchInProgress = isSearchON;
103             lastMasId = masId;
104             lastNotificationStatus = notification;
105         }
106 
isSearchInProgress()107         public boolean isSearchInProgress() {
108             return mIsSearchInProgress;
109         }
110 
setIsSearchInProgress(boolean isSearchON)111         public void setIsSearchInProgress(boolean isSearchON) {
112             mIsSearchInProgress = isSearchON;
113         }
114     }
115 
116     private final class MnsObexClientHandler extends Handler {
MnsObexClientHandler(Looper looper)117         private MnsObexClientHandler(Looper looper) {
118             super(looper);
119         }
120 
121         @Override
handleMessage(Message msg)122         public void handleMessage(Message msg) {
123             switch (msg.what) {
124                 case MSG_MNS_NOTIFICATION_REGISTRATION:
125                     if (V) {
126                         Log.v(TAG, "Reg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
127                     }
128                     if (isValidMnsRecord()) {
129                         handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
130                     } else {
131                         //Should not happen
132                         if (D) {
133                             Log.d(TAG, "MNS SDP info not available yet - Cannot Connect.");
134                         }
135                     }
136                     break;
137                 case MSG_MNS_SEND_EVENT:
138                     sendEventHandler((byte[]) msg.obj/*byte[]*/, msg.arg1 /*masId*/);
139                     break;
140                 case MSG_MNS_SDP_SEARCH_REGISTRATION:
141                     //Initiate SDP Search
142                     notifyMnsSdpSearch();
143                     //Save the mns search info
144                     mMnsLstRegRqst = new MnsSdpSearchInfo(true, msg.arg1, msg.arg2);
145                     //Handle notification registration.
146                     Message msgReg =
147                             mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION, msg.arg1,
148                                     msg.arg2);
149                     if (V) {
150                         Log.v(TAG, "SearchReg  masId:  " + msg.arg1 + " notfStatus: " + msg.arg2);
151                     }
152                     mHandler.sendMessageDelayed(msgReg, MNS_SDP_SEARCH_DELAY);
153                     break;
154                 default:
155                     break;
156             }
157         }
158     }
159 
isConnected()160     public boolean isConnected() {
161         return mConnected;
162     }
163 
164     /**
165      * Disconnect the connection to MNS server.
166      * Call this when the MAS client requests a de-registration on events.
167      */
disconnect()168     public synchronized void disconnect() {
169         try {
170             if (mClientSession != null) {
171                 mClientSession.disconnect(null);
172                 if (D) {
173                     Log.d(TAG, "OBEX session disconnected");
174                 }
175             }
176         } catch (IOException e) {
177             Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
178         }
179         try {
180             if (mClientSession != null) {
181                 if (D) {
182                     Log.d(TAG, "OBEX session close mClientSession");
183                 }
184                 mClientSession.close();
185                 mClientSession = null;
186                 if (D) {
187                     Log.d(TAG, "OBEX session closed");
188                 }
189             }
190         } catch (IOException e) {
191             Log.w(TAG, "OBEX session close error:" + e.getMessage());
192         }
193         if (mTransport != null) {
194             try {
195                 if (D) {
196                     Log.d(TAG, "Close Obex Transport");
197                 }
198                 mTransport.close();
199                 mTransport = null;
200                 mConnected = false;
201                 if (D) {
202                     Log.d(TAG, "Obex Transport Closed");
203                 }
204             } catch (IOException e) {
205                 Log.e(TAG, "mTransport.close error: " + e.getMessage());
206             }
207         }
208     }
209 
210     /**
211      * Shutdown the MNS.
212      */
shutdown()213     public synchronized void shutdown() {
214         /* should shutdown handler thread first to make sure
215          * handleRegistration won't be called when disconnect
216          */
217         if (mHandler != null) {
218             // Shut down the thread
219             mHandler.removeCallbacksAndMessages(null);
220             Looper looper = mHandler.getLooper();
221             if (looper != null) {
222                 looper.quit();
223             }
224         }
225 
226         /* Disconnect if connected */
227         disconnect();
228 
229         mRegisteredMasIds.clear();
230     }
231 
232     /**
233      * We store a list of registered MasIds only to control connect/disconnect
234      * @param masId
235      * @param notificationStatus
236      */
handleRegistration(int masId, int notificationStatus)237     public synchronized void handleRegistration(int masId, int notificationStatus) {
238         if (D) {
239             Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
240         }
241         boolean sendObserverRegistration = true;
242         if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
243             mRegisteredMasIds.delete(masId);
244             if (mMnsLstRegRqst != null && mMnsLstRegRqst.lastMasId == masId) {
245                 //Clear last saved MNSSdpSearchInfo , if Disconnect requested for same MasId.
246                 mMnsLstRegRqst = null;
247             }
248         } else if (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
249             /* Connect if we do not have a connection, and start the content observers providing
250              * this thread as Handler.
251              */
252             if (!isConnected()) {
253                 if (D) {
254                     Log.d(TAG, "handleRegistration: connect");
255                 }
256                 connect();
257             }
258             sendObserverRegistration = isConnected();
259             mRegisteredMasIds.put(masId, true); // We don't use the value for anything
260 
261             // Clear last saved MNSSdpSearchInfo after connect is processed.
262             mMnsLstRegRqst = null;
263         }
264 
265         if (mRegisteredMasIds.size() == 0) {
266             // No more registrations - disconnect
267             if (D) {
268                 Log.d(TAG, "handleRegistration: disconnect");
269             }
270             disconnect();
271         }
272 
273         //Register ContentObserver After connect/disconnect MNS channel.
274         if (V) {
275             Log.v(TAG, "Send  registerObserver: " + sendObserverRegistration);
276         }
277         if (mCallback != null && sendObserverRegistration) {
278             Message msg = Message.obtain(mCallback);
279             msg.what = BluetoothMapService.MSG_OBSERVER_REGISTRATION;
280             msg.arg1 = masId;
281             msg.arg2 = notificationStatus;
282             msg.sendToTarget();
283         }
284     }
285 
isValidMnsRecord()286     public boolean isValidMnsRecord() {
287         return (mMnsRecord != null);
288     }
289 
setMnsRecord(SdpMnsRecord mnsRecord)290     public void setMnsRecord(SdpMnsRecord mnsRecord) {
291         if (V) {
292             Log.v(TAG, "setMNSRecord");
293         }
294         if (isValidMnsRecord()) {
295             Log.w(TAG, "MNS Record already available. Still update.");
296         }
297         mMnsRecord = mnsRecord;
298         if (mMnsLstRegRqst != null) {
299             //SDP Search completed.
300             mMnsLstRegRqst.setIsSearchInProgress(false);
301             if (mHandler.hasMessages(MSG_MNS_NOTIFICATION_REGISTRATION)) {
302                 mHandler.removeMessages(MSG_MNS_NOTIFICATION_REGISTRATION);
303                 //Search Result obtained within MNS_SDP_SEARCH_DELAY timeout
304                 if (!isValidMnsRecord()) {
305                     // SDP info still not available for last trial.
306                     // Clear saved info.
307                     mMnsLstRegRqst = null;
308                 } else {
309                     if (V) {
310                         Log.v(TAG, "Handle registration for last saved request");
311                     }
312                     Message msgReg = mHandler.obtainMessage(MSG_MNS_NOTIFICATION_REGISTRATION);
313                     msgReg.arg1 = mMnsLstRegRqst.lastMasId;
314                     msgReg.arg2 = mMnsLstRegRqst.lastNotificationStatus;
315                     if (V) {
316                         Log.v(TAG, "SearchReg  masId:  " + msgReg.arg1 + " notfStatus: "
317                                 + msgReg.arg2);
318                     }
319                     //Handle notification registration.
320                     mHandler.sendMessageDelayed(msgReg, MNS_NOTIFICATION_DELAY);
321                 }
322             }
323         } else {
324             if (V) {
325                 Log.v(TAG, "No last saved MNSSDPInfo to handle");
326             }
327         }
328     }
329 
connect()330     public void connect() {
331 
332         mConnected = true;
333 
334         BluetoothSocket btSocket = null;
335         try {
336             // TODO: Do SDP record search again?
337             if (isValidMnsRecord() && mMnsRecord.getL2capPsm() > 0) {
338                 // Do L2CAP connect
339                 btSocket = mRemoteDevice.createL2capSocket(mMnsRecord.getL2capPsm());
340 
341             } else if (isValidMnsRecord() && mMnsRecord.getRfcommChannelNumber() > 0) {
342                 // Do Rfcomm connect
343                 btSocket = mRemoteDevice.createRfcommSocket(mMnsRecord.getRfcommChannelNumber());
344             } else {
345                 // This should not happen...
346                 Log.e(TAG, "Invalid SDP content - attempt a connect to UUID...");
347                 // TODO: Why insecure? - is it because the link is already encrypted?
348                 btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
349                         BLUETOOTH_UUID_OBEX_MNS.getUuid());
350             }
351             btSocket.connect();
352         } catch (IOException e) {
353             Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
354             // TODO: do we need to report error somewhere?
355             mConnected = false;
356             return;
357         }
358 
359         mTransport = new BluetoothObexTransport(btSocket);
360 
361         try {
362             mClientSession = new ClientSession(mTransport);
363         } catch (IOException e1) {
364             Log.e(TAG, "OBEX session create error " + e1.getMessage());
365             mConnected = false;
366         }
367         if (mConnected && mClientSession != null) {
368             boolean connected = false;
369             HeaderSet hs = new HeaderSet();
370             // bb582b41-420c-11db-b0de-0800200c9a66
371             byte[] mnsTarget = {
372                     (byte) 0xbb,
373                     (byte) 0x58,
374                     (byte) 0x2b,
375                     (byte) 0x41,
376                     (byte) 0x42,
377                     (byte) 0x0c,
378                     (byte) 0x11,
379                     (byte) 0xdb,
380                     (byte) 0xb0,
381                     (byte) 0xde,
382                     (byte) 0x08,
383                     (byte) 0x00,
384                     (byte) 0x20,
385                     (byte) 0x0c,
386                     (byte) 0x9a,
387                     (byte) 0x66
388             };
389             hs.setHeader(HeaderSet.TARGET, mnsTarget);
390 
391             synchronized (this) {
392                 mWaitingForRemote = true;
393             }
394             try {
395                 mHsConnect = mClientSession.connect(hs);
396                 if (D) {
397                     Log.d(TAG, "OBEX session created");
398                 }
399                 connected = true;
400             } catch (IOException e) {
401                 Log.e(TAG, "OBEX session connect error " + e.getMessage());
402             }
403             mConnected = connected;
404         }
405         synchronized (this) {
406             mWaitingForRemote = false;
407         }
408     }
409 
410     /**
411      * Call this method to queue an event report to be send to the MNS server.
412      * @param eventBytes the encoded event data.
413      * @param masInstanceId the MasId of the instance sending the event.
414      */
sendEvent(byte[] eventBytes, int masInstanceId)415     public void sendEvent(byte[] eventBytes, int masInstanceId) {
416         // We need to check for null, to handle shutdown.
417         if (mHandler != null) {
418             Message msg = mHandler.obtainMessage(MSG_MNS_SEND_EVENT, masInstanceId, 0, eventBytes);
419             if (msg != null) {
420                 msg.sendToTarget();
421             }
422         }
423         notifyUpdateWakeLock();
424     }
425 
notifyMnsSdpSearch()426     private void notifyMnsSdpSearch() {
427         if (mCallback != null) {
428             Message msg = Message.obtain(mCallback);
429             msg.what = BluetoothMapService.MSG_MNS_SDP_SEARCH;
430             msg.sendToTarget();
431         }
432     }
433 
sendEventHandler(byte[] eventBytes, int masInstanceId)434     private int sendEventHandler(byte[] eventBytes, int masInstanceId) {
435 
436         boolean error = false;
437         int responseCode = -1;
438         HeaderSet request;
439         int maxChunkSize, bytesToWrite, bytesWritten = 0;
440         ClientSession clientSession = mClientSession;
441 
442         if ((!mConnected) || (clientSession == null)) {
443             Log.w(TAG, "sendEvent after disconnect:" + mConnected);
444             return responseCode;
445         }
446 
447         request = new HeaderSet();
448         BluetoothMapAppParams appParams = new BluetoothMapAppParams();
449         appParams.setMasInstanceId(masInstanceId);
450 
451         ClientOperation putOperation = null;
452         OutputStream outputStream = null;
453 
454         try {
455             request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
456             request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.encodeParams());
457 
458             if (mHsConnect.mConnectionID != null) {
459                 request.mConnectionID = new byte[4];
460                 System.arraycopy(mHsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
461             } else {
462                 Log.w(TAG, "sendEvent: no connection ID");
463             }
464 
465             synchronized (this) {
466                 mWaitingForRemote = true;
467             }
468             // Send the header first and then the body
469             try {
470                 if (V) {
471                     Log.v(TAG, "Send headerset Event ");
472                 }
473                 putOperation = (ClientOperation) clientSession.put(request);
474                 // TODO - Should this be kept or Removed
475 
476             } catch (IOException e) {
477                 Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
478                 error = true;
479             }
480             synchronized (this) {
481                 mWaitingForRemote = false;
482             }
483             if (!error) {
484                 try {
485                     if (V) {
486                         Log.v(TAG, "Send headerset Event ");
487                     }
488                     outputStream = putOperation.openOutputStream();
489                 } catch (IOException e) {
490                     Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
491                     error = true;
492                 }
493             }
494 
495             if (!error) {
496 
497                 maxChunkSize = putOperation.getMaxPacketSize();
498 
499                 while (bytesWritten < eventBytes.length) {
500                     bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
501                     outputStream.write(eventBytes, bytesWritten, bytesToWrite);
502                     bytesWritten += bytesToWrite;
503                 }
504 
505                 if (bytesWritten == eventBytes.length) {
506                     Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
507                 } else {
508                     error = true;
509                     putOperation.abort();
510                     Log.i(TAG, "SendEvent interrupted");
511                 }
512             }
513         } catch (IOException e) {
514             handleSendException(e.toString());
515             error = true;
516         } catch (IndexOutOfBoundsException e) {
517             handleSendException(e.toString());
518             error = true;
519         } finally {
520             try {
521                 if (outputStream != null) {
522                     outputStream.close();
523                 }
524             } catch (IOException e) {
525                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
526             }
527             try {
528                 if ((!error) && (putOperation != null)) {
529                     responseCode = putOperation.getResponseCode();
530                     if (responseCode != -1) {
531                         if (V) {
532                             Log.v(TAG, "Put response code " + responseCode);
533                         }
534                         if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
535                             Log.i(TAG, "Response error code is " + responseCode);
536                         }
537                     }
538                 }
539                 if (putOperation != null) {
540                     putOperation.close();
541                 }
542             } catch (IOException e) {
543                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
544             }
545         }
546 
547         return responseCode;
548     }
549 
handleSendException(String exception)550     private void handleSendException(String exception) {
551         Log.e(TAG, "Error when sending event: " + exception);
552     }
553 
notifyUpdateWakeLock()554     private void notifyUpdateWakeLock() {
555         if (mCallback != null) {
556             Message msg = Message.obtain(mCallback);
557             msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
558             msg.sendToTarget();
559         }
560     }
561 }
562