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 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