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.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.media.AudioManager;
     27 import android.media.SoundPool;
     28 import android.nfc.NfcAdapter;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.IBinder;
     32 import android.os.Message;
     33 import android.os.Messenger;
     34 import android.os.RemoteException;
     35 import android.util.Log;
     36 
     37 import com.android.nfc.R;
     38 
     39 public 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 
     50     // Amount of time to pause polling when connecting to peripherals
     51     private static final int PAUSE_POLLING_TIMEOUT_MS = 35000;
     52     private static final int PAUSE_DELAY_MILLIS = 300;
     53 
     54     // Variables below only accessed on main thread
     55     final Messenger mMessenger;
     56 
     57     SoundPool mSoundPool;
     58     int mSuccessSound;
     59     int mStartId;
     60 
     61     BluetoothAdapter mBluetoothAdapter;
     62     NfcAdapter mNfcAdapter;
     63     Handler mHandler;
     64     BluetoothPeripheralHandover mBluetoothPeripheralHandover;
     65     boolean mBluetoothHeadsetConnected;
     66     boolean mBluetoothEnabledByNfc;
     67 
     68     class MessageHandler extends Handler {
     69         @Override
     70         public void handleMessage(Message msg) {
     71             switch (msg.what) {
     72                 case MSG_PAUSE_POLLING:
     73                     mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS);
     74                     break;
     75             }
     76         }
     77     }
     78 
     79     final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() {
     80         @Override
     81         public void onReceive(Context context, Intent intent) {
     82             String action = intent.getAction();
     83             if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
     84                 handleBluetoothStateChanged(intent);
     85             }
     86         }
     87     };
     88 
     89     public PeripheralHandoverService() {
     90         mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     91         mHandler = new MessageHandler();
     92         mMessenger = new Messenger(mHandler);
     93         mBluetoothHeadsetConnected = false;
     94         mBluetoothEnabledByNfc = false;
     95         mStartId = 0;
     96     }
     97 
     98     @Override
     99     public int onStartCommand(Intent intent, int flags, int startId) {
    100         if (mStartId != 0) {
    101             // already running
    102             return START_STICKY;
    103         }
    104 
    105         mStartId = startId;
    106 
    107         if (intent == null) {
    108             if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover.");
    109             stopSelf(startId);
    110             return START_NOT_STICKY;
    111         }
    112 
    113         if (doPeripheralHandover(intent.getExtras())) {
    114             return START_STICKY;
    115         } else {
    116             stopSelf(startId);
    117             return START_NOT_STICKY;
    118         }
    119     }
    120 
    121     @Override
    122     public void onCreate() {
    123         super.onCreate();
    124 
    125         mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
    126         mSuccessSound = mSoundPool.load(this, R.raw.end, 1);
    127         mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext());
    128 
    129         IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
    130         registerReceiver(mBluetoothStatusReceiver, filter);
    131     }
    132 
    133     @Override
    134     public void onDestroy() {
    135         super.onDestroy();
    136         if (mSoundPool != null) {
    137             mSoundPool.release();
    138         }
    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 
    156         mBluetoothPeripheralHandover = new BluetoothPeripheralHandover(
    157                 this, device, name, transport, this);
    158 
    159         if (transport == BluetoothDevice.TRANSPORT_LE) {
    160             mHandler.sendMessageDelayed(
    161                     mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS);
    162         }
    163         if (mBluetoothAdapter.isEnabled()) {
    164             if (!mBluetoothPeripheralHandover.start()) {
    165                 mHandler.removeMessages(MSG_PAUSE_POLLING);
    166                 mNfcAdapter.resumePolling();
    167             }
    168         } else {
    169             // Once BT is enabled, the headset pairing will be started
    170             if (!enableBluetooth()) {
    171                 Log.e(TAG, "Error enabling Bluetooth.");
    172                 mBluetoothPeripheralHandover = null;
    173                 return false;
    174             }
    175         }
    176 
    177         return true;
    178     }
    179 
    180     private void handleBluetoothStateChanged(Intent intent) {
    181         int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
    182                 BluetoothAdapter.ERROR);
    183         if (state == BluetoothAdapter.STATE_ON) {
    184             // If there is a pending device pairing, start it
    185             if (mBluetoothPeripheralHandover != null &&
    186                     !mBluetoothPeripheralHandover.hasStarted()) {
    187                 if (!mBluetoothPeripheralHandover.start()) {
    188                     mNfcAdapter.resumePolling();
    189                 }
    190             }
    191         } else if (state == BluetoothAdapter.STATE_OFF) {
    192             mBluetoothEnabledByNfc = false;
    193             mBluetoothHeadsetConnected = false;
    194         }
    195     }
    196 
    197     @Override
    198     public void onBluetoothPeripheralHandoverComplete(boolean connected) {
    199         // Called on the main thread
    200         int transport = mBluetoothPeripheralHandover.mTransport;
    201         mBluetoothPeripheralHandover = null;
    202         mBluetoothHeadsetConnected = connected;
    203 
    204         // <hack> resume polling immediately if the connection failed,
    205         // otherwise just wait for polling to come back up after the timeout
    206         // This ensures we don't disconnect if the user left the volantis
    207         // on the tag after pairing completed, which results in automatic
    208         // disconnection </hack>
    209         if (transport == BluetoothDevice.TRANSPORT_LE && !connected) {
    210             if (mHandler.hasMessages(MSG_PAUSE_POLLING)) {
    211                 mHandler.removeMessages(MSG_PAUSE_POLLING);
    212             }
    213 
    214             // do this unconditionally as the polling could have been paused as we were removing
    215             // the message in the handler. It's a no-op if polling is already enabled.
    216             mNfcAdapter.resumePolling();
    217         }
    218         disableBluetoothIfNeeded();
    219         stopSelf(mStartId);
    220     }
    221 
    222 
    223     boolean enableBluetooth() {
    224         if (!mBluetoothAdapter.isEnabled()) {
    225             mBluetoothEnabledByNfc = true;
    226             return mBluetoothAdapter.enableNoAutoConnect();
    227         }
    228         return true;
    229     }
    230 
    231     void disableBluetoothIfNeeded() {
    232         if (!mBluetoothEnabledByNfc) return;
    233 
    234         if (!mBluetoothHeadsetConnected) {
    235             mBluetoothAdapter.disable();
    236             mBluetoothEnabledByNfc = false;
    237         }
    238     }
    239 
    240     @Override
    241     public IBinder onBind(Intent intent) {
    242         return null;
    243     }
    244 
    245     @Override
    246     public boolean onUnbind(Intent intent) {
    247         // prevent any future callbacks to the client, no rebind call needed.
    248         return false;
    249     }
    250 }
    251