Home | History | Annotate | Download | only in handover
      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