1 /* 2 * Copyright (C) 2012 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 17 package com.android.nfc.handover; 18 19 import java.nio.BufferUnderflowException; 20 import java.nio.ByteBuffer; 21 import java.nio.charset.Charset; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.HashMap; 25 import java.util.Random; 26 27 import android.bluetooth.BluetoothAdapter; 28 import android.bluetooth.BluetoothDevice; 29 import android.content.BroadcastReceiver; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.IntentFilter; 34 import android.content.ServiceConnection; 35 import android.net.Uri; 36 import android.nfc.FormatException; 37 import android.nfc.NdefMessage; 38 import android.nfc.NdefRecord; 39 import android.os.Bundle; 40 import android.os.Handler; 41 import android.os.IBinder; 42 import android.os.Message; 43 import android.os.Messenger; 44 import android.os.RemoteException; 45 import android.os.UserHandle; 46 import android.util.Log; 47 48 /** 49 * Manages handover of NFC to other technologies. 50 */ 51 public class HandoverManager { 52 private static final String TAG = "NfcHandover"; 53 private static final boolean DBG = false; 54 55 private static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob" 56 .getBytes(Charset.forName("US_ASCII")); 57 private static final byte[] TYPE_BLE_OOB = "application/vnd.bluetooth.le.oob" 58 .getBytes(Charset.forName("US_ASCII")); 59 60 private static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(Charset.forName("US_ASCII")); 61 62 private static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr"; 63 64 private static final int CARRIER_POWER_STATE_INACTIVE = 0; 65 private static final int CARRIER_POWER_STATE_ACTIVE = 1; 66 private static final int CARRIER_POWER_STATE_ACTIVATING = 2; 67 private static final int CARRIER_POWER_STATE_UNKNOWN = 3; 68 69 private static final int BT_HANDOVER_TYPE_MAC = 0x1B; 70 private static final int BT_HANDOVER_TYPE_LE_ROLE = 0x1C; 71 private static final int BT_HANDOVER_TYPE_LONG_LOCAL_NAME = 0x09; 72 private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08; 73 public static final int BT_HANDOVER_LE_ROLE_CENTRAL_ONLY = 0x01; 74 75 static final String ACTION_WHITELIST_DEVICE = 76 "android.btopp.intent.action.WHITELIST_DEVICE"; 77 78 static final int MSG_HANDOVER_COMPLETE = 0; 79 static final int MSG_HEADSET_CONNECTED = 1; 80 static final int MSG_HEADSET_NOT_CONNECTED = 2; 81 82 private final Context mContext; 83 private final BluetoothAdapter mBluetoothAdapter; 84 private final MessageHandler mHandler = new MessageHandler(); 85 private final Messenger mMessenger = new Messenger(mHandler); 86 87 private final Object mLock = new Object(); 88 // Variables below synchronized on mLock 89 /* package as optimization */ HashMap<Integer, PendingHandoverTransfer> mPendingTransfers; 90 private ArrayList<Message> mPendingServiceMessages; 91 /* package as optimization */ boolean mBluetoothHeadsetPending; 92 /* package as optimization */ boolean mBluetoothHeadsetConnected; 93 protected boolean mBluetoothEnabledByNfc; 94 private int mHandoverTransferId; 95 private Messenger mService = null; 96 private boolean mBinding = false; 97 private boolean mBound; 98 private String mLocalBluetoothAddress; 99 private boolean mEnabled; 100 101 static class BluetoothHandoverData { 102 public boolean valid = false; 103 public BluetoothDevice device; 104 public String name; 105 public boolean carrierActivating = false; 106 public int transport = BluetoothDevice.TRANSPORT_AUTO; 107 } 108 109 class MessageHandler extends Handler { 110 @Override 111 public void handleMessage(Message msg) { 112 synchronized (mLock) { 113 switch (msg.what) { 114 case MSG_HANDOVER_COMPLETE: 115 int transferId = msg.arg1; 116 Log.d(TAG, "Completed transfer id: " + Integer.toString(transferId)); 117 if (mPendingTransfers.containsKey(transferId)) { 118 mPendingTransfers.remove(transferId); 119 } else { 120 Log.e(TAG, "Could not find completed transfer id: " + 121 Integer.toString(transferId)); 122 } 123 break; 124 case MSG_HEADSET_CONNECTED: 125 mBluetoothEnabledByNfc = msg.arg1 != 0; 126 mBluetoothHeadsetConnected = true; 127 mBluetoothHeadsetPending = false; 128 break; 129 case MSG_HEADSET_NOT_CONNECTED: 130 mBluetoothEnabledByNfc = false; // No need to maintain this state any longer 131 mBluetoothHeadsetConnected = false; 132 mBluetoothHeadsetPending = false; 133 break; 134 default: 135 break; 136 } 137 unbindServiceIfNeededLocked(false); 138 } 139 } 140 }; 141 142 private ServiceConnection mConnection = new ServiceConnection() { 143 @Override 144 public void onServiceConnected(ComponentName name, IBinder service) { 145 synchronized (mLock) { 146 mService = new Messenger(service); 147 mBinding = false; 148 mBound = true; 149 // Register this client and transfer last known service state 150 Message msg = Message.obtain(null, HandoverService.MSG_REGISTER_CLIENT); 151 msg.arg1 = mBluetoothEnabledByNfc ? 1 : 0; 152 msg.arg2 = mBluetoothHeadsetConnected ? 1 : 0; 153 msg.replyTo = mMessenger; 154 try { 155 mService.send(msg); 156 } catch (RemoteException e) { 157 Log.e(TAG, "Failed to register client"); 158 } 159 // Send all queued messages 160 while (!mPendingServiceMessages.isEmpty()) { 161 msg = mPendingServiceMessages.remove(0); 162 try { 163 mService.send(msg); 164 } catch (RemoteException e) { 165 Log.e(TAG, "Failed to send queued message to service"); 166 } 167 } 168 } 169 } 170 171 @Override 172 public void onServiceDisconnected(ComponentName name) { 173 synchronized (mLock) { 174 Log.d(TAG, "Service disconnected"); 175 if (mService != null) { 176 try { 177 Message msg = Message.obtain(null, HandoverService.MSG_DEREGISTER_CLIENT); 178 msg.replyTo = mMessenger; 179 mService.send(msg); 180 } catch (RemoteException e) { 181 // Service may have crashed - ignore 182 } 183 } 184 mService = null; 185 mBound = false; 186 mBluetoothHeadsetPending = false; 187 mPendingTransfers.clear(); 188 mPendingServiceMessages.clear(); 189 } 190 } 191 }; 192 193 public HandoverManager(Context context) { 194 mContext = context; 195 mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 196 mPendingTransfers = new HashMap<Integer, PendingHandoverTransfer>(); 197 mPendingServiceMessages = new ArrayList<Message>(); 198 199 IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED); 200 mContext.registerReceiver(mReceiver, filter, null, null); 201 mEnabled = true; 202 mBluetoothEnabledByNfc = false; 203 } 204 205 final BroadcastReceiver mReceiver = new BroadcastReceiver() { 206 @Override 207 public void onReceive(Context context, Intent intent) { 208 String action = intent.getAction(); 209 if (action.equals(Intent.ACTION_USER_SWITCHED)) { 210 // Just force unbind the service. 211 unbindServiceIfNeededLocked(true); 212 } 213 } 214 }; 215 216 /** 217 * @return whether the service was bound to successfully 218 */ 219 boolean bindServiceIfNeededLocked() { 220 if (!mBinding) { 221 Log.d(TAG, "Binding to handover service"); 222 boolean bindSuccess = mContext.bindServiceAsUser(new Intent(mContext, 223 HandoverService.class), mConnection, Context.BIND_AUTO_CREATE, 224 UserHandle.CURRENT); 225 mBinding = bindSuccess; 226 return bindSuccess; 227 } else { 228 // A previous bind is pending 229 return true; 230 } 231 } 232 233 void unbindServiceIfNeededLocked(boolean force) { 234 // If no service operation is pending, unbind 235 if (mBound && (force || (!mBluetoothHeadsetPending && mPendingTransfers.isEmpty()))) { 236 Log.d(TAG, "Unbinding from service."); 237 mContext.unbindService(mConnection); 238 mBound = false; 239 mPendingServiceMessages.clear(); 240 mBluetoothHeadsetPending = false; 241 mPendingTransfers.clear(); 242 } 243 return; 244 } 245 246 static NdefRecord createCollisionRecord() { 247 byte[] random = new byte[2]; 248 new Random().nextBytes(random); 249 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, RTD_COLLISION_RESOLUTION, null, random); 250 } 251 252 NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) { 253 byte[] payload = new byte[4]; 254 payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING : 255 CARRIER_POWER_STATE_ACTIVE); // Carrier Power State: Activating or active 256 payload[1] = 1; // length of carrier data reference 257 payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record 258 payload[3] = 0; // Auxiliary data reference count 259 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, payload); 260 } 261 262 NdefRecord createBluetoothOobDataRecord() { 263 byte[] payload = new byte[8]; 264 // Note: this field should be little-endian per the BTSSP spec 265 // The Android 4.1 implementation used big-endian order here. 266 // No single Android implementation has ever interpreted this 267 // length field when parsing this record though. 268 payload[0] = (byte) (payload.length & 0xFF); 269 payload[1] = (byte) ((payload.length >> 8) & 0xFF); 270 271 synchronized (mLock) { 272 if (mLocalBluetoothAddress == null) { 273 mLocalBluetoothAddress = mBluetoothAdapter.getAddress(); 274 } 275 276 byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress); 277 System.arraycopy(addressBytes, 0, payload, 2, 6); 278 } 279 280 return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload); 281 } 282 283 public void setEnabled(boolean enabled) { 284 synchronized (mLock) { 285 mEnabled = enabled; 286 } 287 } 288 289 public boolean isHandoverSupported() { 290 return (mBluetoothAdapter != null); 291 } 292 293 public NdefMessage createHandoverRequestMessage() { 294 if (mBluetoothAdapter == null) { 295 return null; 296 } 297 298 NdefRecord[] dataRecords = new NdefRecord[] { 299 createBluetoothOobDataRecord() 300 }; 301 return new NdefMessage( 302 createHandoverRequestRecord(), 303 dataRecords); 304 } 305 306 NdefMessage createBluetoothHandoverSelectMessage(boolean activating) { 307 return new NdefMessage(createHandoverSelectRecord( 308 createBluetoothAlternateCarrierRecord(activating)), 309 createBluetoothOobDataRecord()); 310 } 311 312 NdefRecord createHandoverSelectRecord(NdefRecord alternateCarrier) { 313 NdefMessage nestedMessage = new NdefMessage(alternateCarrier); 314 byte[] nestedPayload = nestedMessage.toByteArray(); 315 316 ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); 317 payload.put((byte)0x12); // connection handover v1.2 318 payload.put(nestedPayload); 319 320 byte[] payloadBytes = new byte[payload.position()]; 321 payload.position(0); 322 payload.get(payloadBytes); 323 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null, 324 payloadBytes); 325 } 326 327 NdefRecord createHandoverRequestRecord() { 328 NdefRecord[] messages = new NdefRecord[] { 329 createBluetoothAlternateCarrierRecord(false) 330 }; 331 332 NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(), messages); 333 334 byte[] nestedPayload = nestedMessage.toByteArray(); 335 336 ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); 337 payload.put((byte) 0x12); // connection handover v1.2 338 payload.put(nestedMessage.toByteArray()); 339 340 byte[] payloadBytes = new byte[payload.position()]; 341 payload.position(0); 342 payload.get(payloadBytes); 343 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null, 344 payloadBytes); 345 } 346 347 /** 348 * Return null if message is not a Handover Request, 349 * return the Handover Select response if it is. 350 */ 351 public NdefMessage tryHandoverRequest(NdefMessage m) { 352 if (m == null) return null; 353 if (mBluetoothAdapter == null) return null; 354 355 if (DBG) Log.d(TAG, "tryHandoverRequest():" + m.toString()); 356 357 NdefRecord handoverRequestRecord = m.getRecords()[0]; 358 if (handoverRequestRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { 359 return null; 360 } 361 362 if (!Arrays.equals(handoverRequestRecord.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) { 363 return null; 364 } 365 366 // we have a handover request, look for BT OOB record 367 BluetoothHandoverData bluetoothData = null; 368 for (NdefRecord dataRecord : m.getRecords()) { 369 if (dataRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) { 370 if (Arrays.equals(dataRecord.getType(), TYPE_BT_OOB)) { 371 bluetoothData = parseBtOob(ByteBuffer.wrap(dataRecord.getPayload())); 372 } 373 } 374 } 375 376 return tryBluetoothHandoverRequest(bluetoothData); 377 } 378 379 private NdefMessage tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData) { 380 NdefMessage selectMessage = null; 381 if (bluetoothData != null) { 382 383 // Note: there could be a race where we conclude 384 // that Bluetooth is already enabled, and shortly 385 // after the user turns it off. That will cause 386 // the transfer to fail, but there's nothing 387 // much we can do about it anyway. It shouldn't 388 // be common for the user to be changing BT settings 389 // while waiting to receive a picture. 390 boolean bluetoothActivating = !mBluetoothAdapter.isEnabled(); 391 synchronized (mLock) { 392 if (!mEnabled) return null; 393 394 Message msg = Message.obtain(null, HandoverService.MSG_START_INCOMING_TRANSFER); 395 PendingHandoverTransfer transfer 396 = registerBluetoothInTransferLocked(bluetoothData.device); 397 Bundle transferData = new Bundle(); 398 transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer); 399 msg.setData(transferData); 400 401 if (!sendOrQueueMessageLocked(msg)) { 402 removeTransferLocked(transfer.id); 403 return null; 404 } 405 } 406 // BT OOB found, whitelist it for incoming OPP data 407 whitelistOppDevice(bluetoothData.device); 408 409 // return BT OOB record so they can perform handover 410 selectMessage = (createBluetoothHandoverSelectMessage(bluetoothActivating)); 411 if (DBG) Log.d(TAG, "Waiting for incoming transfer, [" + 412 bluetoothData.device.getAddress() + "]->[" + mLocalBluetoothAddress + "]"); 413 } 414 415 return selectMessage; 416 } 417 418 public boolean sendOrQueueMessageLocked(Message msg) { 419 if (!mBound || mService == null) { 420 // Need to start service, let us know if we can queue the message 421 if (!bindServiceIfNeededLocked()) { 422 Log.e(TAG, "Could not start service"); 423 return false; 424 } 425 // Queue the message to send when the service is bound 426 mPendingServiceMessages.add(msg); 427 } else { 428 try { 429 mService.send(msg); 430 } catch (RemoteException e) { 431 Log.e(TAG, "Could not connect to handover service"); 432 return false; 433 } 434 } 435 return true; 436 } 437 438 public boolean tryHandover(NdefMessage m) { 439 if (m == null) return false; 440 if (mBluetoothAdapter == null) return false; 441 442 if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); 443 444 BluetoothHandoverData handover = parseBluetooth(m); 445 if (handover == null) return false; 446 if (!handover.valid) return true; 447 448 synchronized (mLock) { 449 if (!mEnabled) return false; 450 451 if (mBluetoothAdapter == null) { 452 if (DBG) Log.d(TAG, "BT handover, but BT not available"); 453 return true; 454 } 455 456 Message msg = Message.obtain(null, HandoverService.MSG_PERIPHERAL_HANDOVER, 0, 0); 457 Bundle headsetData = new Bundle(); 458 headsetData.putParcelable(HandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device); 459 headsetData.putString(HandoverService.EXTRA_PERIPHERAL_NAME, handover.name); 460 headsetData.putInt(HandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport); 461 msg.setData(headsetData); 462 return sendOrQueueMessageLocked(msg); 463 } 464 } 465 466 // This starts sending an Uri over BT 467 public void doHandoverUri(Uri[] uris, 468 NdefMessage handoverResponse) { 469 if (mBluetoothAdapter == null) return; 470 471 BluetoothHandoverData data = parseBluetooth(handoverResponse); 472 if (data != null && data.valid) { 473 // Register a new handover transfer object 474 synchronized (mLock) { 475 Message msg = Message.obtain(null, HandoverService.MSG_START_OUTGOING_TRANSFER, 0, 0); 476 PendingHandoverTransfer transfer = registerBluetoothOutTransferLocked(data, uris); 477 Bundle transferData = new Bundle(); 478 transferData.putParcelable(HandoverService.BUNDLE_TRANSFER, transfer); 479 msg.setData(transferData); 480 if (DBG) Log.d(TAG, "Initiating outgoing bluetooth transfer, [" + 481 mLocalBluetoothAddress + "]->[" + data.device.getAddress() + "]"); 482 sendOrQueueMessageLocked(msg); 483 } 484 } 485 } 486 487 PendingHandoverTransfer registerBluetoothInTransferLocked(BluetoothDevice remoteDevice) { 488 PendingHandoverTransfer transfer = PendingHandoverTransfer.forBluetoothDevice( 489 mHandoverTransferId++, true, remoteDevice, false, null); 490 mPendingTransfers.put(transfer.id, transfer); 491 492 return transfer; 493 } 494 495 PendingHandoverTransfer registerBluetoothOutTransferLocked(BluetoothHandoverData data, 496 Uri[] uris) { 497 PendingHandoverTransfer transfer = PendingHandoverTransfer.forBluetoothDevice( 498 mHandoverTransferId++, false, data.device, data.carrierActivating, uris); 499 mPendingTransfers.put(transfer.id, transfer); 500 return transfer; 501 } 502 503 void removeTransferLocked(int id) { 504 mPendingTransfers.remove(id); 505 } 506 507 void whitelistOppDevice(BluetoothDevice device) { 508 if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); 509 Intent intent = new Intent(ACTION_WHITELIST_DEVICE); 510 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 511 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 512 } 513 514 boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) { 515 byte[] payload = handoverRec.getPayload(); 516 if (payload == null || payload.length <= 1) return false; 517 // Skip version 518 byte[] payloadNdef = new byte[payload.length - 1]; 519 System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1); 520 NdefMessage msg; 521 try { 522 msg = new NdefMessage(payloadNdef); 523 } catch (FormatException e) { 524 return false; 525 } 526 527 for (NdefRecord alt : msg.getRecords()) { 528 byte[] acPayload = alt.getPayload(); 529 if (acPayload != null) { 530 ByteBuffer buf = ByteBuffer.wrap(acPayload); 531 int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits 532 int carrierRefLength = buf.get() & 0xFF; 533 if (carrierRefLength != carrierId.length) return false; 534 535 byte[] carrierRefId = new byte[carrierRefLength]; 536 buf.get(carrierRefId); 537 if (Arrays.equals(carrierRefId, carrierId)) { 538 // Found match, returning whether power state is activating 539 return (cps == CARRIER_POWER_STATE_ACTIVATING); 540 } 541 } 542 } 543 544 return true; 545 } 546 547 BluetoothHandoverData parseBluetoothHandoverSelect(NdefMessage m) { 548 // TODO we could parse this a lot more strictly; right now 549 // we just search for a BT OOB record, and try to cross-reference 550 // the carrier state inside the 'hs' payload. 551 for (NdefRecord oob : m.getRecords()) { 552 if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && 553 Arrays.equals(oob.getType(), TYPE_BT_OOB)) { 554 BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload())); 555 if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) { 556 data.carrierActivating = true; 557 } 558 return data; 559 } 560 561 if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && 562 Arrays.equals(oob.getType(), TYPE_BLE_OOB)) { 563 return parseBleOob(ByteBuffer.wrap(oob.getPayload())); 564 } 565 } 566 567 return null; 568 } 569 570 BluetoothHandoverData parseBluetooth(NdefMessage m) { 571 NdefRecord r = m.getRecords()[0]; 572 short tnf = r.getTnf(); 573 byte[] type = r.getType(); 574 575 // Check for BT OOB record 576 if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) { 577 return parseBtOob(ByteBuffer.wrap(r.getPayload())); 578 } 579 580 // Check for BLE OOB record 581 if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BLE_OOB)) { 582 return parseBleOob(ByteBuffer.wrap(r.getPayload())); 583 } 584 585 // Check for Handover Select, followed by a BT OOB record 586 if (tnf == NdefRecord.TNF_WELL_KNOWN && 587 Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) { 588 return parseBluetoothHandoverSelect(m); 589 } 590 591 // Check for Nokia BT record, found on some Nokia BH-505 Headsets 592 if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) { 593 return parseNokia(ByteBuffer.wrap(r.getPayload())); 594 } 595 596 return null; 597 } 598 599 BluetoothHandoverData parseNokia(ByteBuffer payload) { 600 BluetoothHandoverData result = new BluetoothHandoverData(); 601 result.valid = false; 602 603 try { 604 payload.position(1); 605 byte[] address = new byte[6]; 606 payload.get(address); 607 result.device = mBluetoothAdapter.getRemoteDevice(address); 608 result.valid = true; 609 payload.position(14); 610 int nameLength = payload.get(); 611 byte[] nameBytes = new byte[nameLength]; 612 payload.get(nameBytes); 613 result.name = new String(nameBytes, Charset.forName("UTF-8")); 614 } catch (IllegalArgumentException e) { 615 Log.i(TAG, "nokia: invalid BT address"); 616 } catch (BufferUnderflowException e) { 617 Log.i(TAG, "nokia: payload shorter than expected"); 618 } 619 if (result.valid && result.name == null) result.name = ""; 620 return result; 621 } 622 623 BluetoothHandoverData parseBtOob(ByteBuffer payload) { 624 BluetoothHandoverData result = new BluetoothHandoverData(); 625 result.valid = false; 626 627 try { 628 payload.position(2); // length 629 byte[] address = parseMacFromBluetoothRecord(payload); 630 result.device = mBluetoothAdapter.getRemoteDevice(address); 631 result.valid = true; 632 633 while (payload.remaining() > 0) { 634 byte[] nameBytes; 635 int len = payload.get(); 636 int type = payload.get(); 637 switch (type) { 638 case BT_HANDOVER_TYPE_SHORT_LOCAL_NAME: 639 nameBytes = new byte[len - 1]; 640 payload.get(nameBytes); 641 result.name = new String(nameBytes, Charset.forName("UTF-8")); 642 break; 643 case BT_HANDOVER_TYPE_LONG_LOCAL_NAME: 644 if (result.name != null) break; // prefer short name 645 nameBytes = new byte[len - 1]; 646 payload.get(nameBytes); 647 result.name = new String(nameBytes, Charset.forName("UTF-8")); 648 break; 649 default: 650 payload.position(payload.position() + len - 1); 651 break; 652 } 653 } 654 } catch (IllegalArgumentException e) { 655 Log.i(TAG, "BT OOB: invalid BT address"); 656 } catch (BufferUnderflowException e) { 657 Log.i(TAG, "BT OOB: payload shorter than expected"); 658 } 659 if (result.valid && result.name == null) result.name = ""; 660 return result; 661 } 662 663 BluetoothHandoverData parseBleOob(ByteBuffer payload) { 664 BluetoothHandoverData result = new BluetoothHandoverData(); 665 result.valid = false; 666 result.transport = BluetoothDevice.TRANSPORT_LE; 667 668 try { 669 670 while (payload.remaining() > 0) { 671 byte[] nameBytes; 672 int len = payload.get(); 673 int type = payload.get(); 674 switch (type) { 675 case BT_HANDOVER_TYPE_MAC: // mac address 676 byte[] address = parseMacFromBluetoothRecord(payload); 677 payload.position(payload.position() + 1); // advance over random byte 678 result.device = mBluetoothAdapter.getRemoteDevice(address); 679 result.valid = true; 680 break; 681 case BT_HANDOVER_TYPE_LE_ROLE: 682 byte role = payload.get(); 683 if (role == BT_HANDOVER_LE_ROLE_CENTRAL_ONLY) { 684 // only central role supported, can't pair 685 result.valid = false; 686 return result; 687 } 688 break; 689 case BT_HANDOVER_TYPE_LONG_LOCAL_NAME: 690 nameBytes = new byte[len - 1]; 691 payload.get(nameBytes); 692 result.name = new String(nameBytes, Charset.forName("UTF-8")); 693 break; 694 default: 695 payload.position(payload.position() + len - 1); 696 break; 697 } 698 } 699 } catch (IllegalArgumentException e) { 700 Log.i(TAG, "BT OOB: invalid BT address"); 701 } catch (BufferUnderflowException e) { 702 Log.i(TAG, "BT OOB: payload shorter than expected"); 703 } 704 if (result.valid && result.name == null) result.name = ""; 705 return result; 706 } 707 708 private byte[] parseMacFromBluetoothRecord(ByteBuffer payload) { 709 byte[] address = new byte[6]; 710 payload.get(address); 711 // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for 712 // ByteBuffer.get(byte[]), so manually swap order 713 for (int i = 0; i < 3; i++) { 714 byte temp = address[i]; 715 address[i] = address[5 - i]; 716 address[5 - i] = temp; 717 } 718 return address; 719 } 720 721 static byte[] addressToReverseBytes(String address) { 722 String[] split = address.split(":"); 723 byte[] result = new byte[split.length]; 724 725 for (int i = 0; i < split.length; i++) { 726 // need to parse as int because parseByte() expects a signed byte 727 result[split.length - 1 - i] = (byte)Integer.parseInt(split[i], 16); 728 } 729 730 return result; 731 } 732 733 final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); 734 private static String byteArrayToHexString(byte[] bytes) { 735 char[] hexChars = new char[bytes.length * 2]; 736 for ( int j = 0; j < bytes.length; j++ ) { 737 int v = bytes[j] & 0xFF; 738 hexChars[j * 2] = hexArray[v >>> 4]; 739 hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 740 } 741 return new String(hexChars); 742 } 743 } 744 745