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