Home | History | Annotate | Download | only in map
      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 Handler mCallback = null;
     62     private BluetoothMapContentObserver mObserver;
     63     private boolean mObserverRegistered = false;
     64 
     65     // Used by the MAS to forward notification registrations
     66     public static final int MSG_MNS_NOTIFICATION_REGISTRATION = 1;
     67 
     68 
     69     public static final ParcelUuid BluetoothUuid_ObexMns =
     70             ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
     71 
     72 
     73     public BluetoothMnsObexClient(Context context, BluetoothDevice remoteDevice,
     74                                   Handler callback) {
     75         if (remoteDevice == null) {
     76             throw new NullPointerException("Obex transport is null");
     77         }
     78         HandlerThread thread = new HandlerThread("BluetoothMnsObexClient");
     79         thread.start();
     80         Looper looper = thread.getLooper();
     81         mHandler = new MnsObexClientHandler(looper);
     82         mContext = context;
     83         mRemoteDevice = remoteDevice;
     84         mCallback = callback;
     85         mObserver = new BluetoothMapContentObserver(mContext);
     86         mObserver.init();
     87     }
     88 
     89     public Handler getMessageHandler() {
     90         return mHandler;
     91     }
     92 
     93     public BluetoothMapContentObserver getContentObserver() {
     94         return mObserver;
     95     }
     96 
     97     private final class MnsObexClientHandler extends Handler {
     98         private MnsObexClientHandler(Looper looper) {
     99             super(looper);
    100         }
    101 
    102         @Override
    103         public void handleMessage(Message msg) {
    104             switch (msg.what) {
    105             case MSG_MNS_NOTIFICATION_REGISTRATION:
    106                 handleRegistration(msg.arg1 /*masId*/, msg.arg2 /*status*/);
    107                 break;
    108             default:
    109                 break;
    110             }
    111         }
    112     }
    113 
    114     public boolean isConnected() {
    115         return mConnected;
    116     }
    117 
    118     /**
    119      * Disconnect the connection to MNS server.
    120      * Call this when the MAS client requests a de-registration on events.
    121      */
    122     public void disconnect() {
    123         try {
    124             if (mClientSession != null) {
    125                 mClientSession.disconnect(null);
    126                 if (D) Log.d(TAG, "OBEX session disconnected");
    127             }
    128         } catch (IOException e) {
    129             Log.w(TAG, "OBEX session disconnect error " + e.getMessage());
    130         }
    131         try {
    132             if (mClientSession != null) {
    133                 if (D) Log.d(TAG, "OBEX session close mClientSession");
    134                 mClientSession.close();
    135                 mClientSession = null;
    136                 if (D) Log.d(TAG, "OBEX session closed");
    137             }
    138         } catch (IOException e) {
    139             Log.w(TAG, "OBEX session close error:" + e.getMessage());
    140         }
    141         if (mTransport != null) {
    142             try {
    143                 if (D) Log.d(TAG, "Close Obex Transport");
    144                 mTransport.close();
    145                 mTransport = null;
    146                 mConnected = false;
    147                 if (D) Log.d(TAG, "Obex Transport Closed");
    148             } catch (IOException e) {
    149                 Log.e(TAG, "mTransport.close error: " + e.getMessage());
    150             }
    151         }
    152     }
    153 
    154     /**
    155      * Shutdown the MNS.
    156      */
    157     public void shutdown() {
    158         /* should shutdown handler thread first to make sure
    159          * handleRegistration won't be called when disconnet
    160          */
    161         if (mHandler != null) {
    162             // Shut down the thread
    163             mHandler.removeCallbacksAndMessages(null);
    164             Looper looper = mHandler.getLooper();
    165             if (looper != null) {
    166                 looper.quit();
    167             }
    168             mHandler = null;
    169         }
    170 
    171         /* Disconnect if connected */
    172         disconnect();
    173 
    174         if(mObserverRegistered) {
    175             mObserver.unregisterObserver();
    176             mObserverRegistered = false;
    177         }
    178         if (mObserver != null) {
    179             mObserver.deinit();
    180             mObserver = null;
    181         }
    182     }
    183 
    184     private HeaderSet hsConnect = null;
    185 
    186     public void handleRegistration(int masId, int notificationStatus){
    187         Log.d(TAG, "handleRegistration( " + masId + ", " + notificationStatus + ")");
    188 
    189         if((isConnected() == false) &&
    190            (notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES)) {
    191             Log.d(TAG, "handleRegistration: connect");
    192             connect();
    193         }
    194 
    195         if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_NO) {
    196             // Unregister - should we disconnect, or keep the connection? - the spec. says nothing about this.
    197             if(mObserverRegistered == true) {
    198                 mObserver.unregisterObserver();
    199                 mObserverRegistered = false;
    200                 disconnect();
    201             }
    202         } else if(notificationStatus == BluetoothMapAppParams.NOTIFICATION_STATUS_YES) {
    203             /* Connect if we do not have a connection, and start the content observers providing
    204              * this thread as Handler.
    205              */
    206             if(mObserverRegistered == false) {
    207                 mObserver.registerObserver(this, masId);
    208                 mObserverRegistered = true;
    209             }
    210         }
    211     }
    212 
    213     public void connect() {
    214         Log.d(TAG, "handleRegistration: connect 2");
    215 
    216         BluetoothSocket btSocket = null;
    217         try {
    218             btSocket = mRemoteDevice.createInsecureRfcommSocketToServiceRecord(
    219                     BluetoothUuid_ObexMns.getUuid());
    220             btSocket.connect();
    221         } catch (IOException e) {
    222             Log.e(TAG, "BtSocket Connect error " + e.getMessage(), e);
    223             // TODO: do we need to report error somewhere?
    224             return;
    225         }
    226 
    227         mTransport = new BluetoothMnsRfcommTransport(btSocket);
    228 
    229         try {
    230             mClientSession = new ClientSession(mTransport);
    231             mConnected = true;
    232         } catch (IOException e1) {
    233             Log.e(TAG, "OBEX session create error " + e1.getMessage());
    234         }
    235         if (mConnected && mClientSession != null) {
    236             mConnected = false;
    237             HeaderSet hs = new HeaderSet();
    238             // bb582b41-420c-11db-b0de-0800200c9a66
    239             byte[] mnsTarget = { (byte) 0xbb, (byte) 0x58, (byte) 0x2b, (byte) 0x41,
    240                                  (byte) 0x42, (byte) 0x0c, (byte) 0x11, (byte) 0xdb,
    241                                  (byte) 0xb0, (byte) 0xde, (byte) 0x08, (byte) 0x00,
    242                                  (byte) 0x20, (byte) 0x0c, (byte) 0x9a, (byte) 0x66 };
    243             hs.setHeader(HeaderSet.TARGET, mnsTarget);
    244 
    245             synchronized (this) {
    246                 mWaitingForRemote = true;
    247             }
    248             try {
    249                 hsConnect = mClientSession.connect(hs);
    250                 if (D) Log.d(TAG, "OBEX session created");
    251                 mConnected = true;
    252             } catch (IOException e) {
    253                 Log.e(TAG, "OBEX session connect error " + e.getMessage());
    254             }
    255         }
    256             synchronized (this) {
    257                 mWaitingForRemote = false;
    258         }
    259     }
    260 
    261     public int sendEvent(byte[] eventBytes, int masInstanceId) {
    262 
    263         boolean error = false;
    264         int responseCode = -1;
    265         HeaderSet request;
    266         int maxChunkSize, bytesToWrite, bytesWritten = 0;
    267         ClientSession clientSession = mClientSession;
    268 
    269         if ((!mConnected) || (clientSession == null)) {
    270             Log.w(TAG, "sendEvent after disconnect:" + mConnected);
    271             return responseCode;
    272         }
    273 
    274         notifyUpdateWakeLock();
    275 
    276         request = new HeaderSet();
    277         BluetoothMapAppParams appParams = new BluetoothMapAppParams();
    278         appParams.setMasInstanceId(masInstanceId);
    279 
    280         ClientOperation putOperation = null;
    281         OutputStream outputStream = null;
    282 
    283         try {
    284             request.setHeader(HeaderSet.TYPE, TYPE_EVENT);
    285             request.setHeader(HeaderSet.APPLICATION_PARAMETER, appParams.EncodeParams());
    286 
    287             if (hsConnect.mConnectionID != null) {
    288                 request.mConnectionID = new byte[4];
    289                 System.arraycopy(hsConnect.mConnectionID, 0, request.mConnectionID, 0, 4);
    290             } else {
    291                 Log.w(TAG, "sendEvent: no connection ID");
    292             }
    293 
    294             synchronized (this) {
    295                 mWaitingForRemote = true;
    296             }
    297             // Send the header first and then the body
    298             try {
    299                 if (V) Log.v(TAG, "Send headerset Event ");
    300                 putOperation = (ClientOperation)clientSession.put(request);
    301                 // TODO - Should this be kept or Removed
    302 
    303             } catch (IOException e) {
    304                 Log.e(TAG, "Error when put HeaderSet " + e.getMessage());
    305                 error = true;
    306             }
    307             synchronized (this) {
    308                 mWaitingForRemote = false;
    309             }
    310             if (!error) {
    311                 try {
    312                     if (V) Log.v(TAG, "Send headerset Event ");
    313                     outputStream = putOperation.openOutputStream();
    314                 } catch (IOException e) {
    315                     Log.e(TAG, "Error when opening OutputStream " + e.getMessage());
    316                     error = true;
    317                 }
    318             }
    319 
    320             if (!error) {
    321 
    322                 maxChunkSize = putOperation.getMaxPacketSize();
    323 
    324                 while (bytesWritten < eventBytes.length) {
    325                     bytesToWrite = Math.min(maxChunkSize, eventBytes.length - bytesWritten);
    326                     outputStream.write(eventBytes, bytesWritten, bytesToWrite);
    327                     bytesWritten += bytesToWrite;
    328                 }
    329 
    330                 if (bytesWritten == eventBytes.length) {
    331                     Log.i(TAG, "SendEvent finished send length" + eventBytes.length);
    332                 } else {
    333                     error = true;
    334                     putOperation.abort();
    335                     Log.i(TAG, "SendEvent interrupted");
    336                 }
    337             }
    338         } catch (IOException e) {
    339             handleSendException(e.toString());
    340             error = true;
    341         } catch (IndexOutOfBoundsException e) {
    342             handleSendException(e.toString());
    343             error = true;
    344         } finally {
    345             try {
    346                 if (outputStream != null) {
    347                     outputStream.close();
    348                 }
    349             } catch (IOException e) {
    350                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
    351             }
    352             try {
    353                 if ((!error) && (putOperation != null)) {
    354                     responseCode = putOperation.getResponseCode();
    355                     if (responseCode != -1) {
    356                         if (V) Log.v(TAG, "Put response code " + responseCode);
    357                         if (responseCode != ResponseCodes.OBEX_HTTP_OK) {
    358                             Log.i(TAG, "Response error code is " + responseCode);
    359                         }
    360                     }
    361                 }
    362                 if (putOperation != null) {
    363                     putOperation.close();
    364                 }
    365             } catch (IOException e) {
    366                 Log.e(TAG, "Error when closing stream after send " + e.getMessage());
    367             }
    368         }
    369 
    370         return responseCode;
    371     }
    372 
    373     private void handleSendException(String exception) {
    374         Log.e(TAG, "Error when sending event: " + exception);
    375     }
    376 
    377     private void notifyUpdateWakeLock() {
    378         Message msg = Message.obtain(mCallback);
    379         msg.what = BluetoothMapService.MSG_ACQUIRE_WAKE_LOCK;
    380         msg.sendToTarget();
    381     }
    382 }
    383