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