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