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