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