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.bluetooth.statemachine.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 L2CAP_INVALID_PSM = -1; 68 private static final int MAP_FEATURE_NOTIFICATION_REGISTRATION = 0x00000001; 69 private static final int MAP_FEATURE_NOTIFICATION = 0x00000002; 70 private static final int MAP_FEATURE_BROWSING = 0x00000004; 71 private static final int MAP_FEATURE_UPLOADING = 0x00000008; 72 private static final int MAP_FEATURE_EXTENDED_EVENT_REPORT_1_1 = 0x00000040; 73 static final int MAP_SUPPORTED_FEATURES = 74 MAP_FEATURE_NOTIFICATION_REGISTRATION | MAP_FEATURE_NOTIFICATION 75 | MAP_FEATURE_BROWSING | MAP_FEATURE_UPLOADING 76 | MAP_FEATURE_EXTENDED_EVENT_REPORT_1_1; 77 78 private final StateMachine mCallback; 79 private Handler mHandler; 80 private BluetoothSocket mSocket; 81 private BluetoothObexTransport mTransport; 82 private BluetoothDevice mRemoteDevice; 83 private ClientSession mSession; 84 private HandlerThread mThread; 85 private boolean mConnected = false; 86 SdpMasRecord mSdpMasRecord; 87 MasClient(BluetoothDevice remoteDevice, StateMachine callback, SdpMasRecord sdpMasRecord)88 public MasClient(BluetoothDevice remoteDevice, StateMachine callback, 89 SdpMasRecord sdpMasRecord) { 90 if (remoteDevice == null) { 91 throw new NullPointerException("Obex transport is null"); 92 } 93 mRemoteDevice = remoteDevice; 94 mCallback = callback; 95 mSdpMasRecord = sdpMasRecord; 96 mThread = new HandlerThread("Client"); 97 mThread.start(); 98 /* This will block until the looper have started, hence it will be safe to use it, 99 when the constructor completes */ 100 Looper looper = mThread.getLooper(); 101 mHandler = new MasClientHandler(looper, this); 102 103 mHandler.obtainMessage(CONNECT).sendToTarget(); 104 } 105 connect()106 private void connect() { 107 try { 108 int l2capSocket = mSdpMasRecord.getL2capPsm(); 109 110 if (l2capSocket != L2CAP_INVALID_PSM) { 111 if (DBG) { 112 Log.d(TAG, "Connecting to OBEX on L2CAP channel " + l2capSocket); 113 } 114 mSocket = mRemoteDevice.createL2capSocket(l2capSocket); 115 } else { 116 if (DBG) { 117 Log.d(TAG, "Connecting to OBEX on RFCOM channel " 118 + mSdpMasRecord.getRfcommCannelNumber()); 119 } 120 mSocket = mRemoteDevice.createRfcommSocket(mSdpMasRecord.getRfcommCannelNumber()); 121 } 122 if (DBG) Log.d(TAG, mRemoteDevice.toString() + "Socket: " + mSocket.toString()); 123 mSocket.connect(); 124 mTransport = new BluetoothObexTransport(mSocket); 125 126 mSession = new ClientSession(mTransport); 127 HeaderSet headerset = new HeaderSet(); 128 headerset.setHeader(HeaderSet.TARGET, BLUETOOTH_UUID_OBEX_MAS); 129 ObexAppParameters oap = new ObexAppParameters(); 130 131 oap.add(OAP_TAGID_MAP_SUPPORTED_FEATURES, MAP_SUPPORTED_FEATURES); 132 133 oap.addToHeaderSet(headerset); 134 135 headerset = mSession.connect(headerset); 136 if (DBG) Log.d(TAG, "Connection results" + headerset.getResponseCode()); 137 138 if (headerset.getResponseCode() == ResponseCodes.OBEX_HTTP_OK) { 139 if (DBG) { 140 Log.d(TAG, "Connection Successful"); 141 } 142 mConnected = true; 143 mCallback.sendMessage(MceStateMachine.MSG_MAS_CONNECTED); 144 } else { 145 disconnect(); 146 } 147 148 } catch (IOException e) { 149 Log.e(TAG, "Caught an exception " + e.toString()); 150 disconnect(); 151 } 152 } 153 disconnect()154 private void disconnect() { 155 if (mSession != null) { 156 try { 157 mSession.disconnect(null); 158 } catch (IOException e) { 159 Log.e(TAG, "Caught an exception while disconnecting:" + e.toString()); 160 } 161 162 try { 163 mSession.close(); 164 } catch (IOException e) { 165 Log.e(TAG, "Caught an exception while closing:" + e.toString()); 166 } 167 } 168 169 mConnected = false; 170 mCallback.sendMessage(MceStateMachine.MSG_MAS_DISCONNECTED); 171 } 172 executeRequest(Request request)173 private void executeRequest(Request request) { 174 try { 175 request.execute(mSession); 176 mCallback.sendMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request); 177 } catch (IOException e) { 178 if (DBG) { 179 Log.d(TAG, "Request failed: " + request); 180 } 181 // Disconnect to cleanup. 182 disconnect(); 183 } 184 } 185 makeRequest(Request request)186 public boolean makeRequest(Request request) { 187 if (DBG) { 188 Log.d(TAG, "makeRequest called with: " + request); 189 } 190 191 boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request)); 192 if (!status) { 193 Log.e(TAG, "Adding messages failed, state: " + mConnected); 194 return false; 195 } 196 return true; 197 } 198 shutdown()199 public void shutdown() { 200 mHandler.obtainMessage(DISCONNECT).sendToTarget(); 201 mThread.quitSafely(); 202 } 203 204 public enum CharsetType { 205 NATIVE, UTF_8; 206 } 207 getSdpMasRecord()208 SdpMasRecord getSdpMasRecord() { 209 return mSdpMasRecord; 210 } 211 212 private static class MasClientHandler extends Handler { 213 WeakReference<MasClient> mInst; 214 MasClientHandler(Looper looper, MasClient inst)215 MasClientHandler(Looper looper, MasClient inst) { 216 super(looper); 217 mInst = new WeakReference<>(inst); 218 } 219 220 @Override handleMessage(Message msg)221 public void handleMessage(Message msg) { 222 MasClient inst = mInst.get(); 223 switch (msg.what) { 224 case CONNECT: 225 if (!inst.mConnected) { 226 inst.connect(); 227 } 228 break; 229 230 case DISCONNECT: 231 if (inst.mConnected) { 232 inst.disconnect(); 233 } 234 break; 235 236 case REQUEST: 237 if (inst.mConnected) { 238 inst.executeRequest((Request) msg.obj); 239 } 240 break; 241 } 242 } 243 } 244 245 } 246