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.server.hdmi; 18 19 import android.hardware.hdmi.HdmiDeviceInfo; 20 import android.hardware.input.InputManager; 21 import android.os.Handler; 22 import android.os.Looper; 23 import android.os.Message; 24 import android.os.SystemClock; 25 import android.util.Slog; 26 import android.view.InputDevice; 27 import android.view.KeyCharacterMap; 28 import android.view.KeyEvent; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.util.IndentingPrintWriter; 32 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 33 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.Iterator; 37 import java.util.List; 38 39 /** 40 * Class that models a logical CEC device hosted in this system. Handles initialization, 41 * CEC commands that call for actions customized per device type. 42 */ 43 abstract class HdmiCecLocalDevice { 44 private static final String TAG = "HdmiCecLocalDevice"; 45 46 private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; 47 private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2; 48 // Timeout in millisecond for device clean up (5s). 49 // Normal actions timeout is 2s but some of them would have several sequence of timeout. 50 private static final int DEVICE_CLEANUP_TIMEOUT = 5000; 51 // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. 52 // When it expires, we can assume <User Control Release> is received. 53 private static final int FOLLOWER_SAFETY_TIMEOUT = 550; 54 55 protected final HdmiControlService mService; 56 protected final int mDeviceType; 57 protected int mAddress; 58 protected int mPreferredAddress; 59 protected HdmiDeviceInfo mDeviceInfo; 60 protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; 61 protected int mLastKeyRepeatCount = 0; 62 63 static class ActiveSource { 64 int logicalAddress; 65 int physicalAddress; 66 67 public ActiveSource() { 68 invalidate(); 69 } 70 public ActiveSource(int logical, int physical) { 71 logicalAddress = logical; 72 physicalAddress = physical; 73 } 74 public static ActiveSource of(int logical, int physical) { 75 return new ActiveSource(logical, physical); 76 } 77 public boolean isValid() { 78 return HdmiUtils.isValidAddress(logicalAddress); 79 } 80 public void invalidate() { 81 logicalAddress = Constants.ADDR_INVALID; 82 physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 83 } 84 public boolean equals(int logical, int physical) { 85 return logicalAddress == logical && physicalAddress == physical; 86 } 87 @Override 88 public boolean equals(Object obj) { 89 if (obj instanceof ActiveSource) { 90 ActiveSource that = (ActiveSource) obj; 91 return that.logicalAddress == logicalAddress && 92 that.physicalAddress == physicalAddress; 93 } 94 return false; 95 } 96 @Override 97 public int hashCode() { 98 return logicalAddress * 29 + physicalAddress; 99 } 100 @Override 101 public String toString() { 102 StringBuffer s = new StringBuffer(); 103 String logicalAddressString = (logicalAddress == Constants.ADDR_INVALID) 104 ? "invalid" : String.format("0x%02x", logicalAddress); 105 s.append("logical_address: ").append(logicalAddressString); 106 String physicalAddressString = (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) 107 ? "invalid" : String.format("0x%04x", physicalAddress); 108 s.append(", physical_address: ").append(physicalAddressString); 109 return s.toString(); 110 } 111 } 112 // Logical address of the active source. 113 @GuardedBy("mLock") 114 protected final ActiveSource mActiveSource = new ActiveSource(); 115 116 // Active routing path. Physical address of the active source but not all the time, such as 117 // when the new active source does not claim itself to be one. Note that we don't keep 118 // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}. 119 @GuardedBy("mLock") 120 private int mActiveRoutingPath; 121 122 protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 123 protected final Object mLock; 124 125 // A collection of FeatureAction. 126 // Note that access to this collection should happen in service thread. 127 private final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>(); 128 129 private final Handler mHandler = new Handler () { 130 @Override 131 public void handleMessage(Message msg) { 132 switch (msg.what) { 133 case MSG_DISABLE_DEVICE_TIMEOUT: 134 handleDisableDeviceTimeout(); 135 break; 136 case MSG_USER_CONTROL_RELEASE_TIMEOUT: 137 handleUserControlReleased(); 138 break; 139 } 140 } 141 }; 142 143 /** 144 * A callback interface to get notified when all pending action is cleared. 145 * It can be called when timeout happened. 146 */ 147 interface PendingActionClearedCallback { 148 void onCleared(HdmiCecLocalDevice device); 149 } 150 151 protected PendingActionClearedCallback mPendingActionClearedCallback; 152 153 protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { 154 mService = service; 155 mDeviceType = deviceType; 156 mAddress = Constants.ADDR_UNREGISTERED; 157 mLock = service.getServiceLock(); 158 } 159 160 // Factory method that returns HdmiCecLocalDevice of corresponding type. 161 static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) { 162 switch (deviceType) { 163 case HdmiDeviceInfo.DEVICE_TV: 164 return new HdmiCecLocalDeviceTv(service); 165 case HdmiDeviceInfo.DEVICE_PLAYBACK: 166 return new HdmiCecLocalDevicePlayback(service); 167 default: 168 return null; 169 } 170 } 171 172 @ServiceThreadOnly 173 void init() { 174 assertRunOnServiceThread(); 175 mPreferredAddress = getPreferredAddress(); 176 } 177 178 /** 179 * Called once a logical address of the local device is allocated. 180 */ 181 protected abstract void onAddressAllocated(int logicalAddress, int reason); 182 183 /** 184 * Get the preferred logical address from system properties. 185 */ 186 protected abstract int getPreferredAddress(); 187 188 /** 189 * Set the preferred logical address to system properties. 190 */ 191 protected abstract void setPreferredAddress(int addr); 192 193 /** 194 * Returns true if the TV input associated with the CEC device is ready 195 * to accept further processing such as input switching. This is used 196 * to buffer certain CEC commands and process it later if the input is not 197 * ready yet. For other types of local devices(non-TV), this method returns 198 * true by default to let the commands be processed right away. 199 */ 200 protected boolean isInputReady(int deviceId) { 201 return true; 202 } 203 204 /** 205 * Returns true if the local device allows the system to be put to standby. 206 * The default implementation returns true. 207 */ 208 protected boolean canGoToStandby() { 209 return true; 210 } 211 212 /** 213 * Dispatch incoming message. 214 * 215 * @param message incoming message 216 * @return true if consumed a message; otherwise, return false. 217 */ 218 @ServiceThreadOnly 219 boolean dispatchMessage(HdmiCecMessage message) { 220 assertRunOnServiceThread(); 221 int dest = message.getDestination(); 222 if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { 223 return false; 224 } 225 // Cache incoming message. Note that it caches only white-listed one. 226 mCecMessageCache.cacheMessage(message); 227 return onMessage(message); 228 } 229 230 @ServiceThreadOnly 231 protected final boolean onMessage(HdmiCecMessage message) { 232 assertRunOnServiceThread(); 233 if (dispatchMessageToAction(message)) { 234 return true; 235 } 236 switch (message.getOpcode()) { 237 case Constants.MESSAGE_ACTIVE_SOURCE: 238 return handleActiveSource(message); 239 case Constants.MESSAGE_INACTIVE_SOURCE: 240 return handleInactiveSource(message); 241 case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: 242 return handleRequestActiveSource(message); 243 case Constants.MESSAGE_GET_MENU_LANGUAGE: 244 return handleGetMenuLanguage(message); 245 case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS: 246 return handleGivePhysicalAddress(); 247 case Constants.MESSAGE_GIVE_OSD_NAME: 248 return handleGiveOsdName(message); 249 case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID: 250 return handleGiveDeviceVendorId(); 251 case Constants.MESSAGE_GET_CEC_VERSION: 252 return handleGetCecVersion(message); 253 case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: 254 return handleReportPhysicalAddress(message); 255 case Constants.MESSAGE_ROUTING_CHANGE: 256 return handleRoutingChange(message); 257 case Constants.MESSAGE_ROUTING_INFORMATION: 258 return handleRoutingInformation(message); 259 case Constants.MESSAGE_INITIATE_ARC: 260 return handleInitiateArc(message); 261 case Constants.MESSAGE_TERMINATE_ARC: 262 return handleTerminateArc(message); 263 case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: 264 return handleSetSystemAudioMode(message); 265 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 266 return handleSystemAudioModeStatus(message); 267 case Constants.MESSAGE_REPORT_AUDIO_STATUS: 268 return handleReportAudioStatus(message); 269 case Constants.MESSAGE_STANDBY: 270 return handleStandby(message); 271 case Constants.MESSAGE_TEXT_VIEW_ON: 272 return handleTextViewOn(message); 273 case Constants.MESSAGE_IMAGE_VIEW_ON: 274 return handleImageViewOn(message); 275 case Constants.MESSAGE_USER_CONTROL_PRESSED: 276 return handleUserControlPressed(message); 277 case Constants.MESSAGE_USER_CONTROL_RELEASED: 278 return handleUserControlReleased(); 279 case Constants.MESSAGE_SET_STREAM_PATH: 280 return handleSetStreamPath(message); 281 case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: 282 return handleGiveDevicePowerStatus(message); 283 case Constants.MESSAGE_MENU_REQUEST: 284 return handleMenuRequest(message); 285 case Constants.MESSAGE_MENU_STATUS: 286 return handleMenuStatus(message); 287 case Constants.MESSAGE_VENDOR_COMMAND: 288 return handleVendorCommand(message); 289 case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID: 290 return handleVendorCommandWithId(message); 291 case Constants.MESSAGE_SET_OSD_NAME: 292 return handleSetOsdName(message); 293 case Constants.MESSAGE_RECORD_TV_SCREEN: 294 return handleRecordTvScreen(message); 295 case Constants.MESSAGE_TIMER_CLEARED_STATUS: 296 return handleTimerClearedStatus(message); 297 case Constants.MESSAGE_REPORT_POWER_STATUS: 298 return handleReportPowerStatus(message); 299 case Constants.MESSAGE_TIMER_STATUS: 300 return handleTimerStatus(message); 301 case Constants.MESSAGE_RECORD_STATUS: 302 return handleRecordStatus(message); 303 default: 304 return false; 305 } 306 } 307 308 @ServiceThreadOnly 309 private boolean dispatchMessageToAction(HdmiCecMessage message) { 310 assertRunOnServiceThread(); 311 boolean processed = false; 312 // Use copied action list in that processCommand may remove itself. 313 for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) { 314 // Iterates all actions to check whether incoming message is consumed. 315 boolean result = action.processCommand(message); 316 processed = processed || result; 317 } 318 return processed; 319 } 320 321 @ServiceThreadOnly 322 protected boolean handleGivePhysicalAddress() { 323 assertRunOnServiceThread(); 324 325 int physicalAddress = mService.getPhysicalAddress(); 326 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 327 mAddress, physicalAddress, mDeviceType); 328 mService.sendCecCommand(cecMessage); 329 return true; 330 } 331 332 @ServiceThreadOnly 333 protected boolean handleGiveDeviceVendorId() { 334 assertRunOnServiceThread(); 335 int vendorId = mService.getVendorId(); 336 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 337 mAddress, vendorId); 338 mService.sendCecCommand(cecMessage); 339 return true; 340 } 341 342 @ServiceThreadOnly 343 protected boolean handleGetCecVersion(HdmiCecMessage message) { 344 assertRunOnServiceThread(); 345 int version = mService.getCecVersion(); 346 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 347 message.getSource(), version); 348 mService.sendCecCommand(cecMessage); 349 return true; 350 } 351 352 @ServiceThreadOnly 353 protected boolean handleActiveSource(HdmiCecMessage message) { 354 return false; 355 } 356 357 @ServiceThreadOnly 358 protected boolean handleInactiveSource(HdmiCecMessage message) { 359 return false; 360 } 361 362 @ServiceThreadOnly 363 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 364 return false; 365 } 366 367 @ServiceThreadOnly 368 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 369 assertRunOnServiceThread(); 370 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 371 // 'return false' will cause to reply with <Feature Abort>. 372 return false; 373 } 374 375 @ServiceThreadOnly 376 protected boolean handleGiveOsdName(HdmiCecMessage message) { 377 assertRunOnServiceThread(); 378 // Note that since this method is called after logical address allocation is done, 379 // mDeviceInfo should not be null. 380 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 381 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 382 if (cecMessage != null) { 383 mService.sendCecCommand(cecMessage); 384 } else { 385 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 386 } 387 return true; 388 } 389 390 protected boolean handleRoutingChange(HdmiCecMessage message) { 391 return false; 392 } 393 394 protected boolean handleRoutingInformation(HdmiCecMessage message) { 395 return false; 396 } 397 398 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 399 return false; 400 } 401 402 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 403 return false; 404 } 405 406 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 407 return false; 408 } 409 410 protected boolean handleTerminateArc(HdmiCecMessage message) { 411 return false; 412 } 413 414 protected boolean handleInitiateArc(HdmiCecMessage message) { 415 return false; 416 } 417 418 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 419 return false; 420 } 421 422 @ServiceThreadOnly 423 protected boolean handleStandby(HdmiCecMessage message) { 424 assertRunOnServiceThread(); 425 // Seq #12 426 if (mService.isControlEnabled() && !mService.isProhibitMode() 427 && mService.isPowerOnOrTransient()) { 428 mService.standby(); 429 return true; 430 } 431 return false; 432 } 433 434 @ServiceThreadOnly 435 protected boolean handleUserControlPressed(HdmiCecMessage message) { 436 assertRunOnServiceThread(); 437 mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); 438 if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { 439 mService.standby(); 440 return true; 441 } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { 442 mService.wakeUp(); 443 return true; 444 } 445 446 final long downTime = SystemClock.uptimeMillis(); 447 final byte[] params = message.getParams(); 448 final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params); 449 int keyRepeatCount = 0; 450 if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 451 if (keycode == mLastKeycode) { 452 keyRepeatCount = mLastKeyRepeatCount + 1; 453 } else { 454 injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0); 455 } 456 } 457 mLastKeycode = keycode; 458 mLastKeyRepeatCount = keyRepeatCount; 459 460 if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 461 injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount); 462 mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT), 463 FOLLOWER_SAFETY_TIMEOUT); 464 return true; 465 } 466 return false; 467 } 468 469 @ServiceThreadOnly 470 protected boolean handleUserControlReleased() { 471 assertRunOnServiceThread(); 472 mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); 473 mLastKeyRepeatCount = 0; 474 if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 475 final long upTime = SystemClock.uptimeMillis(); 476 injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); 477 mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; 478 return true; 479 } 480 return false; 481 } 482 483 static void injectKeyEvent(long time, int action, int keycode, int repeat) { 484 KeyEvent keyEvent = KeyEvent.obtain(time, time, action, keycode, 485 repeat, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_FROM_SYSTEM, 486 InputDevice.SOURCE_HDMI, null); 487 InputManager.getInstance().injectInputEvent(keyEvent, 488 InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 489 keyEvent.recycle(); 490 } 491 492 static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { 493 byte[] params = message.getParams(); 494 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 495 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 496 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION 497 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 498 } 499 500 static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { 501 byte[] params = message.getParams(); 502 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 503 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 504 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION 505 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 506 } 507 508 protected boolean handleTextViewOn(HdmiCecMessage message) { 509 return false; 510 } 511 512 protected boolean handleImageViewOn(HdmiCecMessage message) { 513 return false; 514 } 515 516 protected boolean handleSetStreamPath(HdmiCecMessage message) { 517 return false; 518 } 519 520 protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) { 521 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus( 522 mAddress, message.getSource(), mService.getPowerStatus())); 523 return true; 524 } 525 526 protected boolean handleMenuRequest(HdmiCecMessage message) { 527 // Always report menu active to receive Remote Control. 528 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportMenuStatus( 529 mAddress, message.getSource(), Constants.MENU_STATE_ACTIVATED)); 530 return true; 531 } 532 533 protected boolean handleMenuStatus(HdmiCecMessage message) { 534 return false; 535 } 536 537 protected boolean handleVendorCommand(HdmiCecMessage message) { 538 if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(), 539 message.getDestination(), message.getParams(), false)) { 540 // Vendor command listener may not have been registered yet. Respond with 541 // <Feature Abort> [NOT_IN_CORRECT_MODE] so that the sender can try again later. 542 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); 543 } 544 return true; 545 } 546 547 protected boolean handleVendorCommandWithId(HdmiCecMessage message) { 548 byte[] params = message.getParams(); 549 int vendorId = HdmiUtils.threeBytesToInt(params); 550 if (vendorId == mService.getVendorId()) { 551 if (!mService.invokeVendorCommandListenersOnReceived(mDeviceType, message.getSource(), 552 message.getDestination(), params, true)) { 553 mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE); 554 } 555 } else if (message.getDestination() != Constants.ADDR_BROADCAST && 556 message.getSource() != Constants.ADDR_UNREGISTERED) { 557 Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); 558 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE); 559 } else { 560 Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); 561 } 562 return true; 563 } 564 565 protected void sendStandby(int deviceId) { 566 // Do nothing. 567 } 568 569 protected boolean handleSetOsdName(HdmiCecMessage message) { 570 // The default behavior of <Set Osd Name> is doing nothing. 571 return true; 572 } 573 574 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 575 // The default behavior of <Record TV Screen> is replying <Feature Abort> with 576 // "Cannot provide source". 577 mService.maySendFeatureAbortCommand(message, Constants.ABORT_CANNOT_PROVIDE_SOURCE); 578 return true; 579 } 580 581 protected boolean handleTimerClearedStatus(HdmiCecMessage message) { 582 return false; 583 } 584 585 protected boolean handleReportPowerStatus(HdmiCecMessage message) { 586 return false; 587 } 588 589 protected boolean handleTimerStatus(HdmiCecMessage message) { 590 return false; 591 } 592 593 protected boolean handleRecordStatus(HdmiCecMessage message) { 594 return false; 595 } 596 597 @ServiceThreadOnly 598 final void handleAddressAllocated(int logicalAddress, int reason) { 599 assertRunOnServiceThread(); 600 mAddress = mPreferredAddress = logicalAddress; 601 onAddressAllocated(logicalAddress, reason); 602 setPreferredAddress(logicalAddress); 603 } 604 605 int getType() { 606 return mDeviceType; 607 } 608 609 @ServiceThreadOnly 610 HdmiDeviceInfo getDeviceInfo() { 611 assertRunOnServiceThread(); 612 return mDeviceInfo; 613 } 614 615 @ServiceThreadOnly 616 void setDeviceInfo(HdmiDeviceInfo info) { 617 assertRunOnServiceThread(); 618 mDeviceInfo = info; 619 } 620 621 // Returns true if the logical address is same as the argument. 622 @ServiceThreadOnly 623 boolean isAddressOf(int addr) { 624 assertRunOnServiceThread(); 625 return addr == mAddress; 626 } 627 628 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 629 @ServiceThreadOnly 630 void clearAddress() { 631 assertRunOnServiceThread(); 632 mAddress = Constants.ADDR_UNREGISTERED; 633 } 634 635 @ServiceThreadOnly 636 void addAndStartAction(final HdmiCecFeatureAction action) { 637 assertRunOnServiceThread(); 638 mActions.add(action); 639 if (mService.isPowerStandbyOrTransient()) { 640 Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action); 641 return; 642 } 643 action.start(); 644 } 645 646 @ServiceThreadOnly 647 void startQueuedActions() { 648 assertRunOnServiceThread(); 649 for (HdmiCecFeatureAction action : mActions) { 650 if (!action.started()) { 651 Slog.i(TAG, "Starting queued action:" + action); 652 action.start(); 653 } 654 } 655 } 656 657 // See if we have an action of a given type in progress. 658 @ServiceThreadOnly 659 <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) { 660 assertRunOnServiceThread(); 661 for (HdmiCecFeatureAction action : mActions) { 662 if (action.getClass().equals(clazz)) { 663 return true; 664 } 665 } 666 return false; 667 } 668 669 // Returns all actions matched with given class type. 670 @ServiceThreadOnly 671 <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) { 672 assertRunOnServiceThread(); 673 List<T> actions = Collections.<T>emptyList(); 674 for (HdmiCecFeatureAction action : mActions) { 675 if (action.getClass().equals(clazz)) { 676 if (actions.isEmpty()) { 677 actions = new ArrayList<T>(); 678 } 679 actions.add((T) action); 680 } 681 } 682 return actions; 683 } 684 685 /** 686 * Remove the given {@link HdmiCecFeatureAction} object from the action queue. 687 * 688 * @param action {@link HdmiCecFeatureAction} to remove 689 */ 690 @ServiceThreadOnly 691 void removeAction(final HdmiCecFeatureAction action) { 692 assertRunOnServiceThread(); 693 action.finish(false); 694 mActions.remove(action); 695 checkIfPendingActionsCleared(); 696 } 697 698 // Remove all actions matched with the given Class type. 699 @ServiceThreadOnly 700 <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) { 701 assertRunOnServiceThread(); 702 removeActionExcept(clazz, null); 703 } 704 705 // Remove all actions matched with the given Class type besides |exception|. 706 @ServiceThreadOnly 707 <T extends HdmiCecFeatureAction> void removeActionExcept(final Class<T> clazz, 708 final HdmiCecFeatureAction exception) { 709 assertRunOnServiceThread(); 710 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 711 while (iter.hasNext()) { 712 HdmiCecFeatureAction action = iter.next(); 713 if (action != exception && action.getClass().equals(clazz)) { 714 action.finish(false); 715 iter.remove(); 716 } 717 } 718 checkIfPendingActionsCleared(); 719 } 720 721 protected void checkIfPendingActionsCleared() { 722 if (mActions.isEmpty() && mPendingActionClearedCallback != null) { 723 PendingActionClearedCallback callback = mPendingActionClearedCallback; 724 // To prevent from calling the callback again during handling the callback itself. 725 mPendingActionClearedCallback = null; 726 callback.onCleared(this); 727 } 728 } 729 730 protected void assertRunOnServiceThread() { 731 if (Looper.myLooper() != mService.getServiceLooper()) { 732 throw new IllegalStateException("Should run on service thread."); 733 } 734 } 735 736 /** 737 * Called when a hot-plug event issued. 738 * 739 * @param portId id of port where a hot-plug event happened 740 * @param connected whether to connected or not on the event 741 */ 742 void onHotplug(int portId, boolean connected) { 743 } 744 745 final HdmiControlService getService() { 746 return mService; 747 } 748 749 @ServiceThreadOnly 750 final boolean isConnectedToArcPort(int path) { 751 assertRunOnServiceThread(); 752 return mService.isConnectedToArcPort(path); 753 } 754 755 ActiveSource getActiveSource() { 756 synchronized (mLock) { 757 return mActiveSource; 758 } 759 } 760 761 void setActiveSource(ActiveSource newActive) { 762 setActiveSource(newActive.logicalAddress, newActive.physicalAddress); 763 } 764 765 void setActiveSource(HdmiDeviceInfo info) { 766 setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress()); 767 } 768 769 void setActiveSource(int logicalAddress, int physicalAddress) { 770 synchronized (mLock) { 771 mActiveSource.logicalAddress = logicalAddress; 772 mActiveSource.physicalAddress = physicalAddress; 773 } 774 mService.setLastInputForMhl(Constants.INVALID_PORT_ID); 775 } 776 777 int getActivePath() { 778 synchronized (mLock) { 779 return mActiveRoutingPath; 780 } 781 } 782 783 void setActivePath(int path) { 784 synchronized (mLock) { 785 mActiveRoutingPath = path; 786 } 787 mService.setActivePortId(pathToPortId(path)); 788 } 789 790 /** 791 * Returns the ID of the active HDMI port. The active port is the one that has the active 792 * routing path connected to it directly or indirectly under the device hierarchy. 793 */ 794 int getActivePortId() { 795 synchronized (mLock) { 796 return mService.pathToPortId(mActiveRoutingPath); 797 } 798 } 799 800 /** 801 * Update the active port. 802 * 803 * @param portId the new active port id 804 */ 805 void setActivePortId(int portId) { 806 // We update active routing path instead, since we get the active port id from 807 // the active routing path. 808 setActivePath(mService.portIdToPath(portId)); 809 } 810 811 @ServiceThreadOnly 812 HdmiCecMessageCache getCecMessageCache() { 813 assertRunOnServiceThread(); 814 return mCecMessageCache; 815 } 816 817 @ServiceThreadOnly 818 int pathToPortId(int newPath) { 819 assertRunOnServiceThread(); 820 return mService.pathToPortId(newPath); 821 } 822 823 /** 824 * Called when the system goes to standby mode. 825 * 826 * @param initiatedByCec true if this power sequence is initiated 827 * by the reception the CEC messages like <Standby> 828 */ 829 protected void onStandby(boolean initiatedByCec) {} 830 831 /** 832 * Disable device. {@code callback} is used to get notified when all pending 833 * actions are completed or timeout is issued. 834 * 835 * @param initiatedByCec true if this sequence is initiated 836 * by the reception the CEC messages like <Standby> 837 * @param origialCallback callback interface to get notified when all pending actions are 838 * cleared 839 */ 840 protected void disableDevice(boolean initiatedByCec, 841 final PendingActionClearedCallback origialCallback) { 842 mPendingActionClearedCallback = new PendingActionClearedCallback() { 843 @Override 844 public void onCleared(HdmiCecLocalDevice device) { 845 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); 846 origialCallback.onCleared(device); 847 } 848 }; 849 mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), 850 DEVICE_CLEANUP_TIMEOUT); 851 } 852 853 @ServiceThreadOnly 854 private void handleDisableDeviceTimeout() { 855 assertRunOnServiceThread(); 856 857 // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. 858 // onCleard will be called at the last action's finish method. 859 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 860 while (iter.hasNext()) { 861 HdmiCecFeatureAction action = iter.next(); 862 action.finish(false); 863 iter.remove(); 864 } 865 } 866 867 /** 868 * Send a key event to other device. 869 * 870 * @param keyCode key code defined in {@link android.view.KeyEvent} 871 * @param isPressed {@code true} for key down event 872 */ 873 protected void sendKeyEvent(int keyCode, boolean isPressed) { 874 Slog.w(TAG, "sendKeyEvent not implemented"); 875 } 876 877 void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { 878 mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlPressed( 879 mAddress, targetAddress, cecKeycode)); 880 mService.sendCecCommand(HdmiCecMessageBuilder.buildUserControlReleased( 881 mAddress, targetAddress)); 882 } 883 884 /** 885 * Dump internal status of HdmiCecLocalDevice object. 886 */ 887 protected void dump(final IndentingPrintWriter pw) { 888 pw.println("mDeviceType: " + mDeviceType); 889 pw.println("mAddress: " + mAddress); 890 pw.println("mPreferredAddress: " + mPreferredAddress); 891 pw.println("mDeviceInfo: " + mDeviceInfo); 892 pw.println("mActiveSource: " + mActiveSource); 893 pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); 894 } 895 } 896