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