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