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