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