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