Home | History | Annotate | Download | only in accessories
      1 /*
      2  * Copyright (C) 2014 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.tv.settings.accessories;
     18 
     19 import android.app.Activity;
     20 import android.app.FragmentManager;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.content.ComponentName;
     23 import android.content.Intent;
     24 import android.content.ServiceConnection;
     25 import android.content.pm.ResolveInfo;
     26 import android.hardware.input.InputManager;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.IBinder;
     30 import android.os.Message;
     31 import android.os.Messenger;
     32 import android.os.RemoteException;
     33 import android.os.SystemClock;
     34 import android.support.annotation.NonNull;
     35 import android.transition.TransitionManager;
     36 import android.util.Log;
     37 import android.view.KeyEvent;
     38 import android.view.View;
     39 import android.view.ViewGroup;
     40 import android.view.WindowManager;
     41 
     42 import com.android.tv.settings.R;
     43 
     44 import java.lang.ref.WeakReference;
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 
     48 /**
     49  * Activity for detecting and adding (pairing) new bluetooth devices.
     50  */
     51 public class AddAccessoryActivity extends Activity implements BluetoothDevicePairer.EventListener {
     52 
     53     private static final boolean DEBUG = false;
     54     private static final String TAG = "AddAccessoryActivity";
     55 
     56     private static final String ACTION_CONNECT_INPUT =
     57             "com.google.android.intent.action.CONNECT_INPUT";
     58 
     59     private static final String INTENT_EXTRA_NO_INPUT_MODE = "no_input_mode";
     60 
     61     private static final String SAVED_STATE_PREFERENCE_FRAGMENT =
     62             "AddAccessoryActivity.PREFERENCE_FRAGMENT";
     63     private static final String SAVED_STATE_CONTENT_FRAGMENT =
     64             "AddAccessoryActivity.CONTENT_FRAGMENT";
     65     private static final String SAVED_STATE_BLUETOOTH_DEVICES =
     66             "AddAccessoryActivity.BLUETOOTH_DEVICES";
     67 
     68     private static final String ADDRESS_NONE = "NONE";
     69 
     70     private static final int AUTOPAIR_COUNT = 10;
     71 
     72     private static final int MSG_UPDATE_VIEW = 1;
     73     private static final int MSG_REMOVE_CANCELED = 2;
     74     private static final int MSG_PAIRING_COMPLETE = 3;
     75     private static final int MSG_OP_TIMEOUT = 4;
     76     private static final int MSG_RESTART = 5;
     77     private static final int MSG_TRIGGER_SELECT_DOWN = 6;
     78     private static final int MSG_TRIGGER_SELECT_UP = 7;
     79     private static final int MSG_AUTOPAIR_TICK = 8;
     80     private static final int MSG_START_AUTOPAIR_COUNTDOWN = 9;
     81 
     82     private static final int CANCEL_MESSAGE_TIMEOUT = 3000;
     83     private static final int DONE_MESSAGE_TIMEOUT = 3000;
     84     private static final int PAIR_OPERATION_TIMEOUT = 120000;
     85     private static final int CONNECT_OPERATION_TIMEOUT = 15000;
     86     private static final int RESTART_DELAY = 3000;
     87     private static final int LONG_PRESS_DURATION = 3000;
     88     private static final int KEY_DOWN_TIME = 150;
     89     private static final int TIME_TO_START_AUTOPAIR_COUNT = 5000;
     90     private static final int EXIT_TIMEOUT_MILLIS = 90 * 1000;
     91 
     92     private AddAccessoryPreferenceFragment mPreferenceFragment;
     93     private AddAccessoryContentFragment mContentFragment;
     94 
     95     // members related to Bluetooth pairing
     96     private BluetoothDevicePairer mBluetoothPairer;
     97     private int mPreviousStatus = BluetoothDevicePairer.STATUS_NONE;
     98     private boolean mPairingSuccess = false;
     99     private boolean mPairingBluetooth = false;
    100     private List<BluetoothDevice> mBluetoothDevices;
    101     private String mCancelledAddress = ADDRESS_NONE;
    102     private String mCurrentTargetAddress = ADDRESS_NONE;
    103     private String mCurrentTargetStatus = "";
    104     private boolean mPairingInBackground = false;
    105 
    106     private boolean mDone = false;
    107 
    108     private boolean mHwKeyDown;
    109     private boolean mHwKeyDidSelect;
    110     private boolean mNoInputMode;
    111 
    112     private static final String GAMEPAD_PAIRING_SERVICE =
    113             "com.google.android.settings.accessories.GamepadPairingService";
    114     private static final String GAMEPAD_PAIRING_PACKAGE =
    115             "com.google.android.tv.remotepairing";
    116     /* Keep in sync with GamepadPairingService */
    117     private static final int GAMEPAD_MESSAGE_START_PAIRING = 1;
    118     private static final int GAMEPAD_MESSAGE_STOP_PAIRING = 2;
    119     private static final int GAMEPAD_MESSAGE_PAUSE_PAIRING = 3;
    120     private static final int GAMEPAD_MESSAGE_RESUME_PAIRING = 4;
    121     private final List<Message> mGamepadMessageQueue = new ArrayList<>();
    122     private Messenger mGamepadService;
    123     private ServiceConnection mGamepadServiceConn;
    124 
    125     private class GamepadServiceConnection implements ServiceConnection {
    126         @Override
    127         public void onServiceConnected(ComponentName name, IBinder service) {
    128             Log.d(TAG, "Gamepad Service Connected");
    129             mGamepadService = new Messenger(service);
    130             for (Message message : mGamepadMessageQueue) {
    131                 try {
    132                     mGamepadService.send(message);
    133                 } catch (RemoteException e) {
    134                     Log.e(TAG, "Remote exception sending message " + message + " " + e);
    135                 }
    136             }
    137             mGamepadMessageQueue.clear();
    138         }
    139 
    140         @Override
    141         public void onServiceDisconnected(ComponentName name) {
    142             Log.d(TAG, "Gamepad Service Disconnected");
    143             mGamepadService = null;
    144         }
    145     };
    146 
    147     // Internal message handler
    148     private final MessageHandler mMsgHandler = new MessageHandler();
    149 
    150     private static class MessageHandler extends Handler {
    151 
    152         private WeakReference<AddAccessoryActivity> mActivityRef = new WeakReference<>(null);
    153 
    154         public void setActivity(AddAccessoryActivity activity) {
    155             mActivityRef = new WeakReference<>(activity);
    156         }
    157 
    158         @Override
    159         public void handleMessage(Message msg) {
    160             final AddAccessoryActivity activity = mActivityRef.get();
    161             if (activity == null) {
    162                 return;
    163             }
    164             switch (msg.what) {
    165                 case MSG_UPDATE_VIEW:
    166                     activity.updateView();
    167                     break;
    168                 case MSG_REMOVE_CANCELED:
    169                     activity.mCancelledAddress = ADDRESS_NONE;
    170                     activity.updateView();
    171                     break;
    172                 case MSG_PAIRING_COMPLETE:
    173                     activity.finish();
    174                     break;
    175                 case MSG_OP_TIMEOUT:
    176                     activity.handlePairingTimeout();
    177                     break;
    178                 case MSG_RESTART:
    179                     if (activity.mBluetoothPairer != null) {
    180                         activity.mBluetoothPairer.start();
    181                         activity.mBluetoothPairer.cancelPairing();
    182                     }
    183                     break;
    184                 case MSG_TRIGGER_SELECT_DOWN:
    185                     activity.sendKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, true);
    186                     activity.mHwKeyDidSelect = true;
    187                     sendEmptyMessageDelayed(MSG_TRIGGER_SELECT_UP, KEY_DOWN_TIME);
    188                     activity.cancelPairingCountdown();
    189                     break;
    190                 case MSG_TRIGGER_SELECT_UP:
    191                     activity.sendKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, false);
    192                     break;
    193                 case MSG_START_AUTOPAIR_COUNTDOWN:
    194                     activity.setPairingText(
    195                             activity.getString(R.string.accessories_autopair_msg, AUTOPAIR_COUNT));
    196                     sendMessageDelayed(obtainMessage(MSG_AUTOPAIR_TICK,
    197                             AUTOPAIR_COUNT, 0, null), 1000);
    198                     break;
    199                 case MSG_AUTOPAIR_TICK:
    200                     int countToAutoPair = msg.arg1 - 1;
    201                     if (countToAutoPair <= 0) {
    202                         activity.setPairingText(null);
    203                         // AutoPair
    204                         activity.startAutoPairing();
    205                     } else {
    206                         activity.setPairingText(
    207                                 activity.getString(R.string.accessories_autopair_msg,
    208                                         countToAutoPair));
    209                         sendMessageDelayed(obtainMessage(MSG_AUTOPAIR_TICK,
    210                                 countToAutoPair, 0, null), 1000);
    211                     }
    212                     break;
    213                 default:
    214                     super.handleMessage(msg);
    215             }
    216         }
    217     }
    218 
    219     private final Handler mAutoExitHandler = new Handler();
    220 
    221     private final Runnable mAutoExitRunnable = new Runnable() {
    222         @Override
    223         public void run() {
    224             finish();
    225         }
    226     };
    227 
    228     @Override
    229     public void onCreate(Bundle savedInstanceState) {
    230         super.onCreate(savedInstanceState);
    231 
    232         setContentView(R.layout.lb_dialog_fragment);
    233 
    234         Intent intent = new Intent();
    235         intent.setAction(GAMEPAD_PAIRING_SERVICE);
    236         intent.setPackage(GAMEPAD_PAIRING_PACKAGE);
    237         if (serviceIntentIsHandled(intent)) {
    238             // Don't auto-start the service. If it's not running we don't need to pause it.
    239             mGamepadServiceConn = new GamepadServiceConnection();
    240             bindService(intent, mGamepadServiceConn, BIND_ADJUST_WITH_ACTIVITY);
    241         }
    242 
    243         pauseGamepadPairingService();
    244 
    245         mMsgHandler.setActivity(this);
    246 
    247         getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
    248                 WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
    249 
    250         mNoInputMode = getIntent().getBooleanExtra(INTENT_EXTRA_NO_INPUT_MODE, false);
    251         mHwKeyDown = false;
    252 
    253         if (savedInstanceState == null) {
    254             mBluetoothDevices = new ArrayList<>();
    255         } else {
    256             mBluetoothDevices =
    257                     savedInstanceState.getParcelableArrayList(SAVED_STATE_BLUETOOTH_DEVICES);
    258         }
    259 
    260         final FragmentManager fm = getFragmentManager();
    261         if (savedInstanceState == null) {
    262             mPreferenceFragment = AddAccessoryPreferenceFragment.newInstance();
    263             mContentFragment = AddAccessoryContentFragment.newInstance();
    264             fm.beginTransaction()
    265                     .add(R.id.action_fragment, mPreferenceFragment)
    266                     .add(R.id.content_fragment, mContentFragment)
    267                     .commit();
    268         } else {
    269             mPreferenceFragment = (AddAccessoryPreferenceFragment)
    270                     fm.getFragment(savedInstanceState,
    271                             SAVED_STATE_PREFERENCE_FRAGMENT);
    272             mContentFragment = (AddAccessoryContentFragment)
    273                     fm.getFragment(savedInstanceState,
    274                             SAVED_STATE_CONTENT_FRAGMENT);
    275         }
    276 
    277         rearrangeViews();
    278     }
    279 
    280     @Override
    281     protected void onSaveInstanceState(@NonNull Bundle outState) {
    282         super.onSaveInstanceState(outState);
    283         getFragmentManager().putFragment(outState,
    284                 SAVED_STATE_PREFERENCE_FRAGMENT, mPreferenceFragment);
    285         getFragmentManager().putFragment(outState,
    286                 SAVED_STATE_CONTENT_FRAGMENT, mContentFragment);
    287         outState.putParcelableList(SAVED_STATE_BLUETOOTH_DEVICES, mBluetoothDevices);
    288     }
    289 
    290     @Override
    291     protected void onStart() {
    292         super.onStart();
    293 
    294         if (DEBUG) {
    295             Log.d(TAG, "onStart() mPairingInBackground = " + mPairingInBackground);
    296         }
    297 
    298         // Only do the following if we are not coming back to this activity from
    299         // the Secure Pairing activity.
    300         if (!mPairingInBackground) {
    301             startBluetoothPairer();
    302         }
    303 
    304         mPairingInBackground = false;
    305     }
    306 
    307     @Override
    308     public void onResume() {
    309         super.onResume();
    310         if (mNoInputMode) {
    311             // Start timer count down for exiting activity.
    312             if (DEBUG) Log.d(TAG, "starting auto-exit timer");
    313             mAutoExitHandler.postDelayed(mAutoExitRunnable, EXIT_TIMEOUT_MILLIS);
    314         }
    315     }
    316 
    317     @Override
    318     public void onPause() {
    319         super.onPause();
    320         if (DEBUG) Log.d(TAG, "stopping auto-exit timer");
    321         mAutoExitHandler.removeCallbacks(mAutoExitRunnable);
    322     }
    323 
    324 
    325     @Override
    326     public void onStop() {
    327         if (DEBUG) {
    328             Log.d(TAG, "onStop()");
    329         }
    330         if (!mPairingBluetooth) {
    331             stopBluetoothPairer();
    332             mMsgHandler.removeCallbacksAndMessages(null);
    333         } else {
    334             // allow activity to remain in the background while we perform the
    335             // BT Secure pairing.
    336             mPairingInBackground = true;
    337         }
    338 
    339         super.onStop();
    340     }
    341 
    342     @Override
    343     protected void onDestroy() {
    344         super.onDestroy();
    345         resumeGamepadPairingService();
    346         if (mGamepadServiceConn != null) {
    347             unbindService(mGamepadServiceConn);
    348             mGamepadServiceConn = null;
    349         }
    350         stopBluetoothPairer();
    351         mMsgHandler.removeCallbacksAndMessages(null);
    352     }
    353 
    354     @Override
    355     public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
    356         if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) {
    357             if (mPairingBluetooth && !mDone) {
    358                 cancelBtPairing();
    359             }
    360         }
    361         return super.onKeyUp(keyCode, event);
    362     }
    363 
    364     @Override
    365     public void onNewIntent(Intent intent) {
    366         if (ACTION_CONNECT_INPUT.equals(intent.getAction()) &&
    367                 (intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) == 0) {
    368 
    369             KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
    370             if (event != null && event.getKeyCode() == KeyEvent.KEYCODE_PAIRING) {
    371                 if (event.getAction() == KeyEvent.ACTION_UP) {
    372                     onHwKeyEvent(false);
    373                 } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
    374                     onHwKeyEvent(true);
    375                 }
    376             }
    377         } else {
    378             setIntent(intent);
    379         }
    380     }
    381 
    382     private boolean serviceIntentIsHandled(Intent intent) {
    383         List<ResolveInfo> matches = getPackageManager().queryIntentServices(intent, 0);
    384         if (matches != null) {
    385             for (ResolveInfo info : matches) {
    386                 if (info.serviceInfo != null && info.serviceInfo.enabled) {
    387                     return true;
    388                 }
    389             }
    390         }
    391         return false;
    392     }
    393 
    394     private void pauseGamepadPairingService() {
    395         final Message m = Message.obtain(null, GAMEPAD_MESSAGE_PAUSE_PAIRING);
    396         if (mGamepadService != null) {
    397             try {
    398                 mGamepadService.send(m);
    399             } catch (RemoteException e) {
    400                 Log.e(TAG, "Remote exception sending message " + m + " " + e);
    401             }
    402         } else {
    403             Log.d(TAG, "Queueing pause message");
    404             mGamepadMessageQueue.add(m);
    405         }
    406     }
    407 
    408     private void resumeGamepadPairingService() {
    409         final Message m = Message.obtain(null, GAMEPAD_MESSAGE_RESUME_PAIRING);
    410         if (mGamepadService != null) {
    411             try {
    412                 mGamepadService.send(m);
    413             } catch (RemoteException e) {
    414                 Log.e(TAG, "Remote exception sending message " + m + " " + e);
    415             }
    416         } else {
    417             Log.d(TAG, "Queueing resume message");
    418             mGamepadMessageQueue.add(m);
    419         }
    420     }
    421 
    422 
    423     public void onActionClicked(String address) {
    424         cancelPairingCountdown();
    425         if (!mDone) {
    426             btDeviceClicked(address);
    427         }
    428     }
    429 
    430     // Events related to a device HW key
    431     protected void onHwKeyEvent(boolean keyDown) {
    432         if (!mHwKeyDown) {
    433             // HW key was in UP state before
    434             if (keyDown) {
    435                 // Back key pressed down
    436                 mHwKeyDown = true;
    437                 mHwKeyDidSelect = false;
    438                 mMsgHandler.sendEmptyMessageDelayed(MSG_TRIGGER_SELECT_DOWN, LONG_PRESS_DURATION);
    439             }
    440         } else {
    441             // HW key was in DOWN state before
    442             if (!keyDown) {
    443                 // HW key released
    444                 mHwKeyDown = false;
    445                 mMsgHandler.removeMessages(MSG_TRIGGER_SELECT_DOWN);
    446                 if (!mHwKeyDidSelect) {
    447                     // key wasn't pressed long enough for selection, move selection
    448                     // to next item.
    449                     mPreferenceFragment.advanceSelection();
    450                 }
    451                 mHwKeyDidSelect = false;
    452             }
    453         }
    454     }
    455 
    456     private void sendKeyEvent(int keyCode, boolean down) {
    457         InputManager iMgr = (InputManager) getSystemService(INPUT_SERVICE);
    458         if (iMgr != null) {
    459             long time = SystemClock.uptimeMillis();
    460             KeyEvent evt = new KeyEvent(time, time,
    461                     down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
    462                     keyCode, 0);
    463             iMgr.injectInputEvent(evt, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
    464         }
    465     }
    466 
    467     protected void updateView() {
    468         if (mPreferenceFragment == null) {
    469             // view not yet ready, update will happen on first layout event
    470             return;
    471         }
    472 
    473         int prevNumDevices = mPreferenceFragment.getPreferenceScreen().getPreferenceCount();
    474 
    475         mPreferenceFragment.updateList(mBluetoothDevices, mCurrentTargetAddress,
    476                 mCurrentTargetStatus, mCancelledAddress);
    477 
    478         if (mNoInputMode) {
    479             if (DEBUG) Log.d(TAG, "stopping auto-exit timer");
    480             mAutoExitHandler.removeCallbacks(mAutoExitRunnable);
    481             if (mBluetoothDevices.size() == 1 && prevNumDevices == 0) {
    482                 // first device added, start counter for autopair
    483                 mMsgHandler.sendEmptyMessageDelayed(MSG_START_AUTOPAIR_COUNTDOWN,
    484                         TIME_TO_START_AUTOPAIR_COUNT);
    485             } else {
    486 
    487                 // Start timer count down for exiting activity.
    488                 if (DEBUG) Log.d(TAG, "starting auto-exit timer");
    489                 mAutoExitHandler.postDelayed(mAutoExitRunnable, EXIT_TIMEOUT_MILLIS);
    490 
    491                 if (mBluetoothDevices.size() > 1) {
    492                     // More than one device found, cancel auto pair
    493                     cancelPairingCountdown();
    494                 }
    495            }
    496         }
    497 
    498         TransitionManager.beginDelayedTransition((ViewGroup) findViewById(R.id.content_frame));
    499 
    500         rearrangeViews();
    501     }
    502 
    503     private void rearrangeViews() {
    504         final boolean empty = mBluetoothDevices.isEmpty();
    505 
    506         final View contentView = findViewById(R.id.content_fragment);
    507         final ViewGroup.LayoutParams contentLayoutParams = contentView.getLayoutParams();
    508         contentLayoutParams.width = empty ? ViewGroup.LayoutParams.MATCH_PARENT :
    509                 getResources().getDimensionPixelSize(R.dimen.lb_content_section_width);
    510         contentView.setLayoutParams(contentLayoutParams);
    511 
    512         mContentFragment.setContentWidth(empty
    513                 ? getResources().getDimensionPixelSize(R.dimen.progress_fragment_content_width)
    514                 : getResources().getDimensionPixelSize(R.dimen.bt_progress_width_narrow));
    515     }
    516 
    517     private void setPairingText(CharSequence text) {
    518         if (mContentFragment != null) {
    519             mContentFragment.setExtraText(text);
    520         }
    521     }
    522 
    523     private void cancelPairingCountdown() {
    524         // Cancel countdown
    525         mMsgHandler.removeMessages(MSG_AUTOPAIR_TICK);
    526         mMsgHandler.removeMessages(MSG_START_AUTOPAIR_COUNTDOWN);
    527         setPairingText(null);
    528     }
    529 
    530     private void setTimeout(int timeout) {
    531         cancelTimeout();
    532         mMsgHandler.sendEmptyMessageDelayed(MSG_OP_TIMEOUT, timeout);
    533     }
    534 
    535     private void cancelTimeout() {
    536         mMsgHandler.removeMessages(MSG_OP_TIMEOUT);
    537     }
    538 
    539     protected void startAutoPairing() {
    540         if (mBluetoothDevices.size() > 0) {
    541             onActionClicked(mBluetoothDevices.get(0).getAddress());
    542         }
    543     }
    544 
    545     private void btDeviceClicked(String clickedAddress) {
    546         if (mBluetoothPairer != null && !mBluetoothPairer.isInProgress()) {
    547             if (mBluetoothPairer.getStatus() == BluetoothDevicePairer.STATUS_WAITING_TO_PAIR &&
    548                     mBluetoothPairer.getTargetDevice() != null) {
    549                 cancelBtPairing();
    550             } else {
    551                 if (DEBUG) {
    552                     Log.d(TAG, "Looking for " + clickedAddress +
    553                             " in available devices to start pairing");
    554                 }
    555                 for (BluetoothDevice target : mBluetoothDevices) {
    556                     if (target.getAddress().equalsIgnoreCase(clickedAddress)) {
    557                         if (DEBUG) {
    558                             Log.d(TAG, "Found it!");
    559                         }
    560                         mCancelledAddress = ADDRESS_NONE;
    561                         setPairingBluetooth(true);
    562                         mBluetoothPairer.startPairing(target);
    563                         break;
    564                     }
    565                 }
    566             }
    567         }
    568     }
    569 
    570     private void cancelBtPairing() {
    571         // cancel current request to pair
    572         if (mBluetoothPairer != null) {
    573             if (mBluetoothPairer.getTargetDevice() != null) {
    574                 mCancelledAddress = mBluetoothPairer.getTargetDevice().getAddress();
    575             } else {
    576                 mCancelledAddress = ADDRESS_NONE;
    577             }
    578             mBluetoothPairer.cancelPairing();
    579         }
    580         mPairingSuccess = false;
    581         setPairingBluetooth(false);
    582         mMsgHandler.sendEmptyMessageDelayed(MSG_REMOVE_CANCELED,
    583                 CANCEL_MESSAGE_TIMEOUT);
    584     }
    585 
    586     private void setPairingBluetooth(boolean pairing) {
    587         if (mPairingBluetooth != pairing) {
    588             mPairingBluetooth = pairing;
    589         }
    590     }
    591 
    592     private void startBluetoothPairer() {
    593         stopBluetoothPairer();
    594         mBluetoothPairer = new BluetoothDevicePairer(this, this);
    595         mBluetoothPairer.start();
    596 
    597         mBluetoothPairer.disableAutoPairing();
    598 
    599         mPairingSuccess = false;
    600         statusChanged();
    601     }
    602 
    603     private void stopBluetoothPairer() {
    604         if (mBluetoothPairer != null) {
    605             mBluetoothPairer.setListener(null);
    606             mBluetoothPairer.dispose();
    607             mBluetoothPairer = null;
    608         }
    609     }
    610 
    611     private String getMessageForStatus(int status) {
    612         final int msgId;
    613         String msg;
    614 
    615         switch (status) {
    616             case BluetoothDevicePairer.STATUS_WAITING_TO_PAIR:
    617             case BluetoothDevicePairer.STATUS_PAIRING:
    618                 msgId = R.string.accessory_state_pairing;
    619                 break;
    620             case BluetoothDevicePairer.STATUS_CONNECTING:
    621                 msgId = R.string.accessory_state_connecting;
    622                 break;
    623             case BluetoothDevicePairer.STATUS_ERROR:
    624                 msgId = R.string.accessory_state_error;
    625                 break;
    626             default:
    627                 return "";
    628         }
    629 
    630         msg = getString(msgId);
    631 
    632         return msg;
    633     }
    634 
    635     @Override
    636     public void statusChanged() {
    637         if (mBluetoothPairer == null) return;
    638 
    639         int numDevices = mBluetoothPairer.getAvailableDevices().size();
    640         int status = mBluetoothPairer.getStatus();
    641         int oldStatus = mPreviousStatus;
    642         mPreviousStatus = status;
    643 
    644         String address = mBluetoothPairer.getTargetDevice() == null ? ADDRESS_NONE :
    645                 mBluetoothPairer.getTargetDevice().getAddress();
    646 
    647         if (DEBUG) {
    648             String state = "?";
    649             switch (status) {
    650                 case BluetoothDevicePairer.STATUS_NONE:
    651                     state = "BluetoothDevicePairer.STATUS_NONE";
    652                     break;
    653                 case BluetoothDevicePairer.STATUS_SCANNING:
    654                     state = "BluetoothDevicePairer.STATUS_SCANNING";
    655                     break;
    656                 case BluetoothDevicePairer.STATUS_WAITING_TO_PAIR:
    657                     state = "BluetoothDevicePairer.STATUS_WAITING_TO_PAIR";
    658                     break;
    659                 case BluetoothDevicePairer.STATUS_PAIRING:
    660                     state = "BluetoothDevicePairer.STATUS_PAIRING";
    661                     break;
    662                 case BluetoothDevicePairer.STATUS_CONNECTING:
    663                     state = "BluetoothDevicePairer.STATUS_CONNECTING";
    664                     break;
    665                 case BluetoothDevicePairer.STATUS_ERROR:
    666                     state = "BluetoothDevicePairer.STATUS_ERROR";
    667                     break;
    668             }
    669             long time = mBluetoothPairer.getNextStageTime() - SystemClock.elapsedRealtime();
    670             Log.d(TAG, "Update received, number of devices:" + numDevices + " state: " +
    671                     state + " target device: " + address + " time to next event: " + time);
    672         }
    673 
    674         mBluetoothDevices.clear();
    675         for (BluetoothDevice device : mBluetoothPairer.getAvailableDevices()) {
    676             mBluetoothDevices.add(device);
    677         }
    678 
    679         cancelTimeout();
    680 
    681         switch (status) {
    682             case BluetoothDevicePairer.STATUS_NONE:
    683                 // if we just connected to something or just tried to connect
    684                 // to something, restart scanning just in case the user wants
    685                 // to pair another device.
    686                 if (oldStatus == BluetoothDevicePairer.STATUS_CONNECTING) {
    687                     if (mPairingSuccess) {
    688                         // Pairing complete
    689                         mCurrentTargetStatus = getString(R.string.accessory_state_paired);
    690                         mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
    691                         mMsgHandler.sendEmptyMessageDelayed(MSG_PAIRING_COMPLETE,
    692                                 DONE_MESSAGE_TIMEOUT);
    693                         mDone = true;
    694 
    695                         // Done, return here and just wait for the message
    696                         // to close the activity
    697                         return;
    698                     }
    699                     if (DEBUG) {
    700                         Log.d(TAG, "Invalidating and restarting.");
    701                     }
    702 
    703                     mBluetoothPairer.invalidateDevice(mBluetoothPairer.getTargetDevice());
    704                     mBluetoothPairer.start();
    705                     mBluetoothPairer.cancelPairing();
    706                     setPairingBluetooth(false);
    707 
    708                     // if this looks like a successful connection run, reflect
    709                     // this in the UI, otherwise use the default message
    710                     if (!mPairingSuccess && BluetoothDevicePairer.hasValidInputDevice(this)) {
    711                         mPairingSuccess = true;
    712                     }
    713                 }
    714                 break;
    715             case BluetoothDevicePairer.STATUS_SCANNING:
    716                 mPairingSuccess = false;
    717                 break;
    718             case BluetoothDevicePairer.STATUS_WAITING_TO_PAIR:
    719                 break;
    720             case BluetoothDevicePairer.STATUS_PAIRING:
    721                 // reset the pairing success value since this is now a new
    722                 // pairing run
    723                 mPairingSuccess = true;
    724                 setTimeout(PAIR_OPERATION_TIMEOUT);
    725                 break;
    726             case BluetoothDevicePairer.STATUS_CONNECTING:
    727                 setTimeout(CONNECT_OPERATION_TIMEOUT);
    728                 break;
    729             case BluetoothDevicePairer.STATUS_ERROR:
    730                 mPairingSuccess = false;
    731                 setPairingBluetooth(false);
    732                 if (mNoInputMode) {
    733                     clearDeviceList();
    734                 }
    735                 break;
    736         }
    737 
    738         mCurrentTargetAddress = address;
    739         mCurrentTargetStatus = getMessageForStatus(status);
    740         mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
    741     }
    742 
    743     private void clearDeviceList() {
    744         mBluetoothDevices.clear();
    745         mBluetoothPairer.clearDeviceList();
    746     }
    747 
    748     private void handlePairingTimeout() {
    749         if (mPairingInBackground) {
    750             finish();
    751         } else {
    752             // Either Pairing or Connecting timeout out.
    753             // Display error message and post delayed message to the scanning process.
    754             mPairingSuccess = false;
    755             if (mBluetoothPairer != null) {
    756                 mBluetoothPairer.cancelPairing();
    757             }
    758             mCurrentTargetStatus = getString(R.string.accessory_state_error);
    759             mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
    760             mMsgHandler.sendEmptyMessageDelayed(MSG_RESTART, RESTART_DELAY);
    761         }
    762     }
    763 
    764     List<BluetoothDevice> getBluetoothDevices() {
    765         return mBluetoothDevices;
    766     }
    767 
    768     String getCurrentTargetAddress() {
    769         return mCurrentTargetAddress;
    770     }
    771 
    772     String getCurrentTargetStatus() {
    773         return mCurrentTargetStatus;
    774     }
    775 
    776     String getCancelledAddress() {
    777         return mCancelledAddress;
    778     }
    779 }
    780