• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
onServiceConnected(ComponentName name, IBinder service)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
onServiceDisconnected(ComponentName name)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 
setActivity(AddAccessoryActivity activity)154         public void setActivity(AddAccessoryActivity activity) {
155             mActivityRef = new WeakReference<>(activity);
156         }
157 
158         @Override
handleMessage(Message msg)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
onCreate(Bundle savedInstanceState)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
onSaveInstanceState(@onNull Bundle outState)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
onStart()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
onResume()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
onPause()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
onStop()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
onDestroy()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
onKeyUp(int keyCode, @NonNull KeyEvent event)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
onNewIntent(Intent intent)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 
serviceIntentIsHandled(Intent intent)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 
pauseGamepadPairingService()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 
resumeGamepadPairingService()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 
onActionClicked(String address)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
onHwKeyEvent(boolean keyDown)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 
sendKeyEvent(int keyCode, boolean down)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 
updateView()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 
rearrangeViews()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 
setPairingText(CharSequence text)517     private void setPairingText(CharSequence text) {
518         if (mContentFragment != null) {
519             mContentFragment.setExtraText(text);
520         }
521     }
522 
cancelPairingCountdown()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 
setTimeout(int timeout)530     private void setTimeout(int timeout) {
531         cancelTimeout();
532         mMsgHandler.sendEmptyMessageDelayed(MSG_OP_TIMEOUT, timeout);
533     }
534 
cancelTimeout()535     private void cancelTimeout() {
536         mMsgHandler.removeMessages(MSG_OP_TIMEOUT);
537     }
538 
startAutoPairing()539     protected void startAutoPairing() {
540         if (mBluetoothDevices.size() > 0) {
541             onActionClicked(mBluetoothDevices.get(0).getAddress());
542         }
543     }
544 
btDeviceClicked(String clickedAddress)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 
cancelBtPairing()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 
setPairingBluetooth(boolean pairing)586     private void setPairingBluetooth(boolean pairing) {
587         if (mPairingBluetooth != pairing) {
588             mPairingBluetooth = pairing;
589         }
590     }
591 
startBluetoothPairer()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 
stopBluetoothPairer()603     private void stopBluetoothPairer() {
604         if (mBluetoothPairer != null) {
605             mBluetoothPairer.setListener(null);
606             mBluetoothPairer.dispose();
607             mBluetoothPairer = null;
608         }
609     }
610 
getMessageForStatus(int status)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
statusChanged()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 
clearDeviceList()743     private void clearDeviceList() {
744         mBluetoothDevices.clear();
745         mBluetoothPairer.clearDeviceList();
746     }
747 
handlePairingTimeout()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 
getBluetoothDevices()764     List<BluetoothDevice> getBluetoothDevices() {
765         return mBluetoothDevices;
766     }
767 
getCurrentTargetAddress()768     String getCurrentTargetAddress() {
769         return mCurrentTargetAddress;
770     }
771 
getCurrentTargetStatus()772     String getCurrentTargetStatus() {
773         return mCurrentTargetStatus;
774     }
775 
getCancelledAddress()776     String getCancelledAddress() {
777         return mCancelledAddress;
778     }
779 }
780