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