1a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly/* 2a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Copyright (C) 2012 The Android Open Source Project 3a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * 4a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Licensed under the Apache License, Version 2.0 (the "License"); 5a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * you may not use this file except in compliance with the License. 6a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * You may obtain a copy of the License at 7a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * 8a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * http://www.apache.org/licenses/LICENSE-2.0 9a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * 10a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Unless required by applicable law or agreed to in writing, software 11a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * distributed under the License is distributed on an "AS IS" BASIS, 12a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * See the License for the specific language governing permissions and 14a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * limitations under the License. 15a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly */ 16a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 17a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellypackage com.android.nfc.handover; 18a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 19226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport java.io.File; 20a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport java.nio.BufferUnderflowException; 21a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport java.nio.ByteBuffer; 22a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport java.nio.charset.Charset; 23402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport java.text.SimpleDateFormat; 24402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport java.util.ArrayList; 25a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport java.util.Arrays; 26402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport java.util.Date; 27226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport java.util.HashMap; 28d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenenimport java.util.Iterator; 29d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenenimport java.util.Map; 3043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pellyimport java.util.Random; 31a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 32226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.app.Notification; 33226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.app.NotificationManager; 34226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.app.PendingIntent; 35226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.app.Notification.Builder; 36a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothAdapter; 37a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.bluetooth.BluetoothDevice; 38226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.content.BroadcastReceiver; 39402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport android.content.ContentResolver; 40a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.content.Context; 4143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pellyimport android.content.Intent; 42226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.content.IntentFilter; 43402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport android.media.MediaScannerConnection; 4443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pellyimport android.net.Uri; 45d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenenimport android.nfc.FormatException; 46a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.nfc.NdefMessage; 47a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.nfc.NdefRecord; 48402da07068333f9393fd960a4c90df39b34bf668Martijn Coenenimport android.os.Environment; 49226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.os.Handler; 50226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.os.Message; 51226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.os.SystemClock; 5288913e627332e95f916f59c826b075e7e5959d36Martijn Coenenimport android.os.UserHandle; 53a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pellyimport android.util.Log; 54226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenenimport android.util.Pair; 554fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen 56d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenenimport com.android.nfc.NfcService; 57d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenenimport com.android.nfc.R; 58d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 59a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 60a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly/** 61a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly * Manages handover of NFC to other technologies. 62a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly */ 63a112f9c00e3337ef38ea8e1715a99db4966c7219Martijn Coenenpublic class HandoverManager implements BluetoothHeadsetHandover.Callback { 64a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly static final String TAG = "NfcHandover"; 65a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly static final boolean DBG = true; 66a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 67a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(Charset.forName("US_ASCII")); 68a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob". 69a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly getBytes(Charset.forName("US_ASCII")); 70a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 7163cccc4b5096fd9cbcb08410e69f7932375b4bc6Martijn Coenen static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr"; 7263cccc4b5096fd9cbcb08410e69f7932375b4bc6Martijn Coenen 73d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String ACTION_BT_OPP_TRANSFER_PROGRESS = 74226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.action.BT_OPP_TRANSFER_PROGRESS"; 75226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 76d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String ACTION_BT_OPP_TRANSFER_DONE = 77226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.action.BT_OPP_TRANSFER_DONE"; 78226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 79d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_STATUS = 80226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_STATUS"; 81226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 82402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_MIMETYPE = 83402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_MIMETYPE"; 84402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 85402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final String EXTRA_BT_OPP_ADDRESS = 86402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen "android.btopp.intent.extra.BT_OPP_ADDRESS"; 87402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 88d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0; 89226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 90d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1; 91226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 92d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_DIRECTION = 93226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_DIRECTION"; 94226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 95d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int DIRECTION_BLUETOOTH_INCOMING = 0; 96226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 97d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int DIRECTION_BLUETOOTH_OUTGOING = 1; 98226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 99d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_ID = 100226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_ID"; 101226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 102d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_PROGRESS = 103226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_PROGRESS"; 104226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 105d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String EXTRA_BT_OPP_TRANSFER_URI = 106226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.extra.BT_OPP_TRANSFER_URI"; 107226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 108226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // permission needed to be able to receive handover status requests 109d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String HANDOVER_STATUS_PERMISSION = 110226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "com.android.permission.HANDOVER_STATUS"; 111226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 112226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen static final int MSG_HANDOVER_POWER_CHECK = 0; 113226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 114226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // We poll whether we can safely disable BT every POWER_CHECK_MS 115226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen static final int POWER_CHECK_MS = 20000; 116226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 117d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final String ACTION_WHITELIST_DEVICE = 118226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen "android.btopp.intent.action.WHITELIST_DEVICE"; 119226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 1203bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen static final String ACTION_CANCEL_HANDOVER_TRANSFER = 1213bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER"; 1223bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen static final String EXTRA_SOURCE_ADDRESS = 1233bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen "com.android.nfc.handover.extra.SOURCE_ADDRESS"; 1243bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 125d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int SOURCE_BLUETOOTH_INCOMING = 0; 126226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 127d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int SOURCE_BLUETOOTH_OUTGOING = 1; 128226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 129d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int CARRIER_POWER_STATE_INACTIVE = 0; 130d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int CARRIER_POWER_STATE_ACTIVE = 1; 131d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int CARRIER_POWER_STATE_ACTIVATING = 2; 132d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen static final int CARRIER_POWER_STATE_UNKNOWN = 3; 133226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 134a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly final Context mContext; 135a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly final BluetoothAdapter mBluetoothAdapter; 136226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen final NotificationManager mNotificationManager; 137226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen final HandoverPowerManager mHandoverPowerManager; 138a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 1393bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // Variables below synchronized on HandoverManager.this 1403bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen final HashMap<Pair<String, Boolean>, HandoverTransfer> mTransfers; 1413bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 142a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHeadsetHandover mBluetoothHeadsetHandover; 143226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen boolean mBluetoothHeadsetConnected; 144226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 145d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen String mLocalBluetoothAddress; 146226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen int mNotificationId; 147a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 148a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly static class BluetoothHandoverData { 149a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly public boolean valid = false; 150a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly public BluetoothDevice device; 151a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly public String name; 152d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen public boolean carrierActivating = false; 153a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 154a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 155226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen class HandoverPowerManager implements Handler.Callback { 156226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen final Handler handler; 157226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen final Context context; 158226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 159226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen public HandoverPowerManager(Context context) { 160226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.handler = new Handler(this); 161226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.context = context; 162226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 163226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 164226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen /** 165226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen * Enables Bluetooth and will automatically disable it 166226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen * when there is no Bluetooth activity intitiated by NFC 167226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen * anymore. 168226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen */ 169402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized boolean enableBluetooth() { 170226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // Enable BT 1710af2f3baa4b58419f6d4268fb83d9a2952dceba0Martijn Coenen boolean result = mBluetoothAdapter.enableNoAutoConnect(); 172226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 173226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen if (result) { 174226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // Start polling for BT activity to make sure we eventually disable 175226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // it again. 176226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS); 177226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 178226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return result; 179226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 180226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 181402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized boolean isBluetoothEnabled() { 182226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return mBluetoothAdapter.isEnabled(); 183226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 184226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 185402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized void resetTimer() { 186402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (handler.hasMessages(MSG_HANDOVER_POWER_CHECK)) { 187402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen handler.removeMessages(MSG_HANDOVER_POWER_CHECK); 188402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS); 189402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 190402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 191402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 192d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen void stopMonitoring() { 193d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen handler.removeMessages(MSG_HANDOVER_POWER_CHECK); 194d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 195d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 196226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen @Override 197226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen public boolean handleMessage(Message msg) { 198226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen switch (msg.what) { 199226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen case MSG_HANDOVER_POWER_CHECK: 200226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // Check for any alive transfers 201226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen boolean transferAlive = false; 202226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen synchronized (HandoverManager.this) { 203226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen for (HandoverTransfer transfer : mTransfers.values()) { 204226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen if (transfer.isRunning()) { 205226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen transferAlive = true; 206226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 207226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 208226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 209226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen if (!transferAlive && !mBluetoothHeadsetConnected) { 210226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen mBluetoothAdapter.disable(); 211226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen handler.removeMessages(MSG_HANDOVER_POWER_CHECK); 212226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else { 213226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen handler.sendEmptyMessageDelayed(MSG_HANDOVER_POWER_CHECK, POWER_CHECK_MS); 214226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 215226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 216226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return true; 217226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 218226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return false; 219226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 220226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 221226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 222402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen /** 223402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * A HandoverTransfer object represents a set of files 224402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * that were received through NFC connection handover 225402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * from the same source address. 226402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * 227402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * For Bluetooth, files are received through OPP, and 228402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * we have no knowledge how many files will be transferred 229402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * as part of a single transaction. 230402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * Hence, a transfer has a notion of being "alive": if 231402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * the last update to a transfer was within WAIT_FOR_NEXT_TRANSFER_MS 232402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * milliseconds, we consider a new file transfer from the 233402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * same source address as part of the same transfer. 234402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * The corresponding URIs will be grouped in a single folder. 235402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen * 236402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen */ 237402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen class HandoverTransfer implements Handler.Callback, 238402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen MediaScannerConnection.OnScanCompletedListener { 2393bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // In the states below we still accept new file transfer 240226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen static final int STATE_NEW = 0; 241226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen static final int STATE_IN_PROGRESS = 1; 242402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int STATE_W4_NEXT_TRANSFER = 2; 2433bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 2443bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // In the states below no new files are accepted. 245402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int STATE_W4_MEDIA_SCANNER = 3; 246402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int STATE_FAILED = 4; 247402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int STATE_SUCCESS = 5; 248402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int STATE_CANCELLED = 6; 249402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 250402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int MSG_NEXT_TRANSFER_TIMER = 0; 2513bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen static final int MSG_TRANSFER_TIMEOUT = 1; 252226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 253226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // We need to receive an update within this time period 254226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // to still consider this transfer to be "alive" (ie 255226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen // a reason to keep the handover transport enabled). 2560de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen static final int ALIVE_CHECK_MS = 20000; 257226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 258402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // The amount of time to wait for a new transfer 259402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // once the current one completes. 260402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final int WAIT_FOR_NEXT_TRANSFER_MS = 4000; 261402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 262402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen static final String BEAM_DIR = "beam"; 263402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 264402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen final BluetoothDevice device; 265402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen final String sourceAddress; 266402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen final boolean incoming; // whether this is an incoming transfer 267402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen final int notificationId; // Unique ID of this transfer used for notifications 268402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen final Handler handler; 2693bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen final PendingIntent cancelIntent; 270402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 271402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen int state; 272226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen Long lastUpdate; // Last time an event occurred for this transfer 273226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen float progress; // Progress in range [0..1] 274402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen ArrayList<Uri> btUris; // Received uris from Bluetooth OPP 275402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen ArrayList<String> btMimeTypes; // Mime-types received from Bluetooth OPP 276226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 277402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen ArrayList<String> paths; // Raw paths on the filesystem for Beam-stored files 278402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen HashMap<String, String> mimeTypes; // Mime-types associated with each path 279402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen HashMap<String, Uri> mediaUris; // URIs found by the media scanner for each path 280402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen int urisScanned; 281402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 282402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public HandoverTransfer(String sourceAddress, boolean incoming) { 283226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen synchronized (HandoverManager.this) { 284226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.notificationId = mNotificationId++; 285226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 286226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.lastUpdate = SystemClock.elapsedRealtime(); 287226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.progress = 0.0f; 288226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.state = STATE_NEW; 289402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.btUris = new ArrayList<Uri>(); 290402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.btMimeTypes = new ArrayList<String>(); 291402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.paths = new ArrayList<String>(); 292402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.mimeTypes = new HashMap<String, String>(); 293402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.mediaUris = new HashMap<String, Uri>(); 294402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.sourceAddress = sourceAddress; 295226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.incoming = incoming; 296402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.handler = new Handler(mContext.getMainLooper(), this); 2973bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen this.cancelIntent = buildCancelIntent(); 298402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.urisScanned = 0; 299402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.device = mBluetoothAdapter.getRemoteDevice(sourceAddress); 3003bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 3013bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen handler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS); 302226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 303226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 304402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public synchronized void updateFileProgress(float progress) { 305402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!isRunning()) return; // Ignore when we're no longer running 306402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 307402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen handler.removeMessages(MSG_NEXT_TRANSFER_TIMER); 308402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 309226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen this.progress = progress; 310226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 311402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We're still receiving data from this device - keep it in 312402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // the whitelist for a while longer 313402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (incoming) whitelistOppDevice(device); 314402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 315402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_IN_PROGRESS); 316226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 317226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 318402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public synchronized void finishTransfer(boolean success, Uri uri, String mimeType) { 319402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!isRunning()) return; // Ignore when we're no longer running 320402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 321226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen if (success && uri != null) { 322402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (DBG) Log.d(TAG, "Transfer success, uri " + uri + " mimeType " + mimeType); 323402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.progress = 1.0f; 324402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (mimeType == null) { 325402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mimeType = BluetoothOppHandover.getMimeTypeForUri(mContext, uri); 326402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 327402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (mimeType != null) { 328402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen btUris.add(uri); 329402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen btMimeTypes.add(mimeType); 330402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 331402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (DBG) Log.d(TAG, "Could not get mimeType for file."); 332402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 333226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else { 334402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Log.e(TAG, "Handover transfer failed"); 335402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // Do wait to see if there's another file coming. 336226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 337402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen handler.removeMessages(MSG_NEXT_TRANSFER_TIMER); 338402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen handler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS); 339402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_W4_NEXT_TRANSFER); 340226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 341226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 342402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public synchronized boolean isRunning() { 343402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (state != STATE_NEW && state != STATE_IN_PROGRESS && state != STATE_W4_NEXT_TRANSFER) { 344402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return false; 345226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else { 346226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return true; 347226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 348226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 349226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 3503bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen synchronized void cancel() { 3513bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (!isRunning()) return; 3523bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 3533bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // Delete all files received so far 3543bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen for (Uri uri : btUris) { 3553bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen File file = new File(uri.getPath()); 3563bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (file.exists()) file.delete(); 3573bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 3583bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 3593bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen updateStateAndNotification(STATE_CANCELLED); 3603bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 3613bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 362226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen synchronized void updateNotification() { 363226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen if (!incoming) return; // No notifications for outgoing transfers 364226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 365226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen Builder notBuilder = new Notification.Builder(mContext); 366226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 367402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (state == STATE_NEW || state == STATE_IN_PROGRESS || 368402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen state == STATE_W4_NEXT_TRANSFER || state == STATE_W4_MEDIA_SCANNER) { 369226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setAutoCancel(false); 370226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setSmallIcon(android.R.drawable.stat_sys_download); 371d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setTicker(mContext.getString(R.string.beam_progress)); 372d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setContentTitle(mContext.getString(R.string.beam_progress)); 3733bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.addAction(R.drawable.ic_menu_cancel_holo_dark, 3743bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen mContext.getString(R.string.cancel), cancelIntent); 3753bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setDeleteIntent(cancelIntent); 376402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We do have progress indication on a per-file basis, but in a multi-file 377402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // transfer we don't know the total progress. So for now, just show an 378402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // indeterminate progress bar. 379402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen notBuilder.setProgress(100, 0, true); 380226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else if (state == STATE_SUCCESS) { 381226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setAutoCancel(true); 382226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done); 383d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setTicker(mContext.getString(R.string.beam_complete)); 384d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setContentTitle(mContext.getString(R.string.beam_complete)); 385d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view)); 386226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 387402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Intent viewIntent = buildViewIntent(); 38888913e627332e95f916f59c826b075e7e5959d36Martijn Coenen PendingIntent contentIntent = PendingIntent.getActivityAsUser( 38988913e627332e95f916f59c826b075e7e5959d36Martijn Coenen mContext, 0, viewIntent, 0, null, UserHandle.CURRENT); 390226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 391226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setContentIntent(contentIntent); 392d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 393d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // Play Beam success sound 394d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NfcService.getInstance().playSound(NfcService.SOUND_END); 395226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else if (state == STATE_FAILED) { 3963bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setAutoCancel(false); 397226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done); 398d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setTicker(mContext.getString(R.string.beam_failed)); 399d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen notBuilder.setContentTitle(mContext.getString(R.string.beam_failed)); 4003bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } else if (state == STATE_CANCELLED) { 4013bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setAutoCancel(false); 4023bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setSmallIcon(android.R.drawable.stat_sys_download_done); 4033bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setTicker(mContext.getString(R.string.beam_canceled)); 4043bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen notBuilder.setContentTitle(mContext.getString(R.string.beam_canceled)); 405226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } else { 406226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen return; 407226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 408226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 40988913e627332e95f916f59c826b075e7e5959d36Martijn Coenen mNotificationManager.notifyAsUser(null, mNotificationId, notBuilder.build(), 41088913e627332e95f916f59c826b075e7e5959d36Martijn Coenen UserHandle.CURRENT); 411226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 412402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 413402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized void updateStateAndNotification(int newState) { 414402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.state = newState; 415402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen this.lastUpdate = SystemClock.elapsedRealtime(); 4163bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 4173bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (handler.hasMessages(MSG_TRANSFER_TIMEOUT)) { 4183bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // Update timeout timer 4193bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen handler.removeMessages(MSG_TRANSFER_TIMEOUT); 4203bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen handler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS); 4213bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 422402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateNotification(); 423402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 424402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 425402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized void processFiles() { 426402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // Check the amount of files we received in this transfer; 427402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // If more than one, create a separate directory for it. 428402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String extRoot = Environment.getExternalStorageDirectory().getPath(); 429402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen File beamPath = new File(extRoot + "/" + BEAM_DIR); 430402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 431402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!checkMediaStorage(beamPath) || btUris.size() == 0) { 432402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Log.e(TAG, "Media storage not valid or no uris received."); 433402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_FAILED); 434402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return; 435402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 436402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 437402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (btUris.size() > 1) { 438402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen beamPath = generateMultiplePath(extRoot + "/" + BEAM_DIR + "/"); 439402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!beamPath.isDirectory() && !beamPath.mkdir()) { 440402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Log.e(TAG, "Failed to create multiple path " + beamPath.toString()); 441402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_FAILED); 442402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return; 443402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 444402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 445402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 446402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen for (int i = 0; i < btUris.size(); i++) { 447402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Uri uri = btUris.get(i); 448402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String mimeType = btMimeTypes.get(i); 449402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 450402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen File srcFile = new File(uri.getPath()); 4510de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen 4524fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen File dstFile = generateUniqueDestination(beamPath.getAbsolutePath(), 4530de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen uri.getLastPathSegment()); 454402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!srcFile.renameTo(dstFile)) { 455402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (DBG) Log.d(TAG, "Failed to rename from " + srcFile + " to " + dstFile); 456402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen srcFile.delete(); 457402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return; 458402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 459402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen paths.add(dstFile.getAbsolutePath()); 460402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mimeTypes.put(dstFile.getAbsolutePath(), mimeType); 461402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (DBG) Log.d(TAG, "Did successful rename from " + srcFile + " to " + dstFile); 462402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 463402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 464402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 465402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We can either add files to the media provider, or provide an ACTION_VIEW 466402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // intent to the file directly. We base this decision on the mime type 467402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // of the first file; if it's media the platform can deal with, 468402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // use the media provider, if it's something else, just launch an ACTION_VIEW 469402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // on the file. 470402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String mimeType = mimeTypes.get(paths.get(0)); 471402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (mimeType.startsWith("image/") || mimeType.startsWith("video/") || 472402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mimeType.startsWith("audio/")) { 473402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String[] arrayPaths = new String[paths.size()]; 474402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen MediaScannerConnection.scanFile(mContext, paths.toArray(arrayPaths), null, this); 475402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_W4_MEDIA_SCANNER); 476402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 477402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We're done. 478402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_SUCCESS); 479402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 480402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 481402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 482402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 483402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public boolean handleMessage(Message msg) { 484402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (msg.what == MSG_NEXT_TRANSFER_TIMER) { 485402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We didn't receive a new transfer in time, finalize this one 4863bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (incoming) { 4873bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen processFiles(); 4883bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } else { 4893bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen updateStateAndNotification(STATE_SUCCESS); 4903bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 491402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return true; 4923bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } else if (msg.what == MSG_TRANSFER_TIMEOUT) { 4933bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // No update on this transfer for a while, check 4943bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // to see if it's still running, and fail it if it is. 4953bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (isRunning()) { 4963bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen updateStateAndNotification(STATE_FAILED); 4973bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 498402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 499402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return false; 500402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 501402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 502402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen public synchronized void onScanCompleted(String path, Uri uri) { 503402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (DBG) Log.d(TAG, "Scan completed, path " + path + " uri " + uri); 504402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (uri != null) { 505402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mediaUris.put(path, uri); 506402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 507402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen urisScanned++; 508402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (urisScanned == paths.size()) { 509402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // We're done 510402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen updateStateAndNotification(STATE_SUCCESS); 511402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 512402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 513402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 514402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen boolean checkMediaStorage(File path) { 515402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 516402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!path.isDirectory() && !path.mkdir()) { 517402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Log.e(TAG, "Not dir or not mkdir " + path.getAbsolutePath()); 518402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return false; 519402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 520402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return true; 521402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 522402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Log.e(TAG, "External storage not mounted, can't store file."); 523402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return false; 524402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 525402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 526402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 527402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized Intent buildViewIntent() { 528402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (paths.size() == 0) return null; 529402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 530402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Intent viewIntent = new Intent(Intent.ACTION_VIEW); 531402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 532402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String filePath = paths.get(0); 533402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Uri mediaUri = mediaUris.get(filePath); 534402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Uri uri = mediaUri != null ? mediaUri : 535402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Uri.parse(ContentResolver.SCHEME_FILE + "://" + filePath); 536402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen viewIntent.setDataAndTypeAndNormalize(uri, mimeTypes.get(filePath)); 5379fecbe852683f1bae8dc789c089028acc1ddae20Doris Liu viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 538402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return viewIntent; 539402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 540402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 5413bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen PendingIntent buildCancelIntent() { 5423bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen Intent intent = new Intent(ACTION_CANCEL_HANDOVER_TRANSFER); 5433bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen intent.putExtra(EXTRA_SOURCE_ADDRESS, sourceAddress); 5443bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, intent, 0); 5453bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 5463bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen return pi; 5473bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 5480de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen 5494fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen synchronized File generateUniqueDestination(String path, String fileName) { 5504fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen int dotIndex = fileName.lastIndexOf("."); 5514fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen String extension = null; 5524fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen String fileNameWithoutExtension = null; 5534fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen if (dotIndex < 0) { 5544fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen extension = ""; 5554fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen fileNameWithoutExtension = fileName; 5564fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen } else { 5574fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen extension = fileName.substring(dotIndex); 5584fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen fileNameWithoutExtension = fileName.substring(0, dotIndex); 5594fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen } 5604fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen File dstFile = new File(path + File.separator + fileName); 5610de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen int count = 0; 5620de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen while (dstFile.exists()) { 5634fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen dstFile = new File(path + File.separator + fileNameWithoutExtension + "-" + 5644fe787c0237d00e96018ac2185961451c6d4851dMartijn Coenen Integer.toString(count) + extension); 5650de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen count++; 5660de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen } 5670de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen return dstFile; 5680de2737031fd18aeeadcbc8f46cd0ebcb18ee730Martijn Coenen } 5693bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 570402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized File generateMultiplePath(String beamRoot) { 571402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // Generate a unique directory with the date 572402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String format = "yyyy-MM-dd"; 573402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen SimpleDateFormat sdf = new SimpleDateFormat(format); 574402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String newPath = beamRoot + "beam-" + sdf.format(new Date()); 575402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen File newFile = new File(newPath); 576402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen int count = 0; 577402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen while (newFile.exists()) { 578402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen newPath = beamRoot + "beam-" + sdf.format(new Date()) + "-" + 579402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Integer.toString(count); 580402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen newFile = new File(newPath); 581402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen count++; 582402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 583402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 584402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return newFile; 585402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 586226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 587226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 5883bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen synchronized HandoverTransfer getOrCreateHandoverTransfer(String sourceAddress, boolean incoming, 5893bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen boolean create) { 590402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Pair<String, Boolean> key = new Pair<String, Boolean>(sourceAddress, incoming); 591402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (mTransfers.containsKey(key)) { 592402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen HandoverTransfer transfer = mTransfers.get(key); 593402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (transfer.isRunning()) { 594402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return transfer; 595402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 5963bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (create) mTransfers.remove(key); // new one created below 597226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 598226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 5993bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (create) { 6003bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen HandoverTransfer transfer = new HandoverTransfer(sourceAddress, incoming); 6013bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen mTransfers.put(key, transfer); 6023bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 6033bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen return transfer; 6043bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } else { 6053bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen return null; 6063bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 607226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 608226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 609a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly public HandoverManager(Context context) { 610a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly mContext = context; 611a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 612226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 613226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen mNotificationManager = (NotificationManager) mContext.getSystemService( 614226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen Context.NOTIFICATION_SERVICE); 615226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 616402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mTransfers = new HashMap<Pair<String, Boolean>, HandoverTransfer>(); 617226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen mHandoverPowerManager = new HandoverPowerManager(context); 618226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 619226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen IntentFilter filter = new IntentFilter(ACTION_BT_OPP_TRANSFER_DONE); 620226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen filter.addAction(ACTION_BT_OPP_TRANSFER_PROGRESS); 621d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 6223bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER); 623226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen mContext.registerReceiver(mReceiver, filter, HANDOVER_STATUS_PERMISSION, null); 624a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 625a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 626d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen synchronized void cleanupTransfers() { 627402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Iterator<Map.Entry<Pair<String, Boolean>, HandoverTransfer>> it = mTransfers.entrySet().iterator(); 628d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen while (it.hasNext()) { 629402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen Map.Entry<Pair<String, Boolean>, HandoverTransfer> pair = it.next(); 630d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen HandoverTransfer transfer = pair.getValue(); 631d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (!transfer.isRunning()) { 632d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen it.remove(); 633d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 634d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 635d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 636d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 63743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly static NdefRecord createCollisionRecord() { 63843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly byte[] random = new byte[2]; 63943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly new Random().nextBytes(random); 64063cccc4b5096fd9cbcb08410e69f7932375b4bc6Martijn Coenen return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, RTD_COLLISION_RESOLUTION, null, random); 64143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 64243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 643d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) { 64443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly byte[] payload = new byte[4]; 645d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING : 646d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen CARRIER_POWER_STATE_ACTIVE); // Carrier Power State: Activating or active 64743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload[1] = 1; // length of carrier data reference 64843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record 64943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload[3] = 0; // Auxiliary data reference count 650d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, payload); 65143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 65243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 65343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly NdefRecord createBluetoothOobDataRecord() { 65443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly byte[] payload = new byte[8]; 655f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen // Note: this field should be little-endian per the BTSSP spec 656f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen // The Android 4.1 implementation used big-endian order here. 657f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen // No single Android implementation has ever interpreted this 658f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen // length field when parsing this record though. 659f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen payload[0] = (byte) (payload.length & 0xFF); 660f5a196b643c654c7ea98a5e2935e3bff6683399bMartijn Coenen payload[1] = (byte) ((payload.length >> 8) & 0xFF); 66143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 662d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen synchronized (HandoverManager.this) { 663d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (mLocalBluetoothAddress == null) { 664d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen mLocalBluetoothAddress = mBluetoothAdapter.getAddress(); 665d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 666d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 667d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress); 668d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen System.arraycopy(addressBytes, 0, payload, 2, 6); 669d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 670d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 67143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload); 67243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 67343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 6740015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen public boolean isHandoverSupported() { 6750015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen return (mBluetoothAdapter != null); 6760015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen } 6770015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen 67843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly public NdefMessage createHandoverRequestMessage() { 6790015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen if (mBluetoothAdapter == null) return null; 6800015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen 68143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly return new NdefMessage(createHandoverRequestRecord(), createBluetoothOobDataRecord()); 68243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 68343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 684d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefMessage createHandoverSelectMessage(boolean activating) { 685d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return new NdefMessage(createHandoverSelectRecord(activating), createBluetoothOobDataRecord()); 686d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 687d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 688d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefRecord createHandoverSelectRecord(boolean activating) { 689d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefMessage nestedMessage = new NdefMessage(createBluetoothAlternateCarrierRecord(activating)); 690d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] nestedPayload = nestedMessage.toByteArray(); 691d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 692d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); 69343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload.put((byte)0x12); // connection handover v1.2 694d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payload.put(nestedPayload); 695d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 696d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] payloadBytes = new byte[payload.position()]; 697d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payload.position(0); 698d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payload.get(payloadBytes); 699d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null, 700d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payloadBytes); 701d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 702d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 70343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 704d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefRecord createHandoverRequestRecord() { 70543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(), 706d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen createBluetoothAlternateCarrierRecord(false)); 707d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] nestedPayload = nestedMessage.toByteArray(); 708d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 709d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); 710d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payload.put((byte)0x12); // connection handover v1.2 71143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload.put(nestedMessage.toByteArray()); 71243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 71343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly byte[] payloadBytes = new byte[payload.position()]; 71443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload.position(0); 71543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly payload.get(payloadBytes); 716d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null, 717d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen payloadBytes); 71843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 71943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 72043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly /** 72143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly * Return null if message is not a Handover Request, 72243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly * return the Handover Select response if it is. 72343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly */ 72443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly public NdefMessage tryHandoverRequest(NdefMessage m) { 72543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (m == null) return null; 7260015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen if (mBluetoothAdapter == null) return null; 7270015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen 72843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (DBG) Log.d(TAG, "tryHandoverRequest():" + m.toString()); 72943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 73043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly NdefRecord r = m.getRecords()[0]; 73143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (r.getTnf() != NdefRecord.TNF_WELL_KNOWN) return null; 732d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (!Arrays.equals(r.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) return null; 73343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 734d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // we have a handover request, look for BT OOB record 73543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly BluetoothHandoverData bluetoothData = null; 73643f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly for (NdefRecord oob : m.getRecords()) { 73743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && 73843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly Arrays.equals(oob.getType(), TYPE_BT_OOB)) { 73943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly bluetoothData = parseBtOob(ByteBuffer.wrap(oob.getPayload())); 74043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly break; 74143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 74243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 74343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (bluetoothData == null) return null; 74443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 745d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen boolean bluetoothActivating = false; 746d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 747402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen synchronized(HandoverManager.this) { 748402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!mHandoverPowerManager.isBluetoothEnabled()) { 749402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (!mHandoverPowerManager.enableBluetooth()) { 750402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen return null; 751402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } 752402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen bluetoothActivating = true; 753402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen } else { 754402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen mHandoverPowerManager.resetTimer(); 755d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 756402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 757402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen // Create the initial transfer object 7583bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen HandoverTransfer transfer = getOrCreateHandoverTransfer( 7593bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen bluetoothData.device.getAddress(), true, true); 760402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen transfer.updateNotification(); 761226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 762226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 76343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly // BT OOB found, whitelist it for incoming OPP data 76443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly whitelistOppDevice(bluetoothData.device); 76543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 76643f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly // return BT OOB record so they can perform handover 767d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return (createHandoverSelectMessage(bluetoothActivating)); 76843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 76943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 77043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly void whitelistOppDevice(BluetoothDevice device) { 77143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); 772226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen Intent intent = new Intent(ACTION_WHITELIST_DEVICE); 77343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); 77443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly mContext.sendBroadcast(intent); 77543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 77643f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 777a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly public boolean tryHandover(NdefMessage m) { 778a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (m == null) return false; 7790015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen if (mBluetoothAdapter == null) return false; 7800015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen 781a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); 782a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 783a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData handover = parse(m); 784a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (handover == null) return false; 785a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (!handover.valid) return true; 786a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 787a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly synchronized (HandoverManager.this) { 788a112f9c00e3337ef38ea8e1715a99db4966c7219Martijn Coenen if (mBluetoothAdapter == null) { 789a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (DBG) Log.d(TAG, "BT handover, but BT not available"); 790a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return true; 791a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 792a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (mBluetoothHeadsetHandover != null) { 793a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (DBG) Log.d(TAG, "BT handover already in progress, ignoring"); 794a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return true; 795a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 796a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly mBluetoothHeadsetHandover = new BluetoothHeadsetHandover(mContext, handover.device, 797a112f9c00e3337ef38ea8e1715a99db4966c7219Martijn Coenen handover.name, mHandoverPowerManager, this); 798a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly mBluetoothHeadsetHandover.start(); 799a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 800a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return true; 801a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 802a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 80383558889207858d7ce8500f9fce8a707a86b495eMartijn Coenen // This starts sending an Uri over BT 80483558889207858d7ce8500f9fce8a707a86b495eMartijn Coenen public void doHandoverUri(Uri[] uris, NdefMessage m) { 8050015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen if (mBluetoothAdapter == null) return; 8060015304ebb1fda453f8b2ef660c39bf4f11dc771Martijn Coenen 80743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly BluetoothHandoverData data = parse(m); 808d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (data != null && data.valid) { 8093bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // Register a new handover transfer object 8103bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen getOrCreateHandoverTransfer(data.device.getAddress(), false, true); 811d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen BluetoothOppHandover handover = new BluetoothOppHandover(mContext, data.device, 812d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen uris, mHandoverPowerManager, data.carrierActivating); 813d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen handover.start(); 814d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 815d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 816d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 817d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) { 818d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] payload = handoverRec.getPayload(); 819d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (payload == null || payload.length <= 1) return false; 820d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // Skip version 821d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] payloadNdef = new byte[payload.length - 1]; 822d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1); 823d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen NdefMessage msg; 824d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen try { 825d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen msg = new NdefMessage(payloadNdef); 826d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } catch (FormatException e) { 827d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return false; 828d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 829d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 830d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen for (NdefRecord alt : msg.getRecords()) { 831d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] acPayload = alt.getPayload(); 832d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (acPayload != null) { 833d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen ByteBuffer buf = ByteBuffer.wrap(acPayload); 834d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits 835d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int carrierRefLength = buf.get() & 0xFF; 836d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (carrierRefLength != carrierId.length) return false; 837d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 838d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen byte[] carrierRefId = new byte[carrierRefLength]; 839d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen buf.get(carrierRefId); 840d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (Arrays.equals(carrierRefId, carrierId)) { 841d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // Found match, returning whether power state is activating 842d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return (cps == CARRIER_POWER_STATE_ACTIVATING); 843d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 844d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 845d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 846d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 847d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return true; 848d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 849d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 850d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen BluetoothHandoverData parseHandoverSelect(NdefMessage m) { 851d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // TODO we could parse this a lot more strictly; right now 852d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // we just search for a BT OOB record, and try to cross-reference 853d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen // the carrier state inside the 'hs' payload. 854d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen for (NdefRecord oob : m.getRecords()) { 855d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && 856d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen Arrays.equals(oob.getType(), TYPE_BT_OOB)) { 857d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload())); 858d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) { 859d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen data.carrierActivating = true; 860d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 861d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return data; 862d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 863d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 864d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 865d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return null; 86643f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 86743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 868a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData parse(NdefMessage m) { 869a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly NdefRecord r = m.getRecords()[0]; 870a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly short tnf = r.getTnf(); 871a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte[] type = r.getType(); 872a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 873a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly // Check for BT OOB record 874a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) { 875a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return parseBtOob(ByteBuffer.wrap(r.getPayload())); 876a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 877a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 878a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly // Check for Handover Select, followed by a BT OOB record 879a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (tnf == NdefRecord.TNF_WELL_KNOWN && 880a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) { 881d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return parseHandoverSelect(m); 882a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 883d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 884a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly // Check for Nokia BT record, found on some Nokia BH-505 Headsets 885a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) { 886a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return parseNokia(ByteBuffer.wrap(r.getPayload())); 887a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 888a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 889a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return null; 890a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 891a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 892a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData parseNokia(ByteBuffer payload) { 893a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData result = new BluetoothHandoverData(); 894a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.valid = false; 895a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 896a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly try { 897a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.position(1); 898a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte[] address = new byte[6]; 899a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.get(address); 900a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.device = mBluetoothAdapter.getRemoteDevice(address); 901a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.valid = true; 902a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.position(14); 903a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly int nameLength = payload.get(); 904a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte[] nameBytes = new byte[nameLength]; 905a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.get(nameBytes); 906a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.name = new String(nameBytes, Charset.forName("UTF-8")); 907a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } catch (IllegalArgumentException e) { 908a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly Log.i(TAG, "nokia: invalid BT address"); 909a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } catch (BufferUnderflowException e) { 910a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly Log.i(TAG, "nokia: payload shorter than expected"); 911a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 912a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (result.valid && result.name == null) result.name = ""; 913a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return result; 914a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 915a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 916a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData parseBtOob(ByteBuffer payload) { 917a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly BluetoothHandoverData result = new BluetoothHandoverData(); 918a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.valid = false; 919a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 920a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly try { 921a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.position(2); 922a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte[] address = new byte[6]; 923a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.get(address); 924a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for 925a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly // ByteBuffer.get(byte[]), so manually swap order 926a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly for (int i = 0; i < 3; i++) { 927a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte temp = address[i]; 928a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly address[i] = address[5 - i]; 929a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly address[5 - i] = temp; 930a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 931a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.device = mBluetoothAdapter.getRemoteDevice(address); 932a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.valid = true; 933a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 934a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly while (payload.remaining() > 0) { 935a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly byte[] nameBytes; 936a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly int len = payload.get(); 937a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly int type = payload.get(); 938a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly switch (type) { 939a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly case 0x08: // short local name 940a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly nameBytes = new byte[len - 1]; 941a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.get(nameBytes); 942a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.name = new String(nameBytes, Charset.forName("UTF-8")); 943a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly break; 944a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly case 0x09: // long local name 945a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (result.name != null) break; // prefer short name 946a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly nameBytes = new byte[len - 1]; 947a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.get(nameBytes); 948a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly result.name = new String(nameBytes, Charset.forName("UTF-8")); 949a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly break; 950a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly default: 951a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly payload.position(payload.position() + len - 1); 952a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly break; 953a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 954a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 955a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } catch (IllegalArgumentException e) { 956a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly Log.i(TAG, "BT OOB: invalid BT address"); 957a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } catch (BufferUnderflowException e) { 958a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly Log.i(TAG, "BT OOB: payload shorter than expected"); 959a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 960a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly if (result.valid && result.name == null) result.name = ""; 961a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly return result; 962a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 963a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly 96443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly static byte[] addressToReverseBytes(String address) { 96543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly String[] split = address.split(":"); 96643f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly byte[] result = new byte[split.length]; 96743f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 96843f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly for (int i = 0; i < split.length; i++) { 96943f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly // need to parse as int because parseByte() expects a signed byte 97043f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly result[split.length - 1 - i] = (byte)Integer.parseInt(split[i], 16); 97143f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 97243f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 97343f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly return result; 97443f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly } 97543f2fa7ad4c72ef4849f2d2b78a963c1925c63a3Nick Pelly 976a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly @Override 977226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen public void onBluetoothHeadsetHandoverComplete(boolean connected) { 978a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly synchronized (HandoverManager.this) { 979a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly mBluetoothHeadsetHandover = null; 980226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen mBluetoothHeadsetConnected = connected; 981a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 982a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly } 983226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 984226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen final BroadcastReceiver mReceiver = new BroadcastReceiver() { 985226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen @Override 986226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen public void onReceive(Context context, Intent intent) { 987226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen String action = intent.getAction(); 988d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 989d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 990d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); 991d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (state == BluetoothAdapter.STATE_OFF) { 992d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen mHandoverPowerManager.stopMonitoring(); 993d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 994d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 995d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen return; 9963bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } else if (action.equals(ACTION_CANCEL_HANDOVER_TRANSFER)) { 9973bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen String sourceAddress = intent.getStringExtra(EXTRA_SOURCE_ADDRESS); 9983bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen HandoverTransfer transfer = getOrCreateHandoverTransfer(sourceAddress, true, 9993bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen false); 10003bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (transfer != null) { 10013bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen transfer.cancel(); 10023bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 1003d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS) || 1004d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen action.equals(ACTION_BT_OPP_TRANSFER_DONE)) { 10053bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // Clean up old transfers no longer in progress 1006d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen cleanupTransfers(); 1007d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 1008d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int direction = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_DIRECTION, -1); 1009d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int id = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_ID, -1); 1010402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String sourceAddress = intent.getStringExtra(EXTRA_BT_OPP_ADDRESS); 1011402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen 1012402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen if (direction == -1 || id == -1 || sourceAddress == null) return; 1013402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen boolean incoming = (direction == DIRECTION_BLUETOOTH_INCOMING); 10143bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen 10153bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen HandoverTransfer transfer = getOrCreateHandoverTransfer(sourceAddress, incoming, 10163bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen false); 10173bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen if (transfer == null) { 10183bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // There is no transfer running for this source address; most likely 10193bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // the transfer was cancelled. We need to tell BT OPP to stop transferring 10203bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen // in case this was an incoming transfer 10213bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen Intent cancelIntent = new Intent("android.btopp.intent.action.STOP_HANDOVER_TRANSFER"); 10223bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen cancelIntent.putExtra(EXTRA_BT_OPP_TRANSFER_ID, id); 10233bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen mContext.sendBroadcast(cancelIntent); 10243bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen return; 10253bf04bd4f04b217768b3799dadb4a775b5a31f1aMartijn Coenen } 1026d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 1027d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (action.equals(ACTION_BT_OPP_TRANSFER_DONE)) { 1028d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen int handoverStatus = intent.getIntExtra(EXTRA_BT_OPP_TRANSFER_STATUS, 1029d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen HANDOVER_TRANSFER_STATUS_FAILURE); 1030d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen 1031d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) { 1032d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen String uriString = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_URI); 1033402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen String mimeType = intent.getStringExtra(EXTRA_BT_OPP_TRANSFER_MIMETYPE); 1034d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen Uri uri = Uri.parse(uriString); 1035d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen if (uri.getScheme() == null) { 1036d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen uri = Uri.fromFile(new File(uri.getPath())); 1037d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } 1038402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen transfer.finishTransfer(true, uri, mimeType); 1039d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } else { 1040402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen transfer.finishTransfer(false, null, null); 1041226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 1042d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen } else if (action.equals(ACTION_BT_OPP_TRANSFER_PROGRESS)) { 1043d82d9db81f30ccecd29a0531e6db9b49c9c2cd95Martijn Coenen float progress = intent.getFloatExtra(EXTRA_BT_OPP_TRANSFER_PROGRESS, 0.0f); 1044402da07068333f9393fd960a4c90df39b34bf668Martijn Coenen transfer.updateFileProgress(progress); 1045226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 1046226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 1047226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen } 1048226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen }; 1049226307d8c2952c42855005d7c0107b42b066bc9aMartijn Coenen 1050a2908a164eec02c34efc39db2e3ee0e38ebbfdb1Nick Pelly} 1051