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