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