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