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
17package com.android.tv.settings.accessories;
18
19import com.android.tv.settings.R;
20import com.android.tv.settings.dialog.old.Action;
21import com.android.tv.settings.dialog.old.ActionAdapter;
22import com.android.tv.settings.dialog.old.ActionFragment;
23import com.android.tv.settings.dialog.old.DialogActivity;
24
25import android.app.FragmentManager;
26import android.app.FragmentTransaction;
27import android.bluetooth.BluetoothDevice;
28import android.content.Intent;
29import android.graphics.drawable.AnimationDrawable;
30import android.hardware.input.InputManager;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.Message;
34import android.os.RemoteException;
35import android.os.ServiceManager;
36import android.os.SystemClock;
37import android.service.dreams.DreamService;
38import android.service.dreams.IDreamManager;
39import android.support.v4.view.ViewCompat;
40import android.util.Log;
41import android.view.KeyEvent;
42import android.view.View;
43import android.view.ViewGroup;
44import android.view.ViewTreeObserver;
45import android.view.animation.DecelerateInterpolator;
46import android.widget.FrameLayout;
47import android.widget.ImageView;
48import android.widget.TextView;
49
50import java.util.ArrayList;
51
52/**
53 * Activity for detecting and adding (pairing) new bluetooth devices.
54 */
55public class AddAccessoryActivity extends DialogActivity
56        implements ActionAdapter.Listener,
57        InputPairer.EventListener {
58
59    private static final boolean DEBUG = false;
60    private static final String TAG = "aah.AddAccessoryActivity";
61
62    private static final String ACTION_CONNECT_INPUT =
63            "com.google.android.intent.action.CONNECT_INPUT";
64
65    private static final String INTENT_EXTRA_NO_INPUT_MODE = "no_input_mode";
66
67    private static final String KEY_BT_DEVICE = "selected_bt_device";
68
69    private static final String ADDRESS_NONE = "NONE";
70
71    private static final int AUTOPAIR_COUNT = 10;
72
73    private static final int MSG_UPDATE_VIEW = 1;
74    private static final int MSG_REMOVE_CANCELED = 2;
75    private static final int MSG_PAIRING_COMPLETE = 3;
76    private static final int MSG_OP_TIMEOUT = 4;
77    private static final int MSG_RESTART = 5;
78    private static final int MSG_TRIGGER_SELECT_DOWN = 6;
79    private static final int MSG_TRIGGER_SELECT_UP = 7;
80    private static final int MSG_AUTOPAIR_TICK = 8;
81    private static final int MSG_START_AUTOPAIR_COUNTDOWN = 9;
82    private static final int MSG_MULTIPAIR_BLINK = 10;
83
84    private static final int CANCEL_MESSAGE_TIMEOUT = 3000;
85    private static final int DONE_MESSAGE_TIMEOUT = 3000;
86    private static final int PAIR_OPERATION_TIMEOUT = 120000;
87    private static final int CONNECT_OPERATION_TIMEOUT = 15000;
88    private static final int RESTART_DELAY = 3000;
89    private static final int LONG_PRESS_DURATION = 3000;
90    private static final int KEY_DOWN_TIME = 150;
91    private static final int TIME_TO_START_AUTOPAIR_COUNT = 5000;
92    private static final int BLINK_START = 1000;
93    private static final int EXIT_TIMEOUT_MILLIS = 90 * 1000;
94
95    private ActionFragment mActionFragment;
96    private ArrayList<Action> mActions;
97    private AddAccessoryContentFragment mContentFragment;
98
99    // members related to Bluetooth pairing
100    private InputPairer mBtPairer;
101    private int mPreviousStatus = InputPairer.STATUS_NONE;
102    private boolean mPairingSuccess = false;
103    private boolean mPairingBluetooth = false;
104    private ArrayList<BluetoothDevice> mBtDevices;
105    private String mCancelledAddress = ADDRESS_NONE;
106    private String mCurrentTargetAddress = ADDRESS_NONE;
107    private String mCurrentTargetStatus = "";
108    private boolean mPairingInBackground = false;
109
110    private boolean mActionsVisible = false;
111    private FrameLayout mTopLayout;
112    private View mActionView;
113    private View mContentView;
114    private boolean mShowingMultiFragment;
115    private TextView mAutoPairText;
116    private AnimationDrawable mAnimation;
117    private int mViewOffset = 0;
118    private static final int ANIMATE_IN_DELAY = 1500;
119    private static long mStartTime;
120    private boolean mAnimateOnStart = true;
121    private boolean mDone = false;
122    private final Object mLock = new Object();
123
124    private FragmentManager mFragmentManager;
125    private FragmentTransaction mFragmentTransaction;
126
127    private IDreamManager mDreamManager;
128    private boolean mHwKeyDown;
129    private boolean mHwKeyDidSelect;
130    private boolean mNoInputMode;
131    private boolean mActionsAnimationDone;
132    private boolean mFragmentTransactionPending;
133
134    // Internal message handler
135    private Handler mMsgHandler = new Handler() {
136        @Override
137        public void handleMessage(Message msg) {
138            switch (msg.what) {
139                case MSG_UPDATE_VIEW:
140                    updateView();
141                    break;
142                case MSG_REMOVE_CANCELED:
143                    mCancelledAddress = ADDRESS_NONE;
144                    updateView();
145                    break;
146                case MSG_PAIRING_COMPLETE:
147                    AddAccessoryActivity.this.finish();
148                    break;
149                case MSG_OP_TIMEOUT:
150                    handlePairingTimeout();
151                    break;
152                case MSG_RESTART:
153                    if (mBtPairer != null) {
154                        mBtPairer.start();
155                        mBtPairer.cancelPairing();
156                    }
157                    break;
158                case MSG_TRIGGER_SELECT_DOWN:
159                    sendKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, true);
160                    mHwKeyDidSelect = true;
161                    sendEmptyMessageDelayed(MSG_TRIGGER_SELECT_UP, KEY_DOWN_TIME);
162                    cancelPairingCountdown();
163                    break;
164                case MSG_TRIGGER_SELECT_UP:
165                    sendKeyEvent(KeyEvent.KEYCODE_DPAD_CENTER, false);
166                    break;
167                case MSG_START_AUTOPAIR_COUNTDOWN:
168                    mAutoPairText.setVisibility(View.VISIBLE);
169                    mAutoPairText.setText(String.format(
170                            getString(R.string.accessories_autopair_msg), AUTOPAIR_COUNT));
171                    sendMessageDelayed(mMsgHandler.obtainMessage(MSG_AUTOPAIR_TICK,
172                            AUTOPAIR_COUNT, 0, null), 1000);
173                    break;
174                case MSG_AUTOPAIR_TICK:
175                    int countToAutoPair = msg.arg1 - 1;
176                    if (mAutoPairText != null) {
177                        if (countToAutoPair <= 0) {
178                            mAutoPairText.setVisibility(View.GONE);
179                            // AutoPair
180                            startAutoPairing();
181                        } else {
182                            mAutoPairText.setText(String.format(
183                                    getString(R.string.accessories_autopair_msg),
184                                    countToAutoPair));
185                            sendMessageDelayed(mMsgHandler.obtainMessage(MSG_AUTOPAIR_TICK,
186                                    countToAutoPair, 0, null), 1000);
187                        }
188                    }
189                    break;
190                case MSG_MULTIPAIR_BLINK:
191                    // Kick off the blinking animation
192                    ImageView backImage = (ImageView) findViewById(R.id.back_panel_image);
193                    if (backImage != null) {
194                        mAnimation = (AnimationDrawable) backImage.getDrawable();
195                        if (mAnimation != null) {
196                            mAnimation.start();
197                        }
198                    }
199                    break;
200                default:
201                    super.handleMessage(msg);
202            }
203        }
204    };
205
206    private final Handler mAutoExitHandler = new Handler();
207
208    private final Runnable mAutoExitRunnable = new Runnable() {
209        @Override
210        public void run() {
211            stopActivity();
212        }
213    };
214
215    @Override
216    public void onCreate(Bundle savedInstanceState) {
217        setLayoutProperties(R.layout.add_accessory_custom_two_pane_dialog, R.id.content_fragment,
218                R.id.action_fragment);
219
220        super.onCreate(savedInstanceState);
221
222        mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.checkService(
223                DreamService.DREAM_SERVICE));
224
225        mFragmentManager = getFragmentManager();
226
227        mBtDevices = new ArrayList<BluetoothDevice>();
228
229        mActions = new ArrayList<Action>();
230
231        mNoInputMode = getIntent().getBooleanExtra(INTENT_EXTRA_NO_INPUT_MODE, false);
232        mHwKeyDown = false;
233
234        mActions.clear();
235
236        mActionFragment = ActionFragment.newInstance(mActions);
237        mContentFragment = AddAccessoryContentFragment.newInstance(false);
238        setContentAndActionFragments(mContentFragment, mActionFragment);
239        mShowingMultiFragment = false;
240
241        mActionsAnimationDone = false;
242        mFragmentTransactionPending = false;
243    }
244
245    @Override
246    protected void onStart() {
247        super.onStart();
248
249        if (DEBUG) {
250            Log.d(TAG, "onStart() mPairingInBackground = " + mPairingInBackground);
251        }
252
253        // Only do the following if we are not coming back to this activity from
254        // the Secure Pairing activity.
255        if (!mPairingInBackground) {
256            if (mAnimateOnStart) {
257                mAnimateOnStart = false;
258                ViewGroup contentView = (ViewGroup) findViewById(android.R.id.content);
259                mTopLayout = (FrameLayout) contentView.getChildAt(0);
260
261                // Fade out the old activity, and fade in the new activity.
262                overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
263
264                // Set the activity background
265                int bgColor = getResources().getColor(R.color.dialog_activity_background);
266                getBackgroundDrawable().setColor(bgColor);
267                mTopLayout.setBackground(getBackgroundDrawable());
268
269                // Delay the rest of the changes until the first layout event
270                mTopLayout.getViewTreeObserver().addOnGlobalLayoutListener(
271                        new ViewTreeObserver.OnGlobalLayoutListener() {
272                            @Override
273                            public void onGlobalLayout() {
274                                mTopLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
275
276                                // set the Action and Content fragments to their start offsets
277                                mActionView = findViewById(R.id.action_fragment);
278                                mContentView = findViewById(R.id.content_fragment);
279                                if (mActionView != null) {
280                                    mViewOffset = mActionView.getMeasuredWidth();
281                                    int offset = (ViewCompat.getLayoutDirection(mActionView) ==
282                                            View.LAYOUT_DIRECTION_RTL) ? -mViewOffset : mViewOffset;
283                                    mActionView.setTranslationX(offset);
284                                    mContentView.setTranslationX(offset / 2);
285                                }
286                                mAutoPairText = (TextView) findViewById(R.id.autopair_message);
287                                if (mAutoPairText != null) {
288                                    mAutoPairText.setVisibility(View.GONE);
289                                }
290                                updateView();
291                            }
292                        });
293            }
294
295            startBluetoothPairer();
296
297            mStartTime = SystemClock.elapsedRealtime();
298        }
299
300        mPairingInBackground = false;
301    }
302
303    @Override
304    public void onPause() {
305        super.onPause();
306        if (DEBUG) Log.d(TAG, "stopping auto-exit timer");
307        mAutoExitHandler.removeCallbacks(mAutoExitRunnable);
308    }
309
310    @Override
311    public void onResume() {
312        super.onResume();
313        if (mNoInputMode) {
314            // Start timer count down for exiting activity.
315            if (DEBUG) Log.d(TAG, "starting auto-exit timer");
316            mAutoExitHandler.postDelayed(mAutoExitRunnable, EXIT_TIMEOUT_MILLIS);
317        }
318    }
319
320    @Override
321    public void onStop() {
322        if (DEBUG) {
323            Log.d(TAG, "onStop()");
324        }
325        if (!mPairingBluetooth) {
326            stopActivity();
327        } else {
328            // allow activity to remain in the background while we perform the
329            // BT Secure pairing.
330            mPairingInBackground = true;
331        }
332
333        super.onStop();
334    }
335
336    @Override
337    public boolean onKeyUp(int keyCode, KeyEvent event) {
338        if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) {
339            if (mPairingBluetooth && !mDone) {
340                cancelBtPairing();
341            }
342        }
343        return super.onKeyUp(keyCode, event);
344    }
345
346    @Override
347    public void onNewIntent(Intent intent) {
348        if (ACTION_CONNECT_INPUT.equals(intent.getAction()) &&
349                (intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) == 0) {
350            // We were the front most app and we got a new intent.
351            // If screen saver is going, stop it.
352            try {
353                if (mDreamManager != null && mDreamManager.isDreaming()) {
354                    mDreamManager.awaken();
355                }
356            } catch (RemoteException e) {
357                // Do nothing.
358            }
359
360            KeyEvent event = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
361            if (event != null && event.getKeyCode() == KeyEvent.KEYCODE_PAIRING) {
362                if (event.getAction() == KeyEvent.ACTION_UP) {
363                    onHwKeyEvent(false);
364                } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
365                    onHwKeyEvent(true);
366                }
367            }
368        } else {
369            setIntent(intent);
370        }
371    }
372
373    @Override
374    protected void onIntroAnimationFinished() {
375        mActionsAnimationDone = true;
376        if (mFragmentTransactionPending) {
377            mFragmentTransactionPending = false;
378            switchToMultipleDevicesFragment();
379        }
380    }
381
382    @Override
383    public void onActionClicked(Action action) {
384        cancelPairingCountdown();
385        if (!mDone) {
386            String key = action.getKey();
387
388            if (KEY_BT_DEVICE.equals(key)) {
389                btDeviceClicked(action.getDescription());
390            }
391        }
392    }
393
394    // Events related to a device HW key
395    protected void onHwKeyEvent(boolean keyDown) {
396        if (!mHwKeyDown) {
397            // HW key was in UP state before
398            if (keyDown) {
399                // Back key pressed down
400                mHwKeyDown = true;
401                mHwKeyDidSelect = false;
402                mMsgHandler.sendEmptyMessageDelayed(MSG_TRIGGER_SELECT_DOWN, LONG_PRESS_DURATION);
403            }
404        } else {
405            // HW key was in DOWN state before
406            if (!keyDown) {
407                // HW key released
408                mHwKeyDown = false;
409                mMsgHandler.removeMessages(MSG_TRIGGER_SELECT_DOWN);
410                if (!mHwKeyDidSelect) {
411                    // key wasn't pressed long enough for selection, move selection
412                    // to next item.
413                    int selectedIndex = mActionFragment.getSelectedItemPosition() + 1;
414                    if (selectedIndex >= mActions.size()) {
415                        selectedIndex = 0;
416                    }
417                    mActionFragment.setSelectionSmooth(selectedIndex);
418                }
419                mHwKeyDidSelect = false;
420            }
421        }
422    }
423
424    private void sendKeyEvent(int keyCode, boolean down) {
425        InputManager iMgr = (InputManager) getSystemService(INPUT_SERVICE);
426        if (iMgr != null) {
427            long time = SystemClock.uptimeMillis();
428            KeyEvent evt = new KeyEvent(time, time,
429                    down ? KeyEvent.ACTION_DOWN : KeyEvent.ACTION_UP,
430                    keyCode, 0);
431            iMgr.injectInputEvent(evt, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
432        }
433    }
434
435    protected void updateView() {
436        if (mActionView == null || mStartTime == 0) {
437            // view not yet ready, update will happen on first layout event
438            return;
439        }
440
441        synchronized (mLock) {
442            int prevNumDevices = mActions.size();
443            mActions.clear();
444
445            if (mActionFragment != null && mBtPairer != null) {
446                // Add entries for the discovered Bluetooth devices
447                for (BluetoothDevice bt : mBtDevices) {
448                    String title = bt.getName();
449                    String desc;
450                    if (mCurrentTargetAddress.equalsIgnoreCase(bt.getAddress()) &&
451                            !mCurrentTargetStatus.isEmpty()) {
452                        desc = mCurrentTargetStatus;
453                    } else if (mCancelledAddress.equalsIgnoreCase(bt.getAddress())) {
454                        desc = getString(R.string.accessory_state_canceled);
455                    } else {
456                        desc = bt.getAddress();
457                    }
458                    mActions.add(new Action.Builder()
459                            .key(KEY_BT_DEVICE)
460                            .title(title)
461                            .description(desc.toUpperCase())
462                            .drawableResource(AccessoryUtils.getImageIdForDevice(bt))
463                            .build());
464                }
465            }
466
467            // Update the main fragment.
468            ActionAdapter adapter = (ActionAdapter) mActionFragment.getAdapter();
469            if (adapter != null) {
470                adapter.setActions(mActions);
471            }
472
473            if (!mActionsVisible && mActions.size() > 0) {
474                mActionsVisible = true;
475                long delay = ANIMATE_IN_DELAY - (SystemClock.elapsedRealtime() - mStartTime);
476                if (delay > 0) {
477                    // Make sure we have a little bit of time after the activity
478                    // fades in
479                    // before we animate the actions in
480                    mActionView.postDelayed(new Runnable() {
481                        @Override
482                        public void run() {
483                            animateActionsIn();
484                        }
485                    }, delay);
486                } else {
487                    animateActionsIn();
488                }
489            }
490
491            if (mNoInputMode) {
492                if (DEBUG) Log.d(TAG, "stopping auto-exit timer");
493                mAutoExitHandler.removeCallbacks(mAutoExitRunnable);
494                if (mActions.size() == 1 && prevNumDevices == 0) {
495                    // first device added, start counter for autopair
496                    mMsgHandler.sendEmptyMessageDelayed(MSG_START_AUTOPAIR_COUNTDOWN,
497                            TIME_TO_START_AUTOPAIR_COUNT);
498                } else {
499
500                    // Start timer count down for exiting activity.
501                    if (DEBUG) Log.d(TAG, "starting auto-exit timer");
502                    mAutoExitHandler.postDelayed(mAutoExitRunnable, EXIT_TIMEOUT_MILLIS);
503
504                    if (mActions.size() > 1) {
505                        // More than one device found, cancel auto pair
506                        cancelPairingCountdown();
507
508                        if (!mShowingMultiFragment && !mFragmentTransactionPending) {
509                            if (mActionsAnimationDone) {
510                                switchToMultipleDevicesFragment();
511                            } else {
512                                mFragmentTransactionPending = true;
513                            }
514                        }
515                    }
516               }
517            }
518        }
519    }
520
521    private void cancelPairingCountdown() {
522        // Cancel countdown
523        mMsgHandler.removeMessages(MSG_AUTOPAIR_TICK);
524        mMsgHandler.removeMessages(MSG_START_AUTOPAIR_COUNTDOWN);
525        if (mAutoPairText != null) {
526            mAutoPairText.setVisibility(View.GONE);
527        }
528    }
529
530    protected void switchToMultipleDevicesFragment() {
531        FragmentTransaction ft = mFragmentManager.beginTransaction();
532        mContentFragment = AddAccessoryContentFragment.newInstance(true);
533        ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
534        ft.replace(R.id.content_fragment, mContentFragment);
535        ft.disallowAddToBackStack();
536
537        ft.commit();
538        mMsgHandler.sendEmptyMessageDelayed(MSG_MULTIPAIR_BLINK, BLINK_START);
539        mShowingMultiFragment = true;
540    }
541
542    private void setTimeout(int timeout) {
543        cancelTimeout();
544        mMsgHandler.sendEmptyMessageDelayed(MSG_OP_TIMEOUT, timeout);
545    }
546
547    private void cancelTimeout() {
548        mMsgHandler.removeMessages(MSG_OP_TIMEOUT);
549    }
550
551    private void animateActionsIn() {
552        prepareAndAnimateView(mContentView, 1f, mViewOffset / 2, 0, ANIMATE_IN_DURATION,
553                new DecelerateInterpolator(1.0f), true);
554        prepareAndAnimateView(mActionView, 1f, mViewOffset, 0, ANIMATE_IN_DURATION,
555                new DecelerateInterpolator(1.0f), false);
556    }
557
558    protected void startAutoPairing() {
559        if (mActions.size() > 0) {
560            onActionClicked(mActions.get(0));
561        }
562    }
563
564    private void btDeviceClicked(String clickedAddress) {
565        if (mBtPairer != null && !mBtPairer.isInProgress()) {
566            if (mBtPairer.getStatus() == InputPairer.STATUS_WAITING_TO_PAIR &&
567                    mBtPairer.getTargetDevice() != null) {
568                cancelBtPairing();
569            } else {
570                if (DEBUG) {
571                    Log.d(TAG, "Looking for " + clickedAddress +
572                            " in available devices to start pairing");
573                }
574                for (BluetoothDevice target : mBtDevices) {
575                    if (target.getAddress().equalsIgnoreCase(clickedAddress)) {
576                        if (DEBUG) {
577                            Log.d(TAG, "Found it!");
578                        }
579                        mCancelledAddress = ADDRESS_NONE;
580                        setPairingBluetooth(true);
581                        mBtPairer.startPairing(target);
582                        break;
583                    }
584                }
585            }
586        }
587    }
588
589    private void cancelBtPairing() {
590        // cancel current request to pair
591        if (mBtPairer != null) {
592            if (mBtPairer.getTargetDevice() != null) {
593                mCancelledAddress = mBtPairer.getTargetDevice().getAddress();
594            } else {
595                mCancelledAddress = ADDRESS_NONE;
596            }
597            mBtPairer.cancelPairing();
598        }
599        mPairingSuccess = false;
600        setPairingBluetooth(false);
601        mMsgHandler.sendEmptyMessageDelayed(MSG_REMOVE_CANCELED,
602                CANCEL_MESSAGE_TIMEOUT);
603    }
604
605    private void setPairingBluetooth(boolean pairing) {
606        if (mPairingBluetooth != pairing) {
607            mPairingBluetooth = pairing;
608        }
609    }
610
611    private void startBluetoothPairer() {
612        stopBluetoothPairer();
613        mBtPairer = new InputPairer(this, this);
614        mBtPairer.start();
615
616        // Disable auto-pairing
617        mBtPairer.cancelPairing();
618
619        mPairingSuccess = false;
620        statusChanged();
621    }
622
623    private void stopBluetoothPairer() {
624        if (mBtPairer != null) {
625            mBtPairer.setListener(null);
626            mBtPairer.dispose();
627            mBtPairer = null;
628        }
629    }
630
631    private String getMessageForStatus(int status) {
632        int msgId = 0;
633        String msg;
634
635        switch (status) {
636            case InputPairer.STATUS_WAITING_TO_PAIR:
637            case InputPairer.STATUS_PAIRING:
638                msgId = R.string.accessory_state_pairing;
639                break;
640            case InputPairer.STATUS_CONNECTING:
641                msgId = R.string.accessory_state_connecting;
642                break;
643            case InputPairer.STATUS_ERROR:
644                msgId = R.string.accessory_state_error;
645                break;
646            default:
647                return "";
648        }
649
650        msg = getString(msgId);
651
652        return msg;
653    }
654
655    @Override
656    public void statusChanged() {
657        synchronized (mLock) {
658            if (mBtPairer == null) return;
659
660            int numDevices = mBtPairer.getAvailableDevices().size();
661            int status = mBtPairer.getStatus();
662            int oldStatus = mPreviousStatus;
663            mPreviousStatus = status;
664
665            String address = mBtPairer.getTargetDevice() == null ? ADDRESS_NONE :
666                    mBtPairer.getTargetDevice().getAddress();
667
668            if (DEBUG) {
669                String state = "?";
670                switch (status) {
671                    case InputPairer.STATUS_NONE:
672                        state = "InputPairer.STATUS_NONE";
673                        break;
674                    case InputPairer.STATUS_SCANNING:
675                        state = "InputPairer.STATUS_SCANNING";
676                        break;
677                    case InputPairer.STATUS_WAITING_TO_PAIR:
678                        state = "InputPairer.STATUS_WAITING_TO_PAIR";
679                        break;
680                    case InputPairer.STATUS_PAIRING:
681                        state = "InputPairer.STATUS_PAIRING";
682                        break;
683                    case InputPairer.STATUS_CONNECTING:
684                        state = "InputPairer.STATUS_CONNECTING";
685                        break;
686                    case InputPairer.STATUS_ERROR:
687                        state = "InputPairer.STATUS_ERROR";
688                        break;
689                }
690                long time = mBtPairer.getNextStageTime() - SystemClock.elapsedRealtime();
691                Log.d(TAG, "Update received, number of devices:" + numDevices + " state: " +
692                        state + " target device: " + address + " time to next event: " + time);
693            }
694
695            mBtDevices.clear();
696            for (BluetoothDevice device : mBtPairer.getAvailableDevices()) {
697                mBtDevices.add(device);
698            }
699
700            cancelTimeout();
701
702            switch (status) {
703                case InputPairer.STATUS_NONE:
704                    // if we just connected to something or just tried to connect
705                    // to something, restart scanning just in case the user wants
706                    // to pair another device.
707                    if (oldStatus == InputPairer.STATUS_CONNECTING) {
708                        if (mPairingSuccess) {
709                            // Pairing complete
710                            mCurrentTargetStatus = getString(R.string.accessory_state_paired);
711                            mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
712                            mMsgHandler.sendEmptyMessageDelayed(MSG_PAIRING_COMPLETE,
713                                    DONE_MESSAGE_TIMEOUT);
714                            mDone = true;
715                            if (mAnimation != null) {
716                                mAnimation.setOneShot(true);
717                            }
718
719                            // Done, return here and just wait for the message
720                            // to close the activity
721                            return;
722                        }
723                        if (DEBUG) {
724                            Log.d(TAG, "Invalidating and restarting.");
725                        }
726
727                        mBtPairer.invalidateDevice(mBtPairer.getTargetDevice());
728                        mBtPairer.start();
729                        mBtPairer.cancelPairing();
730                        setPairingBluetooth(false);
731
732                        // if this looks like a successful connection run, reflect
733                        // this in the UI, otherwise use the default message
734                        if (!mPairingSuccess && InputPairer.hasValidInputDevice(this)) {
735                            mPairingSuccess = true;
736                        }
737                    }
738                    break;
739                case InputPairer.STATUS_SCANNING:
740                    mPairingSuccess = false;
741                    break;
742                case InputPairer.STATUS_WAITING_TO_PAIR:
743                    break;
744                case InputPairer.STATUS_PAIRING:
745                    // reset the pairing success value since this is now a new
746                    // pairing run
747                    mPairingSuccess = true;
748                    setTimeout(PAIR_OPERATION_TIMEOUT);
749                    break;
750                case InputPairer.STATUS_CONNECTING:
751                    setTimeout(CONNECT_OPERATION_TIMEOUT);
752                    break;
753                case InputPairer.STATUS_ERROR:
754                    mPairingSuccess = false;
755                    setPairingBluetooth(false);
756                    if (mNoInputMode) {
757                        clearDeviceList();
758                    }
759                    break;
760            }
761
762            mCurrentTargetAddress = address;
763            mCurrentTargetStatus = getMessageForStatus(status);
764            mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
765        }
766    }
767
768    private void clearDeviceList() {
769        mBtDevices.clear();
770        mBtPairer.clearDeviceList();
771    }
772
773    private void stopActivity() {
774        stopBluetoothPairer();
775        mMsgHandler.removeCallbacksAndMessages(null);
776        mAnimateOnStart = true;
777
778        // Forcing this activity to finish in OnStop, to make sure it always gets created
779        // fresh, since it has different behavior depending on the intent that launched
780        // it (Settings vs HW button press).
781        Log.d(TAG, "Calling finish() on activity.onStop().");
782        finish();
783    }
784
785    private void handlePairingTimeout() {
786        if (mPairingInBackground) {
787            stopActivity();
788        } else {
789            // Either Pairing or Connecting timeout out.
790            // Display error message and post delayed message to the scanning process.
791            mPairingSuccess = false;
792            if (mBtPairer != null) {
793                mBtPairer.cancelPairing();
794            }
795            mCurrentTargetStatus = getString(R.string.accessory_state_error);
796            mMsgHandler.sendEmptyMessage(MSG_UPDATE_VIEW);
797            mMsgHandler.sendEmptyMessageDelayed(MSG_RESTART, RESTART_DELAY);
798        }
799    }
800
801}
802