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