Home | History | Annotate | Download | only in mapclient
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.bluetooth.mapclient;
     18 
     19 import android.bluetooth.BluetoothDevice;
     20 import android.bluetooth.BluetoothSocket;
     21 import android.bluetooth.SdpMasRecord;
     22 import android.os.Handler;
     23 import android.os.HandlerThread;
     24 import android.os.Looper;
     25 import android.os.Message;
     26 import android.util.Log;
     27 
     28 import com.android.bluetooth.BluetoothObexTransport;
     29 import com.android.internal.util.StateMachine;
     30 
     31 import java.io.IOException;
     32 import java.lang.ref.WeakReference;
     33 
     34 import javax.obex.ClientSession;
     35 import javax.obex.HeaderSet;
     36 import javax.obex.ResponseCodes;
     37 /* MasClient is a one time use connection to a server defined by the SDP record passed in at
     38  * construction.  After use shutdown() must be called to properly clean up.
     39  */
     40 public class MasClient {
     41     private static final int CONNECT = 0;
     42     private static final int DISCONNECT = 1;
     43     private static final int REQUEST = 2;
     44     private static final String TAG = "MasClient";
     45     private static final boolean DBG = MapClientService.DBG;
     46     private static final boolean VDBG = MapClientService.VDBG;
     47     private static final byte[] BLUETOOTH_UUID_OBEX_MAS = new byte[]{
     48             (byte) 0xbb, 0x58, 0x2b, 0x40, 0x42, 0x0c, 0x11, (byte) 0xdb, (byte) 0xb0, (byte) 0xde,
     49             0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66
     50     };
     51     private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
     52     private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
     53     private static final int MAP_SUPPORTED_FEATURES = MAP_FEATURE_NOTIFICATION_REGISTRATION;
     54 
     55     private final StateMachine mCallback;
     56     private Handler mHandler;
     57     private BluetoothSocket mSocket;
     58     private BluetoothObexTransport mTransport;
     59     private BluetoothDevice mRemoteDevice;
     60     private ClientSession mSession;
     61     private HandlerThread thread;
     62     private boolean mConnected = false;
     63     SdpMasRecord mSdpMasRecord;
     64 
     65     public MasClient(BluetoothDevice remoteDevice,
     66             StateMachine callback, SdpMasRecord sdpMasRecord) {
     67         if (remoteDevice == null) {
     68             throw new NullPointerException("Obex transport is null");
     69         }
     70         mRemoteDevice = remoteDevice;
     71         mCallback = callback;
     72         mSdpMasRecord = sdpMasRecord;
     73         thread = new HandlerThread("Client");
     74         thread.start();
     75         /* This will block until the looper have started, hence it will be safe to use it,
     76            when the constructor completes */
     77         Looper looper = thread.getLooper();
     78         mHandler = new MasClientHandler(looper, this);
     79 
     80         mHandler.obtainMessage(CONNECT).sendToTarget();
     81     }
     82 
     83     private void connect() {
     84         try {
     85             if (DBG) {
     86                 Log.d(TAG, "Connecting to OBEX on RFCOM channel "
     87                         + mSdpMasRecord.getRfcommCannelNumber());
     88             }
     89             mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
     90             Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
     91             mSocket.connect();
     92             mTransport = new BluetoothObexTransport(mSocket);
     93 
     94             mSession = new ClientSession(mTransport);
     95             HeaderSet headerset = new HeaderSet();
     96             headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
     97             ObexAppParameters oap = new ObexAppParameters();
     98 
     99             oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES,
    100                     MAP_SUPPORTED_FEATURES);
    101 
    102             oap.addToHeaderSet(headerset);
    103 
    104             headerset = mSession.connect(headerset);
    105             Log.d(TAG, "Connection results" + headerset.getResponseCode());
    106 
    107             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
    108                 if (DBG) Log.d(TAG, "Connection Successful");
    109                 mConnected = true;
    110                 mCallback.obtainMessage(
    111                         MceStateMachine.MSG_MAS_CONNECTED).sendToTarget();
    112             } else {
    113                 disconnect();
    114             }
    115 
    116         } catch (IOException e) {
    117             Log.e(TAG, "Caught an exception " + e.toString());
    118             disconnect();
    119         }
    120     }
    121 
    122     private void disconnect() {
    123         if (mSession != null) {
    124             try {
    125                 mSession.disconnect(null);
    126             } catch (IOException e) {
    127                 Log.e(TAG, "Caught an exception while disconnecting:" + e.toString());
    128             }
    129 
    130             try {
    131                 mSession.close();
    132             } catch (IOException e) {
    133                 Log.e(TAG, "Caught an exception while closing:" + e.toString());
    134             }
    135         }
    136 
    137         mConnected = false;
    138         mCallback.obtainMessage(MceStateMachine.MSG_MAS_DISCONNECTED).sendToTarget();
    139     }
    140 
    141     private void executeRequest(Request request) {
    142         try {
    143             request.execute(mSession);
    144             mCallback.obtainMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED,
    145                     request).sendToTarget();
    146         } catch (IOException e) {
    147             if (DBG) Log.d(TAG, "Request failed: " + request);
    148             // Disconnect to cleanup.
    149             disconnect();
    150         }
    151     }
    152 
    153     public boolean makeRequest(Request request) {
    154         if (DBG) Log.d(TAG, "makeRequest called with: " + request);
    155 
    156         boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
    157         if (!status) {
    158             Log.e(TAG, "Adding messages failed, state: " + mConnected);
    159             return false;
    160         }
    161         return true;
    162     }
    163 
    164     public void shutdown() {
    165         mHandler.obtainMessage(DISCONNECT).sendToTarget();
    166         thread.quitSafely();
    167     }
    168 
    169     public enum CharsetType {
    170         NATIVE, UTF_8;
    171     }
    172 
    173     private static class MasClientHandler extends Handler {
    174         WeakReference<MasClient> mInst;
    175 
    176         MasClientHandler(Looper looper, MasClient inst) {
    177             super(looper);
    178             mInst = new WeakReference<>(inst);
    179         }
    180 
    181         @Override
    182         public void handleMessage(Message msg) {
    183             MasClient inst = mInst.get();
    184             if (!inst.mConnected && msg.what != CONNECT) {
    185                 Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
    186                 return;
    187             }
    188 
    189             switch (msg.what) {
    190                 case CONNECT:
    191                     inst.connect();
    192                     break;
    193 
    194                 case DISCONNECT:
    195                     inst.disconnect();
    196                     break;
    197 
    198                 case REQUEST:
    199                     inst.executeRequest((Request) msg.obj);
    200                     break;
    201             }
    202         }
    203     }
    204 
    205 }
    206