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