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 17package com.android.bluetooth.mapclient; 18 19import android.bluetooth.BluetoothDevice; 20import android.bluetooth.BluetoothSocket; 21import android.bluetooth.SdpMasRecord; 22import android.os.Handler; 23import android.os.HandlerThread; 24import android.os.Looper; 25import android.os.Message; 26import android.util.Log; 27 28import com.android.bluetooth.BluetoothObexTransport; 29import com.android.internal.util.StateMachine; 30 31import java.io.IOException; 32import java.lang.ref.WeakReference; 33 34import javax.obex.ClientSession; 35import javax.obex.HeaderSet; 36import 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 */ 41public 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