MasClient.java revision 6ebf3e48a841a81985e72064fefd55f67891f663
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.obtainMessage(MceStateMachine.MSG_MAS_CONNECTED).sendToTarget(); 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.obtainMessage(MceStateMachine.MSG_MAS_DISCONNECTED).sendToTarget(); 156 } 157 158 private void executeRequest(Request request) { 159 try { 160 request.execute(mSession); 161 mCallback.obtainMessage(MceStateMachine.MSG_MAS_REQUEST_COMPLETED, request) 162 .sendToTarget(); 163 } catch (IOException e) { 164 if (DBG) { 165 Log.d(TAG, "Request failed: " + request); 166 } 167 // Disconnect to cleanup. 168 disconnect(); 169 } 170 } 171 172 public boolean makeRequest(Request request) { 173 if (DBG) { 174 Log.d(TAG, "makeRequest called with: " + request); 175 } 176 177 boolean status = mHandler.sendMessage(mHandler.obtainMessage(REQUEST, request)); 178 if (!status) { 179 Log.e(TAG, "Adding messages failed, state: " + mConnected); 180 return false; 181 } 182 return true; 183 } 184 185 public void shutdown() { 186 mHandler.obtainMessage(DISCONNECT).sendToTarget(); 187 mThread.quitSafely(); 188 } 189 190 public enum CharsetType { 191 NATIVE, UTF_8; 192 } 193 194 private static class MasClientHandler extends Handler { 195 WeakReference<MasClient> mInst; 196 197 MasClientHandler(Looper looper, MasClient inst) { 198 super(looper); 199 mInst = new WeakReference<>(inst); 200 } 201 202 @Override 203 public void handleMessage(Message msg) { 204 MasClient inst = mInst.get(); 205 if (!inst.mConnected && msg.what != CONNECT) { 206 Log.w(TAG, "Cannot execute " + msg + " when not CONNECTED."); 207 return; 208 } 209 210 switch (msg.what) { 211 case CONNECT: 212 inst.connect(); 213 break; 214 215 case DISCONNECT: 216 inst.disconnect(); 217 break; 218 219 case REQUEST: 220 inst.executeRequest((Request) msg.obj); 221 break; 222 } 223 } 224 } 225 226} 227