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