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