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