PbapClientConnectionHandler.java revision ea2543a5009459fee3581d2fd8b2271511d31a4c
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 */ 16package com.android.bluetooth.pbapclient; 17 18import android.accounts.Account; 19import android.accounts.AccountManager; 20import android.bluetooth.BluetoothAdapter; 21import android.bluetooth.BluetoothDevice; 22import android.bluetooth.BluetoothSocket; 23import android.bluetooth.BluetoothUuid; 24import android.bluetooth.SdpPseRecord; 25import android.content.Context; 26import android.os.Handler; 27import android.os.Looper; 28import android.os.Message; 29import android.provider.CallLog; 30import android.util.Log; 31 32import com.android.bluetooth.BluetoothObexTransport; 33import com.android.bluetooth.R; 34 35import java.io.IOException; 36 37import javax.obex.ClientSession; 38import javax.obex.HeaderSet; 39import javax.obex.ResponseCodes; 40 41/* Bluetooth/pbapclient/PbapClientConnectionHandler is responsible 42 * for connecting, disconnecting and downloading contacts from the 43 * PBAP PSE when commanded. It receives all direction from the 44 * controlling state machine. 45 */ 46class PbapClientConnectionHandler extends Handler { 47 static final String TAG = "PBAP PCE handler"; 48 static final boolean DBG = true; 49 static final int MSG_CONNECT = 1; 50 static final int MSG_DISCONNECT = 2; 51 static final int MSG_DOWNLOAD = 3; 52 53 // The following constants are pulled from the Bluetooth Phone Book Access Profile specification 54 // 1.1 55 private static final byte[] PBAP_TARGET = new byte[]{ 56 0x79, 0x61, 0x35, (byte) 0xf0, (byte) 0xf0, (byte) 0xc5, 0x11, (byte) 0xd8, 0x09, 0x66, 57 0x08, 0x00, 0x20, 0x0c, (byte) 0x9a, 0x66 58 }; 59 60 private static final int PBAP_FEATURE_DEFAULT_IMAGE_FORMAT = 0x00000200; 61 private static final int PBAP_FEATURE_BROWSING = 0x00000002; 62 private static final int PBAP_FEATURE_DOWNLOADING = 0x00000001; 63 64 private static final int PBAP_SUPPORTED_FEATURE = 65 PBAP_FEATURE_DEFAULT_IMAGE_FORMAT | PBAP_FEATURE_BROWSING | PBAP_FEATURE_DOWNLOADING; 66 private static final int PBAP_V1_2 = 0x0102; 67 private static final int L2CAP_INVALID_PSM = -1; 68 69 public static final String PB_PATH = "telecom/pb.vcf"; 70 public static final String MCH_PATH = "telecom/mch.vcf"; 71 public static final String ICH_PATH = "telecom/ich.vcf"; 72 public static final String OCH_PATH = "telecom/och.vcf"; 73 public static final byte VCARD_TYPE_21 = 0; 74 public static final byte VCARD_TYPE_30 = 1; 75 76 private Account mAccount; 77 private AccountManager mAccountManager; 78 private BluetoothSocket mSocket; 79 private final BluetoothAdapter mAdapter; 80 private final BluetoothDevice mDevice; 81 // PSE SDP Record for current device. 82 private SdpPseRecord mPseRec = null; 83 private ClientSession mObexSession; 84 private Context mContext; 85 private BluetoothPbapObexAuthenticator mAuth = null; 86 private final PbapClientStateMachine mPbapClientStateMachine; 87 private boolean mAccountCreated; 88 89 PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine, 90 BluetoothDevice device) { 91 super(looper); 92 mAdapter = BluetoothAdapter.getDefaultAdapter(); 93 mDevice = device; 94 mContext = context; 95 mPbapClientStateMachine = stateMachine; 96 mAuth = new BluetoothPbapObexAuthenticator(this); 97 mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext()); 98 mAccount = new Account(mDevice.getAddress(), mContext.getString( 99 R.string.pbap_account_type)); 100 } 101 102 /** 103 * Constructs PCEConnectionHandler object 104 * 105 * @param Builder To build BluetoothPbapClientHandler Instance. 106 */ 107 PbapClientConnectionHandler(Builder pceHandlerbuild) { 108 super(pceHandlerbuild.looper); 109 mAdapter = BluetoothAdapter.getDefaultAdapter(); 110 mDevice = pceHandlerbuild.device; 111 mContext = pceHandlerbuild.context; 112 mPbapClientStateMachine = pceHandlerbuild.clientStateMachine; 113 mAuth = new BluetoothPbapObexAuthenticator(this); 114 mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext()); 115 mAccount = new Account(mDevice.getAddress(), mContext.getString( 116 R.string.pbap_account_type)); 117 } 118 119 public static class Builder { 120 121 private Looper looper; 122 private Context context; 123 private BluetoothDevice device; 124 private PbapClientStateMachine clientStateMachine; 125 126 public Builder setLooper(Looper loop) { 127 this.looper = loop; 128 return this; 129 } 130 131 public Builder setClientSM(PbapClientStateMachine clientStateMachine) { 132 this.clientStateMachine = clientStateMachine; 133 return this; 134 } 135 136 public Builder setRemoteDevice(BluetoothDevice device) { 137 this.device = device; 138 return this; 139 } 140 141 public Builder setContext(Context context) { 142 this.context = context; 143 return this; 144 } 145 146 public PbapClientConnectionHandler build() { 147 PbapClientConnectionHandler pbapClientHandler = new PbapClientConnectionHandler(this); 148 return pbapClientHandler; 149 } 150 151 } 152 153 @Override 154 public void handleMessage(Message msg) { 155 if (DBG) Log.d(TAG, "Handling Message = " + msg.what); 156 switch (msg.what) { 157 case MSG_CONNECT: 158 mPseRec = (SdpPseRecord) msg.obj; 159 /* To establish a connection, first open a socket and then create an OBEX session */ 160 if (connectSocket()) { 161 if (DBG) Log.d(TAG, "Socket connected"); 162 } else { 163 Log.w(TAG, "Socket CONNECT Failure "); 164 mPbapClientStateMachine.obtainMessage( 165 PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget(); 166 return; 167 } 168 169 if (connectObexSession()) { 170 mPbapClientStateMachine.obtainMessage( 171 PbapClientStateMachine.MSG_CONNECTION_COMPLETE).sendToTarget(); 172 } else { 173 mPbapClientStateMachine.obtainMessage( 174 PbapClientStateMachine.MSG_CONNECTION_FAILED).sendToTarget(); 175 } 176 break; 177 178 case MSG_DISCONNECT: 179 if (DBG) Log.d(TAG, "Starting Disconnect"); 180 try { 181 if (mObexSession != null) { 182 if (DBG) Log.d(TAG, "obexSessionDisconnect" + mObexSession); 183 mObexSession.disconnect(null); 184 mObexSession.close(); 185 } 186 187 if (DBG) Log.d(TAG, "Closing Socket"); 188 closeSocket(); 189 } catch (IOException e) { 190 Log.w(TAG, "DISCONNECT Failure ", e); 191 } 192 if (DBG) Log.d(TAG, "Completing Disconnect"); 193 removeAccount(mAccount); 194 mContext.getContentResolver() 195 .delete(CallLog.Calls.CONTENT_URI, null, null); 196 mPbapClientStateMachine.obtainMessage( 197 PbapClientStateMachine.MSG_CONNECTION_CLOSED).sendToTarget(); 198 break; 199 200 case MSG_DOWNLOAD: 201 try { 202 mAccountCreated = addAccount(mAccount); 203 if (mAccountCreated == false) { 204 Log.e(TAG, "Account creation failed."); 205 return; 206 } 207 // Start at contact 1 to exclued Owner Card PBAP 1.1 sec 3.1.5.2 208 BluetoothPbapRequestPullPhoneBook request = 209 new BluetoothPbapRequestPullPhoneBook(PB_PATH, mAccount, 0, 210 VCARD_TYPE_30, 0, 1); 211 request.execute(mObexSession); 212 PhonebookPullRequest processor = 213 new PhonebookPullRequest(mPbapClientStateMachine.getContext(), 214 mAccount); 215 processor.setResults(request.getList()); 216 processor.onPullComplete(); 217 218 downloadCallLog(MCH_PATH); 219 downloadCallLog(ICH_PATH); 220 downloadCallLog(OCH_PATH); 221 } catch (IOException e) { 222 Log.w(TAG, "DOWNLOAD_CONTACTS Failure" + e.toString()); 223 } 224 break; 225 226 default: 227 Log.w(TAG, "Received Unexpected Message"); 228 } 229 return; 230 } 231 232 /* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified 233 * channel, or RFCOMM default channel. */ 234 private boolean connectSocket() { 235 try { 236 /* Use BluetoothSocket to connect */ 237 if (mPseRec == null) { 238 // BackWardCompatability: Fall back to create RFCOMM through UUID. 239 Log.v(TAG, "connectSocket: UUID: " + BluetoothUuid.PBAP_PSE.getUuid()); 240 mSocket = mDevice.createRfcommSocketToServiceRecord( 241 BluetoothUuid.PBAP_PSE.getUuid()); 242 } else if (mPseRec.getL2capPsm() != L2CAP_INVALID_PSM) { 243 Log.v(TAG, "connectSocket: PSM: " + mPseRec.getL2capPsm()); 244 mSocket = mDevice.createL2capSocket(mPseRec.getL2capPsm()); 245 } else { 246 Log.v(TAG, "connectSocket: channel: " + mPseRec.getRfcommChannelNumber()); 247 mSocket = mDevice.createRfcommSocket(mPseRec.getRfcommChannelNumber()); 248 } 249 250 if (mSocket != null) { 251 mSocket.connect(); 252 return true; 253 } else { 254 Log.w(TAG, "Could not create socket"); 255 } 256 } catch (IOException e) { 257 Log.e(TAG, "Error while connecting socket", e); 258 } 259 return false; 260 } 261 262 /* Connect an OBEX session over the already connected socket. First establish an OBEX Transport 263 * abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */ 264 private boolean connectObexSession() { 265 boolean connectionSuccessful = false; 266 267 try { 268 if (DBG) Log.v(TAG, "Start Obex Client Session"); 269 BluetoothObexTransport transport = new BluetoothObexTransport(mSocket); 270 mObexSession = new ClientSession(transport); 271 mObexSession.setAuthenticator(mAuth); 272 273 HeaderSet connectionRequest = new HeaderSet(); 274 connectionRequest.setHeader(HeaderSet.TARGET, PBAP_TARGET); 275 276 if (mPseRec != null) { 277 if (DBG) { 278 Log.d(TAG, "Remote PbapSupportedFeatures " 279 + mPseRec.getSupportedFeatures()); 280 } 281 282 ObexAppParameters oap = new ObexAppParameters(); 283 284 if (mPseRec.getProfileVersion() >= PBAP_V1_2) { 285 oap.add(BluetoothPbapRequest.OAP_TAGID_PBAP_SUPPORTED_FEATURES, 286 PBAP_SUPPORTED_FEATURE); 287 } 288 289 oap.addToHeaderSet(connectionRequest); 290 } 291 HeaderSet connectionResponse = mObexSession.connect(connectionRequest); 292 293 connectionSuccessful = (connectionResponse.getResponseCode() == 294 ResponseCodes.OBEX_HTTP_OK); 295 if (DBG) Log.d(TAG, "Success = " + Boolean.toString(connectionSuccessful)); 296 } catch (IOException e) { 297 Log.w(TAG, "CONNECT Failure " + e.toString()); 298 closeSocket(); 299 } 300 return connectionSuccessful; 301 } 302 303 public void abort() { 304 // Perform forced cleanup, it is ok if the handler throws an exception this will free the 305 // handler to complete what it is doing and finish with cleanup. 306 closeSocket(); 307 this.getLooper().getThread().interrupt(); 308 } 309 310 private void closeSocket() { 311 try { 312 if (mSocket != null) { 313 if (DBG) Log.d(TAG, "Closing socket" + mSocket); 314 mSocket.close(); 315 mSocket = null; 316 } 317 } catch (IOException e) { 318 Log.e(TAG, "Error when closing socket", e); 319 mSocket = null; 320 } 321 } 322 323 void downloadCallLog(String path) { 324 try { 325 BluetoothPbapRequestPullPhoneBook request = 326 new BluetoothPbapRequestPullPhoneBook(path, mAccount, 0, VCARD_TYPE_30, 0, 0); 327 request.execute(mObexSession); 328 CallLogPullRequest processor = 329 new CallLogPullRequest(mPbapClientStateMachine.getContext(), path); 330 processor.setResults(request.getList()); 331 processor.onPullComplete(); 332 } catch (IOException e) { 333 Log.w(TAG, "Download call log failure"); 334 } 335 } 336 337 private boolean addAccount(Account account) { 338 if (mAccountManager.addAccountExplicitly(account, null, null)) { 339 if (DBG) { 340 Log.d(TAG, "Added account " + mAccount); 341 } 342 return true; 343 } 344 return false; 345 } 346 347 private void removeAccount(Account acc) { 348 if (mAccountManager.removeAccountExplicitly(acc)) { 349 if (DBG) { 350 Log.d(TAG, "Removed account " + acc); 351 } 352 } else { 353 Log.e(TAG, "Failed to remove account " + mAccount); 354 } 355 } 356} 357