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 
     38 /* MasClient is a one time use connection to a server defined by the SDP record passed in at
     39  * construction.  After use shutdown() must be called to properly clean up.
     40  */
     41 public class MasClient {
     42     private static final int CONNECT = 0;
     43     private static final int DISCONNECT = 1;
     44     private static final int REQUEST = 2;
     45     private static final String TAG = "MasClient";
     46     private static final boolean DBG = MapClientService.DBG;
     47     private static final boolean VDBG = MapClientService.VDBG;
     48     private static final byte[] BLUETOOTH_UUID_OBEX_MAS = new byte[]{
     49             (byte) 0xbb,
     50             0x58,
     51             0x2b,
     52             0x40,
     53             0x42,
     54             0x0c,
     55             0x11,
     56             (byte) 0xdb,
     57             (byte) 0xb0,
     58             (byte) 0xde,
     59             0x08,
     60             0x00,
     61             0x20,
     62             0x0c,
     63             (byte) 0x9a,
     64             0x66
     65     };
     66     private static final byte OAP_TAGID_MAP_SUPPORTED_FEATURES = 0x29;
     67     private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001;
     68     private static final int MAP_FEATURE_NOTIFICATION = 0x00000002;
     69     static final int MAP_SUPPORTED_FEATURES =
     70             MAP_FEATURE_NOTIFICATION_REGISTRATION | MAP_FEATURE_NOTIFICATION;
     71 
     72     private final StateMachine mCallback;
     73     private Handler mHandler;
     74     private BluetoothSocket mSocket;
     75     private BluetoothObexTransport mTransport;
     76     private BluetoothDevice mRemoteDevice;
     77     private ClientSession mSession;
     78     private HandlerThread mThread;
     79     private boolean mConnected = false;
     80     SdpMasRecord mSdpMasRecord;
     81 
     82     public MasClient(BluetoothDevice remoteDevice, StateMachine callback,
     83             SdpMasRecord sdpMasRecord) {
     84         if (remoteDevice == null) {
     85             throw new NullPointerException("Obex transport is null");
     86         }
     87         mRemoteDevice = remoteDevice;
     88         mCallback = callback;
     89         mSdpMasRecord = sdpMasRecord;
     90         mThread = new HandlerThread("Client");
     91         mThread.start();
     92         /* This will block until the looper have started, hence it will be safe to use it,
     93            when the constructor completes */
     94         Looper looper = mThread.getLooper();
     95         mHandler = new MasClientHandler(looper, this);
     96 
     97         mHandler.obtainMessage(CONNECT).sendToTarget();
     98     }
     99 
    100     private void connect() {
    101         try {
    102             if (DBG) {
    103                 Log.d(TAG, "Connecting to OBEX on RFCOM channel "
    104                         + mSdpMasRecord.getRfcommCannelNumber());
    105             }
    106             mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber());
    107             Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString());
    108             mSocket.connect();
    109             mTransport = new BluetoothObexTransport(mSocket);
    110 
    111             mSession = new ClientSession(mTransport);
    112             HeaderSet headerset = new HeaderSet();
    113             headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS);
    114             ObexAppParameters oap = new ObexAppParameters();
    115 
    116             oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES, MAP_SUPPORTED_FEATURES);
    117 
    118             oap.addToHeaderSet(headerset);
    119 
    120             headerset = mSession.connect(headerset);
    121             Log.d(TAG, "Connection results" + headerset.getResponseCode());
    122 
    123             if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) {
    124                 if (DBG) {
    125                     Log.d(TAG, "Connection Successful");
    126                 }
    127                 mConnected = true;
    128                 mCallback.sendMessage(MceStateMachine.MSG_MAS_CONNECTED);
    129             } else {
    130                 disconnect();
    131             }
    132 
    133         } catch (IOException e) {
    134             Log.e(TAG, "Caught an exception " + e.toString());
    135             disconnect();
    136         }
    137     }
    138 
    139     private void disconnect() {
    140         if (mSession != null) {
    141             try {
    142                 mSession.disconnect(null);
    143             } catch (IOException e) {
    144                 Log.e(TAG, "Caught an exception while disconnecting:" + e.toString());
    145             }
    146 
    147             try {
    148                 mSession.close();
    149             } catch (IOException e) {
    150                 Log.e(TAG, "Caught an exception while closing:" + e.toString());
    151             }
    152         }
    153 
    154         mConnected = false;
    155         mCallback.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED);
    156     }
    157 
    158     private void executeRequest(Request request) {
    159         try {
    160             request.execute(mSession);
    161             mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request);
    162         } catch (IOException e) {
    163             if (DBG) {
    164                 Log.d(TAG, "Request failed: " + request);
    165             }
    166             // Disconnect to cleanup.
    167             disconnect();
    168         }
    169     }
    170 
    171     public boolean makeRequest(Request request) {
    172         if (DBG) {
    173             Log.d(TAG, "makeRequest called with: " + request);
    174         }
    175 
    176         boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request));
    177         if (!status) {
    178             Log.e(TAG, "Adding messages failed, state: " + mConnected);
    179             return false;
    180         }
    181         return true;
    182     }
    183 
    184     public void shutdown() {
    185         mHandler.obtainMessage(DISCONNECT).sendToTarget();
    186         mThread.quitSafely();
    187     }
    188 
    189     public enum CharsetType {
    190         NATIVE, UTF_8;
    191     }
    192 
    193     private static class MasClientHandler extends Handler {
    194         WeakReference<MasClient> mInst;
    195 
    196         MasClientHandler(Looper looper, MasClient inst) {
    197             super(looper);
    198             mInst = new WeakReference<>(inst);
    199         }
    200 
    201         @Override
    202         public void handleMessage(Message msg) {
    203             MasClient inst = mInst.get();
    204             if (!inst.mConnected && msg.what != CONNECT) {
    205                 Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED.");
    206                 return;
    207             }
    208 
    209             switch (msg.what) {
    210                 case CONNECT:
    211                     inst.connect();
    212                     break;
    213 
    214                 case DISCONNECT:
    215                     inst.disconnect();
    216                     break;
    217 
    218                 case REQUEST:
    219                     inst.executeRequest((Request) msg.obj);
    220                     break;
    221             }
    222         }
    223     }
    224 
    225 }
    226