1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.nfc.handover;
18
19import android.app.Service;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothClass;
22import android.bluetooth.BluetoothDevice;
23import android.bluetooth.OobData;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.nfc.NfcAdapter;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.IBinder;
32import android.os.Message;
33import android.os.Messenger;
34import android.os.Parcelable;
35import android.os.ParcelUuid;
36import android.os.RemoteException;
37import android.util.Log;
38
39public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback {
40    static final String TAG = "PeripheralHandoverService";
41    static final boolean DBG = true;
42
43    static final int MSG_PAUSE_POLLING = 0;
44
45    public static final String BUNDLE_TRANSFER = "transfer";
46    public static final String EXTRA_PERIPHERAL_DEVICE = "device";
47    public static final String EXTRA_PERIPHERAL_NAME = "headsetname";
48    public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype";
49    public static final String EXTRA_PERIPHERAL_OOB_DATA = "oobdata";
50    public static final String EXTRA_PERIPHERAL_UUIDS = "uuids";
51    public static final String EXTRA_PERIPHERAL_CLASS = "class";
52
53    // Amount of time to pause polling when connecting to peripherals
54    private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
55    private static final int PAUSE_DELAY_MILLIS = 300;
56
57    private static final Object sLock = new Object();
58
59    // Variables below only accessed on main thread
60    final Messenger mMessenger;
61
62    int mStartId;
63
64    BluetoothAdapter mBluetoothAdapter;
65    NfcAdapter mNfcAdapter;
66    Handler mHandler;
67    BluetoothPeripheralHandover mBluetoothPeripheralHandover;
68    boolean mBluetoothHeadsetConnected;
69    boolean mBluetoothEnabledByNfc;
70
71    class MessageHandler extends Handler {
72        @Override
73        public void handleMessage(Message msg) {
74            switch (msg.what) {
75                case MSG_PAUSE_POLLING:
76                    mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
77                    break;
78            }
79        }
80    }
81
82    final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
83        @Override
84        public void onReceive(Context context, Intent intent) {
85            String action = intent.getAction();
86            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
87                handleBluetoothStateChanged(intent);
88            }
89        }
90    };
91
92    public PeripheralHandoverService() {
93        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
94        mHandler = new MessageHandler();
95        mMessenger = new Messenger(mHandler);
96        mBluetoothHeadsetConnected = false;
97        mBluetoothEnabledByNfc = false;
98        mStartId = 0;
99    }
100
101    @Override
102    public int onStartCommand(Intent intent, int flags, int startId) {
103
104        synchronized (sLock) {
105            if (mStartId != 0) {
106                mStartId = startId;
107                // already running
108                return START_STICKY;
109            }
110            mStartId = startId;
111        }
112
113        if (intent == null) {
114            if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
115            stopSelf(startId);
116            return START_NOT_STICKY;
117        }
118
119        if (doPeripheralHandover(intent.getExtras())) {
120            return START_STICKY;
121        } else {
122            stopSelf(startId);
123            return START_NOT_STICKY;
124        }
125    }
126
127    @Override
128    public void onCreate() {
129        super.onCreate();
130        mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
131
132        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
133        registerReceiver(mBluetoothStatusReceiver, filter);
134    }
135
136    @Override
137    public void onDestroy() {
138        super.onDestroy();
139        unregisterReceiver(mBluetoothStatusReceiver);
140    }
141
142    boolean doPeripheralHandover(Bundle msgData) {
143        if (mBluetoothPeripheralHandover != null) {
144            Log.d(TAG, "Ignoring pairing request, existing handover in progress.");
145            return true;
146        }
147
148        if (msgData == null) {
149            return false;
150        }
151
152        BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE);
153        String name = msgData.getString(EXTRA_PERIPHERAL_NAME);
154        int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT);
155        OobData oobData = msgData.getParcelable(EXTRA_PERIPHERAL_OOB_DATA);
156        Parcelable[] parcelables = msgData.getParcelableArray(EXTRA_PERIPHERAL_UUIDS);
157        BluetoothClass btClass = msgData.getParcelable(EXTRA_PERIPHERAL_CLASS);
158
159        ParcelUuid[] uuids = null;
160        if (parcelables != null) {
161            uuids = new ParcelUuid[parcelables.length];
162            for (int i = 0; i < parcelables.length; i++) {
163                uuids[i] = (ParcelUuid)parcelables[i];
164            }
165        }
166
167        mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
168                this, device, name, transport, oobData, uuids, btClass, this);
169
170        if (transport == BluetoothDevice.TRANSPORT_LE) {
171            mHandler.sendMessageDelayed(
172                    mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
173        }
174        if (mBluetoothAdapter.isEnabled()) {
175            if (!mBluetoothPeripheralHandover.start()) {
176                mHandler.removeMessages(MSG_PAUSE_POLLING);
177                mNfcAdapter.resumePolling();
178            }
179        } else {
180            // Once BT is enabled, the headset pairing will be started
181            if (!enableBluetooth()) {
182                Log.e(TAG, "Error enabling Bluetooth.");
183                mBluetoothPeripheralHandover = null;
184                return false;
185            }
186        }
187
188        return true;
189    }
190
191    private void handleBluetoothStateChanged(Intent intent) {
192        int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
193                BluetoothAdapter.ERROR);
194        if (state == BluetoothAdapter.STATE_ON) {
195            // If there is a pending device pairing, start it
196            if (mBluetoothPeripheralHandover != null &&
197                    !mBluetoothPeripheralHandover.hasStarted()) {
198                if (!mBluetoothPeripheralHandover.start()) {
199                    mNfcAdapter.resumePolling();
200                }
201            }
202        } else if (state == BluetoothAdapter.STATE_OFF) {
203            mBluetoothEnabledByNfc = false;
204            mBluetoothHeadsetConnected = false;
205        }
206    }
207
208    @Override
209    public void onBluetoothPeripheralHandoverComplete(boolean connected) {
210        // Called on the main thread
211        int transport = mBluetoothPeripheralHandover.mTransport;
212        mBluetoothPeripheralHandover = null;
213        mBluetoothHeadsetConnected = connected;
214
215        // <hack> resume polling immediately if the connection failed,
216        // otherwise just wait for polling to come back up after the timeout
217        // This ensures we don't disconnect if the user left the volantis
218        // on the tag after pairing completed, which results in automatic
219        // disconnection </hack>
220        if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
221            if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
222                mHandler.removeMessages(MSG_PAUSE_POLLING);
223            }
224
225            // do this unconditionally as the polling could have been paused as we were removing
226            // the message in the handler. It's a no-op if polling is already enabled.
227            mNfcAdapter.resumePolling();
228        }
229        disableBluetoothIfNeeded();
230
231        synchronized (sLock) {
232            stopSelf(mStartId);
233            mStartId = 0;
234        }
235    }
236
237
238    boolean enableBluetooth() {
239        if (!mBluetoothAdapter.isEnabled()) {
240            mBluetoothEnabledByNfc = true;
241            return mBluetoothAdapter.enableNoAutoConnect();
242        }
243        return true;
244    }
245
246    void disableBluetoothIfNeeded() {
247        if (!mBluetoothEnabledByNfc) return;
248
249        if (!mBluetoothHeadsetConnected) {
250            mBluetoothAdapter.disable();
251            mBluetoothEnabledByNfc = false;
252        }
253    }
254
255    @Override
256    public IBinder onBind(Intent intent) {
257        return null;
258    }
259
260    @Override
261    public boolean onUnbind(Intent intent) {
262        // prevent any future callbacks to the client, no rebind call needed.
263        return false;
264    }
265}
266