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