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.CallSuper; 20 import android.hardware.hdmi.DeviceFeatures; 21 import android.hardware.hdmi.HdmiControlManager; 22 import android.hardware.hdmi.HdmiDeviceInfo; 23 import android.hardware.hdmi.IHdmiControlCallback; 24 import android.hardware.input.InputManager; 25 import android.hardware.input.InputManagerGlobal; 26 import android.hardware.tv.cec.V1_0.Result; 27 import android.hardware.tv.cec.V1_0.SendMessageResult; 28 import android.media.AudioManager; 29 import android.os.Handler; 30 import android.os.Looper; 31 import android.os.Message; 32 import android.os.RemoteException; 33 import android.os.SystemClock; 34 import android.util.Slog; 35 import android.view.InputDevice; 36 import android.view.KeyCharacterMap; 37 import android.view.KeyEvent; 38 39 import com.android.internal.annotations.GuardedBy; 40 import com.android.internal.annotations.VisibleForTesting; 41 import com.android.internal.util.IndentingPrintWriter; 42 import com.android.server.hdmi.Constants.LocalActivePort; 43 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 44 import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 45 46 import java.text.SimpleDateFormat; 47 import java.util.ArrayList; 48 import java.util.Collections; 49 import java.util.Date; 50 import java.util.Iterator; 51 import java.util.List; 52 import java.util.concurrent.ArrayBlockingQueue; 53 54 /** 55 * Class that models a logical CEC device hosted in this system. Handles initialization, CEC 56 * commands that call for actions customized per device type. 57 */ 58 abstract class HdmiCecLocalDevice extends HdmiLocalDevice { 59 private static final String TAG = "HdmiCecLocalDevice"; 60 61 private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10; 62 private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; 63 private static final int MSG_USER_CONTROL_RELEASE_TIMEOUT = 2; 64 // Timeout in millisecond for device clean up (5s). 65 // Normal actions timeout is 2s but some of them would have several sequence of timeout. 66 private static final int DEVICE_CLEANUP_TIMEOUT = 5000; 67 // Within the timer, a received <User Control Pressed> will start "Press and Hold" behavior. 68 // When it expires, we can assume <User Control Release> is received. 69 private static final int FOLLOWER_SAFETY_TIMEOUT = 550; 70 71 protected int mPreferredAddress; 72 @GuardedBy("mLock") 73 private HdmiDeviceInfo mDeviceInfo; 74 protected int mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; 75 protected int mLastKeyRepeatCount = 0; 76 77 HdmiCecStandbyModeHandler mStandbyHandler; 78 79 // Stores recent changes to the active source in the CEC network. 80 private final ArrayBlockingQueue<HdmiCecController.Dumpable> mActiveSourceHistory = 81 new ArrayBlockingQueue<>(MAX_HDMI_ACTIVE_SOURCE_HISTORY); 82 83 static class ActiveSource { 84 int logicalAddress; 85 int physicalAddress; 86 ActiveSource()87 public ActiveSource() { 88 invalidate(); 89 } 90 ActiveSource(int logical, int physical)91 public ActiveSource(int logical, int physical) { 92 logicalAddress = logical; 93 physicalAddress = physical; 94 } 95 of(ActiveSource source)96 public static ActiveSource of(ActiveSource source) { 97 return new ActiveSource(source.logicalAddress, source.physicalAddress); 98 } 99 of(int logical, int physical)100 public static ActiveSource of(int logical, int physical) { 101 return new ActiveSource(logical, physical); 102 } 103 isValid()104 public boolean isValid() { 105 return HdmiUtils.isValidAddress(logicalAddress); 106 } 107 invalidate()108 public void invalidate() { 109 logicalAddress = Constants.ADDR_INVALID; 110 physicalAddress = Constants.INVALID_PHYSICAL_ADDRESS; 111 } 112 equals(int logical, int physical)113 public boolean equals(int logical, int physical) { 114 return logicalAddress == logical && physicalAddress == physical; 115 } 116 117 @Override equals(Object obj)118 public boolean equals(Object obj) { 119 if (obj instanceof ActiveSource) { 120 ActiveSource that = (ActiveSource) obj; 121 return that.logicalAddress == logicalAddress 122 && that.physicalAddress == physicalAddress; 123 } 124 return false; 125 } 126 127 @Override hashCode()128 public int hashCode() { 129 return logicalAddress * 29 + physicalAddress; 130 } 131 132 @Override toString()133 public String toString() { 134 StringBuilder s = new StringBuilder(); 135 String logicalAddressString = 136 (logicalAddress == Constants.ADDR_INVALID) 137 ? "invalid" 138 : String.format("0x%02x", logicalAddress); 139 s.append("(").append(logicalAddressString); 140 String physicalAddressString = 141 (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) 142 ? "invalid" 143 : String.format("0x%04x", physicalAddress); 144 s.append(", ").append(physicalAddressString).append(")"); 145 return s.toString(); 146 } 147 } 148 149 // Active routing path. Physical address of the active source but not all the time, such as 150 // when the new active source does not claim itself to be one. Note that we don't keep 151 // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}. 152 @GuardedBy("mLock") 153 private int mActiveRoutingPath; 154 155 protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 156 157 // A collection of FeatureAction. 158 // Note that access to this collection should happen in service thread. 159 @VisibleForTesting 160 final ArrayList<HdmiCecFeatureAction> mActions = new ArrayList<>(); 161 162 private final Handler mHandler = 163 new Handler() { 164 @Override 165 public void handleMessage(Message msg) { 166 switch (msg.what) { 167 case MSG_DISABLE_DEVICE_TIMEOUT: 168 handleDisableDeviceTimeout(); 169 break; 170 case MSG_USER_CONTROL_RELEASE_TIMEOUT: 171 handleUserControlReleased(); 172 break; 173 } 174 } 175 }; 176 177 /** 178 * A callback interface to get notified when all pending action is cleared. It can be called 179 * when timeout happened. 180 */ 181 interface PendingActionClearedCallback { onCleared(HdmiCecLocalDevice device)182 void onCleared(HdmiCecLocalDevice device); 183 } 184 185 protected PendingActionClearedCallback mPendingActionClearedCallback; 186 HdmiCecLocalDevice(HdmiControlService service, int deviceType)187 protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { 188 super(service, deviceType); 189 } 190 191 // Factory method that returns HdmiCecLocalDevice of corresponding type. create(HdmiControlService service, int deviceType)192 static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) { 193 switch (deviceType) { 194 case HdmiDeviceInfo.DEVICE_TV: 195 return new HdmiCecLocalDeviceTv(service); 196 case HdmiDeviceInfo.DEVICE_PLAYBACK: 197 return new HdmiCecLocalDevicePlayback(service); 198 case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: 199 return new HdmiCecLocalDeviceAudioSystem(service); 200 default: 201 return null; 202 } 203 } 204 205 @ServiceThreadOnly init()206 void init() { 207 assertRunOnServiceThread(); 208 mPreferredAddress = getPreferredAddress(); 209 if (mHandler.hasMessages(MSG_DISABLE_DEVICE_TIMEOUT)) { 210 // Remove and trigger the queued message for clearing all actions when going to standby. 211 // This is necessary because the device may wake up before the message is triggered. 212 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); 213 handleDisableDeviceTimeout(); 214 } 215 mPendingActionClearedCallback = null; 216 } 217 218 /** Called once a logical address of the local device is allocated. */ onAddressAllocated(int logicalAddress, int reason)219 protected abstract void onAddressAllocated(int logicalAddress, int reason); 220 221 /** Get the preferred logical address from system properties. */ getPreferredAddress()222 protected abstract int getPreferredAddress(); 223 224 /** Set the preferred logical address to system properties. */ setPreferredAddress(int addr)225 protected abstract void setPreferredAddress(int addr); 226 227 /** 228 * Returns true if the TV input associated with the CEC device is ready to accept further 229 * processing such as input switching. 230 * 231 * <p>This is used to buffer certain CEC commands and process it later if the input is not ready 232 * yet. For other types of local devices(non-TV), this method returns true by default to let the 233 * commands be processed right away. 234 */ isInputReady(int deviceId)235 protected boolean isInputReady(int deviceId) { 236 return true; 237 } 238 239 /** 240 * Returns true if the local device allows the system to be put to standby. 241 * 242 * <p>The default implementation returns true. 243 */ canGoToStandby()244 protected boolean canGoToStandby() { 245 return true; 246 } 247 248 /** 249 * Dispatch incoming message. 250 * 251 * @param message incoming message 252 * @return true if consumed a message; otherwise, return false. 253 */ 254 @ServiceThreadOnly 255 @VisibleForTesting 256 @Constants.HandleMessageResult dispatchMessage(HdmiCecMessage message)257 protected int dispatchMessage(HdmiCecMessage message) { 258 assertRunOnServiceThread(); 259 int dest = message.getDestination(); 260 if (dest != mDeviceInfo.getLogicalAddress() && dest != Constants.ADDR_BROADCAST) { 261 return Constants.NOT_HANDLED; 262 } 263 if (mService.isPowerStandby() 264 && !mService.isWakeUpMessageReceived() 265 && mStandbyHandler.handleCommand(message)) { 266 return Constants.HANDLED; 267 } 268 // Cache incoming message if it is included in the list of cacheable opcodes. 269 mCecMessageCache.cacheMessage(message); 270 return onMessage(message); 271 } 272 273 @ServiceThreadOnly 274 @VisibleForTesting isAlreadyActiveSource(HdmiDeviceInfo targetDevice, int targetAddress, IHdmiControlCallback callback)275 protected boolean isAlreadyActiveSource(HdmiDeviceInfo targetDevice, int targetAddress, 276 IHdmiControlCallback callback) { 277 ActiveSource active = getActiveSource(); 278 if (targetDevice.getDevicePowerStatus() == HdmiControlManager.POWER_STATUS_ON 279 && active.isValid() 280 && targetAddress == active.logicalAddress) { 281 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 282 return true; 283 } 284 return false; 285 } 286 287 // Clear all device info. 288 @ServiceThreadOnly clearDeviceInfoList()289 void clearDeviceInfoList() { 290 assertRunOnServiceThread(); 291 mService.getHdmiCecNetwork().clearDeviceList(); 292 } 293 294 @ServiceThreadOnly 295 @Constants.HandleMessageResult onMessage(HdmiCecMessage message)296 protected final int onMessage(HdmiCecMessage message) { 297 assertRunOnServiceThread(); 298 if (dispatchMessageToAction(message)) { 299 return Constants.HANDLED; 300 } 301 302 // If a message type has its own class, all valid messages of that type 303 // will be represented by an instance of that class. 304 if (message instanceof SetAudioVolumeLevelMessage) { 305 return handleSetAudioVolumeLevel((SetAudioVolumeLevelMessage) message); 306 } 307 308 switch (message.getOpcode()) { 309 case Constants.MESSAGE_ACTIVE_SOURCE: 310 return handleActiveSource(message); 311 case Constants.MESSAGE_INACTIVE_SOURCE: 312 return handleInactiveSource(message); 313 case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: 314 return handleRequestActiveSource(message); 315 case Constants.MESSAGE_GET_MENU_LANGUAGE: 316 return handleGetMenuLanguage(message); 317 case Constants.MESSAGE_SET_MENU_LANGUAGE: 318 return handleSetMenuLanguage(message); 319 case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS: 320 return handleGivePhysicalAddress(message); 321 case Constants.MESSAGE_GIVE_OSD_NAME: 322 return handleGiveOsdName(message); 323 case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID: 324 return handleGiveDeviceVendorId(message); 325 case Constants.MESSAGE_CEC_VERSION: 326 return handleCecVersion(); 327 case Constants.MESSAGE_GET_CEC_VERSION: 328 return handleGetCecVersion(message); 329 case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: 330 return handleReportPhysicalAddress(message); 331 case Constants.MESSAGE_ROUTING_CHANGE: 332 return handleRoutingChange(message); 333 case Constants.MESSAGE_ROUTING_INFORMATION: 334 return handleRoutingInformation(message); 335 case Constants.MESSAGE_REQUEST_ARC_INITIATION: 336 return handleRequestArcInitiate(message); 337 case Constants.MESSAGE_REQUEST_ARC_TERMINATION: 338 return handleRequestArcTermination(message); 339 case Constants.MESSAGE_INITIATE_ARC: 340 return handleInitiateArc(message); 341 case Constants.MESSAGE_TERMINATE_ARC: 342 return handleTerminateArc(message); 343 case Constants.MESSAGE_REPORT_ARC_INITIATED: 344 return handleReportArcInitiate(message); 345 case Constants.MESSAGE_REPORT_ARC_TERMINATED: 346 return handleReportArcTermination(message); 347 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST: 348 return handleSystemAudioModeRequest(message); 349 case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: 350 return handleSetSystemAudioMode(message); 351 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 352 return handleSystemAudioModeStatus(message); 353 case Constants.MESSAGE_GIVE_SYSTEM_AUDIO_MODE_STATUS: 354 return handleGiveSystemAudioModeStatus(message); 355 case Constants.MESSAGE_GIVE_AUDIO_STATUS: 356 return handleGiveAudioStatus(message); 357 case Constants.MESSAGE_REPORT_AUDIO_STATUS: 358 return handleReportAudioStatus(message); 359 case Constants.MESSAGE_STANDBY: 360 return handleStandby(message); 361 case Constants.MESSAGE_TEXT_VIEW_ON: 362 return handleTextViewOn(message); 363 case Constants.MESSAGE_IMAGE_VIEW_ON: 364 return handleImageViewOn(message); 365 case Constants.MESSAGE_USER_CONTROL_PRESSED: 366 return handleUserControlPressed(message); 367 case Constants.MESSAGE_USER_CONTROL_RELEASED: 368 return handleUserControlReleased(); 369 case Constants.MESSAGE_SET_STREAM_PATH: 370 return handleSetStreamPath(message); 371 case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: 372 return handleGiveDevicePowerStatus(message); 373 case Constants.MESSAGE_MENU_REQUEST: 374 return handleMenuRequest(message); 375 case Constants.MESSAGE_MENU_STATUS: 376 return handleMenuStatus(message); 377 case Constants.MESSAGE_VENDOR_COMMAND: 378 return handleVendorCommand(message); 379 case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID: 380 return handleVendorCommandWithId(message); 381 case Constants.MESSAGE_SET_OSD_NAME: 382 return handleSetOsdName(message); 383 case Constants.MESSAGE_RECORD_TV_SCREEN: 384 return handleRecordTvScreen(message); 385 case Constants.MESSAGE_TIMER_CLEARED_STATUS: 386 return handleTimerClearedStatus(message); 387 case Constants.MESSAGE_REPORT_POWER_STATUS: 388 return handleReportPowerStatus(message); 389 case Constants.MESSAGE_TIMER_STATUS: 390 return handleTimerStatus(message); 391 case Constants.MESSAGE_RECORD_STATUS: 392 return handleRecordStatus(message); 393 case Constants.MESSAGE_REQUEST_SHORT_AUDIO_DESCRIPTOR: 394 return handleRequestShortAudioDescriptor(message); 395 case Constants.MESSAGE_REPORT_SHORT_AUDIO_DESCRIPTOR: 396 return handleReportShortAudioDescriptor(message); 397 case Constants.MESSAGE_GIVE_FEATURES: 398 return handleGiveFeatures(message); 399 default: 400 return Constants.NOT_HANDLED; 401 } 402 } 403 404 @ServiceThreadOnly dispatchMessageToAction(HdmiCecMessage message)405 private boolean dispatchMessageToAction(HdmiCecMessage message) { 406 assertRunOnServiceThread(); 407 boolean processed = false; 408 // Use copied action list in that processCommand may remove itself. 409 for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) { 410 // Iterates all actions to check whether incoming message is consumed. 411 boolean result = action.processCommand(message); 412 processed = processed || result; 413 } 414 return processed; 415 } 416 417 @ServiceThreadOnly 418 @Constants.HandleMessageResult handleGivePhysicalAddress(HdmiCecMessage message)419 protected int handleGivePhysicalAddress(HdmiCecMessage message) { 420 assertRunOnServiceThread(); 421 int physicalAddress = mService.getPhysicalAddress(); 422 if (physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) { 423 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE); 424 } else { 425 HdmiCecMessage cecMessage = 426 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 427 mDeviceInfo.getLogicalAddress(), physicalAddress, mDeviceType); 428 mService.sendCecCommand(cecMessage); 429 } 430 return Constants.HANDLED; 431 } 432 433 @ServiceThreadOnly 434 @Constants.HandleMessageResult handleGiveDeviceVendorId(HdmiCecMessage message)435 protected int handleGiveDeviceVendorId(HdmiCecMessage message) { 436 assertRunOnServiceThread(); 437 int vendorId = mService.getVendorId(); 438 if (vendorId == Result.FAILURE_UNKNOWN) { 439 mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE); 440 } else { 441 HdmiCecMessage cecMessage = 442 HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 443 mDeviceInfo.getLogicalAddress(), vendorId); 444 mService.sendCecCommand(cecMessage); 445 } 446 return Constants.HANDLED; 447 } 448 449 @ServiceThreadOnly 450 @Constants.HandleMessageResult handleGetCecVersion(HdmiCecMessage message)451 protected int handleGetCecVersion(HdmiCecMessage message) { 452 assertRunOnServiceThread(); 453 int version = mService.getCecVersion(); 454 HdmiCecMessage cecMessage = 455 HdmiCecMessageBuilder.buildCecVersion( 456 message.getDestination(), message.getSource(), version); 457 mService.sendCecCommand(cecMessage); 458 return Constants.HANDLED; 459 } 460 461 @ServiceThreadOnly 462 @Constants.HandleMessageResult handleCecVersion()463 protected int handleCecVersion() { 464 assertRunOnServiceThread(); 465 466 // Return true to avoid <Feature Abort> responses. Cec Version is tracked in HdmiCecNetwork. 467 return Constants.HANDLED; 468 } 469 470 @ServiceThreadOnly 471 @Constants.HandleMessageResult handleActiveSource(HdmiCecMessage message)472 protected int handleActiveSource(HdmiCecMessage message) { 473 return Constants.NOT_HANDLED; 474 } 475 476 @ServiceThreadOnly 477 @Constants.HandleMessageResult handleInactiveSource(HdmiCecMessage message)478 protected int handleInactiveSource(HdmiCecMessage message) { 479 return Constants.NOT_HANDLED; 480 } 481 482 @ServiceThreadOnly 483 @Constants.HandleMessageResult handleRequestActiveSource(HdmiCecMessage message)484 protected int handleRequestActiveSource(HdmiCecMessage message) { 485 return Constants.NOT_HANDLED; 486 } 487 488 @ServiceThreadOnly 489 @Constants.HandleMessageResult handleGetMenuLanguage(HdmiCecMessage message)490 protected int handleGetMenuLanguage(HdmiCecMessage message) { 491 assertRunOnServiceThread(); 492 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 493 return Constants.NOT_HANDLED; 494 } 495 496 @ServiceThreadOnly 497 @Constants.HandleMessageResult handleSetMenuLanguage(HdmiCecMessage message)498 protected int handleSetMenuLanguage(HdmiCecMessage message) { 499 assertRunOnServiceThread(); 500 Slog.w(TAG, "Only Playback device can handle <Set Menu Language>:" + message.toString()); 501 return Constants.NOT_HANDLED; 502 } 503 504 @ServiceThreadOnly 505 @Constants.HandleMessageResult handleGiveOsdName(HdmiCecMessage message)506 protected int handleGiveOsdName(HdmiCecMessage message) { 507 assertRunOnServiceThread(); 508 // Note that since this method is called after logical address allocation is done, 509 // mDeviceInfo should not be null. 510 buildAndSendSetOsdName(message.getSource()); 511 return Constants.HANDLED; 512 } 513 buildAndSendSetOsdName(int dest)514 protected void buildAndSendSetOsdName(int dest) { 515 HdmiCecMessage cecMessage = 516 HdmiCecMessageBuilder.buildSetOsdNameCommand( 517 mDeviceInfo.getLogicalAddress(), dest, mDeviceInfo.getDisplayName()); 518 if (cecMessage != null) { 519 mService.sendCecCommand(cecMessage, new SendMessageCallback() { 520 @Override 521 public void onSendCompleted(int error) { 522 if (error != SendMessageResult.SUCCESS) { 523 HdmiLogger.debug("Failed to send cec command " + cecMessage); 524 } 525 } 526 }); 527 } else { 528 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 529 } 530 } 531 532 // Audio System device with no Playback device type 533 // needs to refactor this function if it's also a switch 534 @Constants.HandleMessageResult handleRoutingChange(HdmiCecMessage message)535 protected int handleRoutingChange(HdmiCecMessage message) { 536 return Constants.NOT_HANDLED; 537 } 538 539 // Audio System device with no Playback device type 540 // needs to refactor this function if it's also a switch 541 @Constants.HandleMessageResult handleRoutingInformation(HdmiCecMessage message)542 protected int handleRoutingInformation(HdmiCecMessage message) { 543 return Constants.NOT_HANDLED; 544 } 545 546 @CallSuper 547 @Constants.HandleMessageResult handleReportPhysicalAddress(HdmiCecMessage message)548 protected int handleReportPhysicalAddress(HdmiCecMessage message) { 549 // <Report Physical Address> is also handled in HdmiCecNetwork to update the local network 550 // state 551 552 int address = message.getSource(); 553 554 // Ignore if [Device Discovery Action] is going on. 555 if (hasAction(DeviceDiscoveryAction.class)) { 556 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); 557 return Constants.HANDLED; 558 } 559 560 HdmiDeviceInfo cecDeviceInfo = mService.getHdmiCecNetwork().getCecDeviceInfo(address); 561 // If no non-default display name is available for the device, request the devices OSD name. 562 if (cecDeviceInfo != null && cecDeviceInfo.getDisplayName().equals( 563 HdmiUtils.getDefaultDeviceName(address))) { 564 mService.sendCecCommand( 565 HdmiCecMessageBuilder.buildGiveOsdNameCommand( 566 mDeviceInfo.getLogicalAddress(), address)); 567 } 568 569 return Constants.HANDLED; 570 } 571 572 @Constants.HandleMessageResult handleSystemAudioModeStatus(HdmiCecMessage message)573 protected int handleSystemAudioModeStatus(HdmiCecMessage message) { 574 return Constants.NOT_HANDLED; 575 } 576 577 @Constants.HandleMessageResult handleGiveSystemAudioModeStatus(HdmiCecMessage message)578 protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { 579 return Constants.NOT_HANDLED; 580 } 581 582 @Constants.HandleMessageResult handleSetSystemAudioMode(HdmiCecMessage message)583 protected int handleSetSystemAudioMode(HdmiCecMessage message) { 584 return Constants.NOT_HANDLED; 585 } 586 587 @Constants.HandleMessageResult handleSystemAudioModeRequest(HdmiCecMessage message)588 protected int handleSystemAudioModeRequest(HdmiCecMessage message) { 589 return Constants.NOT_HANDLED; 590 } 591 592 @Constants.HandleMessageResult handleTerminateArc(HdmiCecMessage message)593 protected int handleTerminateArc(HdmiCecMessage message) { 594 return Constants.NOT_HANDLED; 595 } 596 597 @Constants.HandleMessageResult handleInitiateArc(HdmiCecMessage message)598 protected int handleInitiateArc(HdmiCecMessage message) { 599 return Constants.NOT_HANDLED; 600 } 601 602 @Constants.HandleMessageResult handleRequestArcInitiate(HdmiCecMessage message)603 protected int handleRequestArcInitiate(HdmiCecMessage message) { 604 return Constants.NOT_HANDLED; 605 } 606 607 @Constants.HandleMessageResult handleRequestArcTermination(HdmiCecMessage message)608 protected int handleRequestArcTermination(HdmiCecMessage message) { 609 return Constants.NOT_HANDLED; 610 } 611 612 @Constants.HandleMessageResult handleReportArcInitiate(HdmiCecMessage message)613 protected int handleReportArcInitiate(HdmiCecMessage message) { 614 return Constants.NOT_HANDLED; 615 } 616 617 @Constants.HandleMessageResult handleReportArcTermination(HdmiCecMessage message)618 protected int handleReportArcTermination(HdmiCecMessage message) { 619 return Constants.NOT_HANDLED; 620 } 621 622 @Constants.HandleMessageResult handleReportAudioStatus(HdmiCecMessage message)623 protected int handleReportAudioStatus(HdmiCecMessage message) { 624 return Constants.NOT_HANDLED; 625 } 626 627 @Constants.HandleMessageResult handleGiveAudioStatus(HdmiCecMessage message)628 protected int handleGiveAudioStatus(HdmiCecMessage message) { 629 return Constants.NOT_HANDLED; 630 } 631 632 @Constants.HandleMessageResult handleRequestShortAudioDescriptor(HdmiCecMessage message)633 protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { 634 return Constants.NOT_HANDLED; 635 } 636 637 @Constants.HandleMessageResult handleReportShortAudioDescriptor(HdmiCecMessage message)638 protected int handleReportShortAudioDescriptor(HdmiCecMessage message) { 639 return Constants.NOT_HANDLED; 640 } 641 642 @Constants.HandleMessageResult handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)643 protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) { 644 return Constants.NOT_HANDLED; 645 } 646 647 /** 648 * Called after logical address allocation is finished, allowing a local device to react to 649 * messages in the buffer before they are processed. This method may be used to cancel deferred 650 * actions. 651 */ preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages)652 protected void preprocessBufferedMessages(List<HdmiCecMessage> bufferedMessages) {} 653 654 @Constants.RcProfile getRcProfile()655 protected abstract int getRcProfile(); 656 getRcFeatures()657 protected abstract List<Integer> getRcFeatures(); 658 659 /** 660 * Computes the set of supported device features. To update local state with changes in 661 * the set of supported device features, use {@link #getDeviceFeatures} instead. 662 */ computeDeviceFeatures()663 protected DeviceFeatures computeDeviceFeatures() { 664 return DeviceFeatures.NO_FEATURES_SUPPORTED; 665 } 666 667 /** 668 * Computes the set of supported device features, and updates local state to match. 669 */ updateDeviceFeatures()670 private void updateDeviceFeatures() { 671 setDeviceInfo(getDeviceInfo().toBuilder() 672 .setDeviceFeatures(computeDeviceFeatures()) 673 .build()); 674 } 675 676 /** 677 * Computes and returns the set of supported device features. Updates local state to match. 678 */ getDeviceFeatures()679 protected final DeviceFeatures getDeviceFeatures() { 680 updateDeviceFeatures(); 681 return getDeviceInfo().getDeviceFeatures(); 682 } 683 684 @Constants.HandleMessageResult handleGiveFeatures(HdmiCecMessage message)685 protected int handleGiveFeatures(HdmiCecMessage message) { 686 if (mService.getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) { 687 return Constants.ABORT_UNRECOGNIZED_OPCODE; 688 } 689 690 reportFeatures(); 691 return Constants.HANDLED; 692 } 693 reportFeatures()694 protected void reportFeatures() { 695 List<Integer> localDeviceTypes = new ArrayList<>(); 696 for (HdmiCecLocalDevice localDevice : mService.getAllCecLocalDevices()) { 697 localDeviceTypes.add(localDevice.mDeviceType); 698 } 699 700 701 int rcProfile = getRcProfile(); 702 List<Integer> rcFeatures = getRcFeatures(); 703 DeviceFeatures deviceFeatures = getDeviceFeatures(); 704 705 706 int logicalAddress; 707 synchronized (mLock) { 708 logicalAddress = mDeviceInfo.getLogicalAddress(); 709 } 710 711 mService.sendCecCommand( 712 ReportFeaturesMessage.build( 713 logicalAddress, 714 mService.getCecVersion(), 715 localDeviceTypes, 716 rcProfile, 717 rcFeatures, 718 deviceFeatures)); 719 } 720 721 @ServiceThreadOnly 722 @Constants.HandleMessageResult handleStandby(HdmiCecMessage message)723 protected int handleStandby(HdmiCecMessage message) { 724 assertRunOnServiceThread(); 725 // Seq #12 726 if (mService.isCecControlEnabled() 727 && !mService.isProhibitMode() 728 && mService.isPowerOnOrTransient()) { 729 mService.standby(); 730 return Constants.HANDLED; 731 } 732 return Constants.ABORT_NOT_IN_CORRECT_MODE; 733 } 734 735 @ServiceThreadOnly 736 @Constants.HandleMessageResult handleUserControlPressed(HdmiCecMessage message)737 protected int handleUserControlPressed(HdmiCecMessage message) { 738 assertRunOnServiceThread(); 739 mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); 740 if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { 741 mService.standby(); 742 return Constants.HANDLED; 743 } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { 744 mService.wakeUp(); 745 return Constants.HANDLED; 746 } else if (mService.getHdmiCecVolumeControl() 747 == HdmiControlManager.VOLUME_CONTROL_DISABLED && isVolumeOrMuteCommand( 748 message)) { 749 return Constants.ABORT_REFUSED; 750 } 751 752 if (isPowerOffOrToggleCommand(message) || isPowerOnOrToggleCommand(message)) { 753 // Power commands should already be handled above. Don't continue and convert the CEC 754 // keycode to Android keycode. 755 // Do not <Feature Abort> as the local device should already be in the correct power 756 // state. 757 return Constants.HANDLED; 758 } 759 760 final long downTime = SystemClock.uptimeMillis(); 761 final byte[] params = message.getParams(); 762 final int keycode = HdmiCecKeycode.cecKeycodeAndParamsToAndroidKey(params); 763 int keyRepeatCount = 0; 764 if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 765 if (keycode == mLastKeycode) { 766 keyRepeatCount = mLastKeyRepeatCount + 1; 767 } else { 768 injectKeyEvent(downTime, KeyEvent.ACTION_UP, mLastKeycode, 0); 769 } 770 } 771 mLastKeycode = keycode; 772 mLastKeyRepeatCount = keyRepeatCount; 773 774 if (keycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 775 injectKeyEvent(downTime, KeyEvent.ACTION_DOWN, keycode, keyRepeatCount); 776 mHandler.sendMessageDelayed( 777 Message.obtain(mHandler, MSG_USER_CONTROL_RELEASE_TIMEOUT), 778 FOLLOWER_SAFETY_TIMEOUT); 779 return Constants.HANDLED; 780 } else if (params.length > 0) { 781 // Handle CEC UI commands that are not mapped to an Android keycode 782 return handleUnmappedCecKeycode(params[0]); 783 } 784 785 return Constants.ABORT_INVALID_OPERAND; 786 } 787 788 @ServiceThreadOnly 789 @Constants.HandleMessageResult handleUnmappedCecKeycode(int cecKeycode)790 protected int handleUnmappedCecKeycode(int cecKeycode) { 791 if (cecKeycode == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION) { 792 mService.getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, 793 AudioManager.ADJUST_MUTE, AudioManager.FLAG_SHOW_UI); 794 return Constants.HANDLED; 795 } else if (cecKeycode == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION) { 796 mService.getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, 797 AudioManager.ADJUST_UNMUTE, AudioManager.FLAG_SHOW_UI); 798 return Constants.HANDLED; 799 } 800 return Constants.ABORT_INVALID_OPERAND; 801 } 802 803 @ServiceThreadOnly 804 @Constants.HandleMessageResult handleUserControlReleased()805 protected int handleUserControlReleased() { 806 assertRunOnServiceThread(); 807 mHandler.removeMessages(MSG_USER_CONTROL_RELEASE_TIMEOUT); 808 mLastKeyRepeatCount = 0; 809 if (mLastKeycode != HdmiCecKeycode.UNSUPPORTED_KEYCODE) { 810 final long upTime = SystemClock.uptimeMillis(); 811 injectKeyEvent(upTime, KeyEvent.ACTION_UP, mLastKeycode, 0); 812 mLastKeycode = HdmiCecKeycode.UNSUPPORTED_KEYCODE; 813 } 814 return Constants.HANDLED; 815 } 816 injectKeyEvent(long time, int action, int keycode, int repeat)817 static void injectKeyEvent(long time, int action, int keycode, int repeat) { 818 KeyEvent keyEvent = 819 KeyEvent.obtain( 820 time, 821 time, 822 action, 823 keycode, 824 repeat, 825 0, 826 KeyCharacterMap.VIRTUAL_KEYBOARD, 827 0, 828 KeyEvent.FLAG_FROM_SYSTEM, 829 InputDevice.SOURCE_HDMI, 830 null); 831 InputManagerGlobal.getInstance() 832 .injectInputEvent(keyEvent, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 833 keyEvent.recycle(); 834 } 835 isPowerOnOrToggleCommand(HdmiCecMessage message)836 static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { 837 byte[] params = message.getParams(); 838 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 839 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 840 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION 841 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 842 } 843 isPowerOffOrToggleCommand(HdmiCecMessage message)844 static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { 845 byte[] params = message.getParams(); 846 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 847 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION 848 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 849 } 850 isVolumeOrMuteCommand(HdmiCecMessage message)851 static boolean isVolumeOrMuteCommand(HdmiCecMessage message) { 852 byte[] params = message.getParams(); 853 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 854 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_DOWN 855 || params[0] == HdmiCecKeycode.CEC_KEYCODE_VOLUME_UP 856 || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE 857 || params[0] == HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION 858 || params[0] == HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); 859 } 860 861 @Constants.HandleMessageResult handleTextViewOn(HdmiCecMessage message)862 protected int handleTextViewOn(HdmiCecMessage message) { 863 return Constants.NOT_HANDLED; 864 } 865 866 @Constants.HandleMessageResult handleImageViewOn(HdmiCecMessage message)867 protected int handleImageViewOn(HdmiCecMessage message) { 868 return Constants.NOT_HANDLED; 869 } 870 871 @Constants.HandleMessageResult handleSetStreamPath(HdmiCecMessage message)872 protected int handleSetStreamPath(HdmiCecMessage message) { 873 return Constants.NOT_HANDLED; 874 } 875 876 @Constants.HandleMessageResult handleGiveDevicePowerStatus(HdmiCecMessage message)877 protected int handleGiveDevicePowerStatus(HdmiCecMessage message) { 878 mService.sendCecCommand( 879 HdmiCecMessageBuilder.buildReportPowerStatus( 880 mDeviceInfo.getLogicalAddress(), 881 message.getSource(), 882 mService.getPowerStatus())); 883 return Constants.HANDLED; 884 } 885 886 @Constants.HandleMessageResult handleMenuRequest(HdmiCecMessage message)887 protected int handleMenuRequest(HdmiCecMessage message) { 888 // Always report menu active to receive Remote Control. 889 mService.sendCecCommand( 890 HdmiCecMessageBuilder.buildReportMenuStatus( 891 mDeviceInfo.getLogicalAddress(), 892 message.getSource(), 893 Constants.MENU_STATE_ACTIVATED)); 894 return Constants.HANDLED; 895 } 896 897 @Constants.HandleMessageResult handleMenuStatus(HdmiCecMessage message)898 protected int handleMenuStatus(HdmiCecMessage message) { 899 return Constants.NOT_HANDLED; 900 } 901 902 @Constants.HandleMessageResult handleVendorCommand(HdmiCecMessage message)903 protected int handleVendorCommand(HdmiCecMessage message) { 904 if (!mService.invokeVendorCommandListenersOnReceived( 905 mDeviceType, 906 message.getSource(), 907 message.getDestination(), 908 message.getParams(), 909 false)) { 910 // Vendor command listener may not have been registered yet. Respond with 911 // <Feature Abort> [Refused] so that the sender can try again later. 912 return Constants.ABORT_REFUSED; 913 } 914 return Constants.HANDLED; 915 } 916 917 @Constants.HandleMessageResult handleVendorCommandWithId(HdmiCecMessage message)918 protected int handleVendorCommandWithId(HdmiCecMessage message) { 919 byte[] params = message.getParams(); 920 int vendorId = HdmiUtils.threeBytesToInt(params); 921 if (message.getDestination() == Constants.ADDR_BROADCAST 922 || message.getSource() == Constants.ADDR_UNREGISTERED) { 923 Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); 924 } else if (!mService.invokeVendorCommandListenersOnReceived( 925 mDeviceType, message.getSource(), message.getDestination(), params, true)) { 926 return Constants.ABORT_REFUSED; 927 } 928 return Constants.HANDLED; 929 } 930 sendStandby(int deviceId)931 protected void sendStandby(int deviceId) { 932 // Do nothing. 933 } 934 935 @Constants.HandleMessageResult handleSetOsdName(HdmiCecMessage message)936 protected int handleSetOsdName(HdmiCecMessage message) { 937 // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state 938 return Constants.HANDLED; 939 } 940 941 @Constants.HandleMessageResult handleRecordTvScreen(HdmiCecMessage message)942 protected int handleRecordTvScreen(HdmiCecMessage message) { 943 return Constants.NOT_HANDLED; 944 } 945 946 @Constants.HandleMessageResult handleTimerClearedStatus(HdmiCecMessage message)947 protected int handleTimerClearedStatus(HdmiCecMessage message) { 948 return Constants.NOT_HANDLED; 949 } 950 951 @Constants.HandleMessageResult handleReportPowerStatus(HdmiCecMessage message)952 protected int handleReportPowerStatus(HdmiCecMessage message) { 953 // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state 954 return Constants.HANDLED; 955 } 956 957 @Constants.HandleMessageResult handleTimerStatus(HdmiCecMessage message)958 protected int handleTimerStatus(HdmiCecMessage message) { 959 return Constants.NOT_HANDLED; 960 } 961 962 @Constants.HandleMessageResult handleRecordStatus(HdmiCecMessage message)963 protected int handleRecordStatus(HdmiCecMessage message) { 964 return Constants.NOT_HANDLED; 965 } 966 967 @ServiceThreadOnly handleAddressAllocated( int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason)968 final void handleAddressAllocated( 969 int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) { 970 assertRunOnServiceThread(); 971 preprocessBufferedMessages(bufferedMessages); 972 mPreferredAddress = logicalAddress; 973 updateDeviceFeatures(); 974 if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { 975 reportFeatures(); 976 } 977 onAddressAllocated(logicalAddress, reason); 978 setPreferredAddress(logicalAddress); 979 } 980 getType()981 int getType() { 982 return mDeviceType; 983 } 984 getDeviceInfo()985 HdmiDeviceInfo getDeviceInfo() { 986 synchronized (mLock) { 987 return mDeviceInfo; 988 } 989 } 990 setDeviceInfo(HdmiDeviceInfo info)991 void setDeviceInfo(HdmiDeviceInfo info) { 992 synchronized (mLock) { 993 mDeviceInfo = info; 994 } 995 } 996 997 // Returns true if the logical address is same as the argument. 998 @ServiceThreadOnly isAddressOf(int addr)999 boolean isAddressOf(int addr) { 1000 assertRunOnServiceThread(); 1001 return addr == mDeviceInfo.getLogicalAddress(); 1002 } 1003 1004 @ServiceThreadOnly addAndStartAction(final HdmiCecFeatureAction action)1005 void addAndStartAction(final HdmiCecFeatureAction action) { 1006 assertRunOnServiceThread(); 1007 mActions.add(action); 1008 if (mService.isPowerStandby() || !mService.isAddressAllocated()) { 1009 Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action); 1010 return; 1011 } 1012 action.start(); 1013 } 1014 1015 @ServiceThreadOnly startNewAvbAudioStatusAction(int targetAddress)1016 void startNewAvbAudioStatusAction(int targetAddress) { 1017 assertRunOnServiceThread(); 1018 removeAction(AbsoluteVolumeAudioStatusAction.class); 1019 addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress)); 1020 } 1021 1022 @ServiceThreadOnly removeAvbAudioStatusAction()1023 void removeAvbAudioStatusAction() { 1024 assertRunOnServiceThread(); 1025 removeAction(AbsoluteVolumeAudioStatusAction.class); 1026 } 1027 1028 @ServiceThreadOnly updateAvbVolume(int volumeIndex)1029 void updateAvbVolume(int volumeIndex) { 1030 assertRunOnServiceThread(); 1031 for (AbsoluteVolumeAudioStatusAction action : 1032 getActions(AbsoluteVolumeAudioStatusAction.class)) { 1033 action.updateVolume(volumeIndex); 1034 } 1035 } 1036 1037 /** 1038 * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things 1039 * in parallel: send <Give Features> (to get <Report Features> in response), 1040 * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response). 1041 */ 1042 @ServiceThreadOnly querySetAudioVolumeLevelSupport(int targetAddress)1043 void querySetAudioVolumeLevelSupport(int targetAddress) { 1044 assertRunOnServiceThread(); 1045 1046 // Send <Give Features> if using CEC 2.0 or above. 1047 if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { 1048 mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures( 1049 getDeviceInfo().getLogicalAddress(), targetAddress)); 1050 } 1051 1052 // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target 1053 // device, start one. 1054 List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions = 1055 getActions(SetAudioVolumeLevelDiscoveryAction.class); 1056 if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) { 1057 addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress, 1058 new IHdmiControlCallback.Stub() { 1059 @Override 1060 public void onComplete(int result) { 1061 if (result == HdmiControlManager.RESULT_SUCCESS) { 1062 getService().checkAndUpdateAbsoluteVolumeBehavior(); 1063 } 1064 } 1065 })); 1066 } 1067 } 1068 1069 @ServiceThreadOnly startQueuedActions()1070 void startQueuedActions() { 1071 assertRunOnServiceThread(); 1072 // Use copied action list in that start() may remove itself. 1073 for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) { 1074 if (!action.started()) { 1075 Slog.i(TAG, "Starting queued action:" + action); 1076 action.start(); 1077 } 1078 } 1079 } 1080 1081 // See if we have an action of a given type in progress. 1082 @ServiceThreadOnly hasAction(final Class<T> clazz)1083 <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) { 1084 assertRunOnServiceThread(); 1085 for (HdmiCecFeatureAction action : mActions) { 1086 if (action.getClass().equals(clazz)) { 1087 return true; 1088 } 1089 } 1090 return false; 1091 } 1092 1093 // Returns all actions matched with given class type. 1094 @ServiceThreadOnly getActions(final Class<T> clazz)1095 <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) { 1096 assertRunOnServiceThread(); 1097 List<T> actions = Collections.<T>emptyList(); 1098 for (HdmiCecFeatureAction action : mActions) { 1099 if (action.getClass().equals(clazz)) { 1100 if (actions.isEmpty()) { 1101 actions = new ArrayList<T>(); 1102 } 1103 actions.add((T) action); 1104 } 1105 } 1106 return actions; 1107 } 1108 1109 /** 1110 * Remove the given {@link HdmiCecFeatureAction} object from the action queue. 1111 * 1112 * @param action {@link HdmiCecFeatureAction} to remove 1113 */ 1114 @ServiceThreadOnly removeAction(final HdmiCecFeatureAction action)1115 void removeAction(final HdmiCecFeatureAction action) { 1116 assertRunOnServiceThread(); 1117 action.finish(false); 1118 mActions.remove(action); 1119 checkIfPendingActionsCleared(); 1120 } 1121 1122 // Remove all actions matched with the given Class type. 1123 @ServiceThreadOnly removeAction(final Class<T> clazz)1124 <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) { 1125 assertRunOnServiceThread(); 1126 removeActionExcept(clazz, null); 1127 } 1128 1129 // Remove all actions matched with the given Class type besides |exception|. 1130 @ServiceThreadOnly removeActionExcept( final Class<T> clazz, final HdmiCecFeatureAction exception)1131 <T extends HdmiCecFeatureAction> void removeActionExcept( 1132 final Class<T> clazz, final HdmiCecFeatureAction exception) { 1133 assertRunOnServiceThread(); 1134 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 1135 while (iter.hasNext()) { 1136 HdmiCecFeatureAction action = iter.next(); 1137 if (action != exception && action.getClass().equals(clazz)) { 1138 action.finish(false); 1139 iter.remove(); 1140 } 1141 } 1142 checkIfPendingActionsCleared(); 1143 } 1144 checkIfPendingActionsCleared()1145 protected void checkIfPendingActionsCleared() { 1146 if (mActions.isEmpty() && mPendingActionClearedCallback != null) { 1147 PendingActionClearedCallback callback = mPendingActionClearedCallback; 1148 // To prevent from calling the callback again during handling the callback itself. 1149 mPendingActionClearedCallback = null; 1150 callback.onCleared(this); 1151 } 1152 } 1153 assertRunOnServiceThread()1154 protected void assertRunOnServiceThread() { 1155 if (Looper.myLooper() != mService.getServiceLooper()) { 1156 throw new IllegalStateException("Should run on service thread."); 1157 } 1158 } 1159 1160 /** 1161 * Called when a hot-plug event issued. 1162 * 1163 * @param portId id of port where a hot-plug event happened 1164 * @param connected whether to connected or not on the event 1165 */ onHotplug(int portId, boolean connected)1166 void onHotplug(int portId, boolean connected) {} 1167 getService()1168 final HdmiControlService getService() { 1169 return mService; 1170 } 1171 1172 @ServiceThreadOnly isConnectedToArcPort(int path)1173 final boolean isConnectedToArcPort(int path) { 1174 assertRunOnServiceThread(); 1175 return mService.isConnectedToArcPort(path); 1176 } 1177 getActiveSource()1178 ActiveSource getActiveSource() { 1179 return mService.getLocalActiveSource(); 1180 } 1181 setActiveSource(ActiveSource newActive, String caller)1182 void setActiveSource(ActiveSource newActive, String caller) { 1183 setActiveSource(newActive.logicalAddress, newActive.physicalAddress, caller); 1184 } 1185 setActiveSource(HdmiDeviceInfo info, String caller)1186 void setActiveSource(HdmiDeviceInfo info, String caller) { 1187 setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress(), caller); 1188 } 1189 setActiveSource(int logicalAddress, int physicalAddress, String caller)1190 void setActiveSource(int logicalAddress, int physicalAddress, String caller) { 1191 mService.setActiveSource(logicalAddress, physicalAddress, caller); 1192 mService.setLastInputForMhl(Constants.INVALID_PORT_ID); 1193 } 1194 getActivePath()1195 int getActivePath() { 1196 synchronized (mLock) { 1197 return mActiveRoutingPath; 1198 } 1199 } 1200 setActivePath(int path)1201 void setActivePath(int path) { 1202 synchronized (mLock) { 1203 mActiveRoutingPath = path; 1204 } 1205 mService.setActivePortId(pathToPortId(path)); 1206 } 1207 1208 /** 1209 * Returns the ID of the active HDMI port. The active port is the one that has the active 1210 * routing path connected to it directly or indirectly under the device hierarchy. 1211 */ getActivePortId()1212 int getActivePortId() { 1213 synchronized (mLock) { 1214 return mService.pathToPortId(mActiveRoutingPath); 1215 } 1216 } 1217 1218 /** 1219 * Update the active port. 1220 * 1221 * @param portId the new active port id 1222 */ setActivePortId(int portId)1223 void setActivePortId(int portId) { 1224 // We update active routing path instead, since we get the active port id from 1225 // the active routing path. 1226 setActivePath(mService.portIdToPath(portId)); 1227 } 1228 1229 // Returns the id of the port that the target device is connected to. getPortId(int physicalAddress)1230 int getPortId(int physicalAddress) { 1231 return mService.pathToPortId(physicalAddress); 1232 } 1233 1234 @ServiceThreadOnly getCecMessageCache()1235 HdmiCecMessageCache getCecMessageCache() { 1236 assertRunOnServiceThread(); 1237 return mCecMessageCache; 1238 } 1239 1240 @ServiceThreadOnly pathToPortId(int newPath)1241 int pathToPortId(int newPath) { 1242 assertRunOnServiceThread(); 1243 return mService.pathToPortId(newPath); 1244 } 1245 1246 /** 1247 * Called when the system goes to standby mode. 1248 * 1249 * @param initiatedByCec true if this power sequence is initiated by the reception the CEC 1250 * messages like <Standby> 1251 * @param standbyAction Intent action that drives the standby process, either {@link 1252 * HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN} 1253 */ onStandby(boolean initiatedByCec, int standbyAction)1254 protected void onStandby(boolean initiatedByCec, int standbyAction) {} 1255 1256 /** 1257 * Called when the initialization of local devices is complete. 1258 */ onInitializeCecComplete(int initiatedBy)1259 protected void onInitializeCecComplete(int initiatedBy) {} 1260 1261 /** 1262 * Disable device. {@code callback} is used to get notified when all pending actions are 1263 * completed or timeout is issued. 1264 * 1265 * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages 1266 * like <Standby> 1267 * @param originalCallback callback interface to get notified when all pending actions are 1268 * cleared 1269 */ disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback)1270 protected void disableDevice( 1271 boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { 1272 removeAction(AbsoluteVolumeAudioStatusAction.class); 1273 removeAction(SetAudioVolumeLevelDiscoveryAction.class); 1274 removeAction(ActiveSourceAction.class); 1275 1276 mPendingActionClearedCallback = 1277 new PendingActionClearedCallback() { 1278 @Override 1279 public void onCleared(HdmiCecLocalDevice device) { 1280 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); 1281 originalCallback.onCleared(device); 1282 } 1283 }; 1284 mHandler.sendMessageDelayed( 1285 Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), DEVICE_CLEANUP_TIMEOUT); 1286 } 1287 1288 @ServiceThreadOnly handleDisableDeviceTimeout()1289 private void handleDisableDeviceTimeout() { 1290 assertRunOnServiceThread(); 1291 1292 // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. 1293 // onCleard will be called at the last action's finish method. 1294 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 1295 while (iter.hasNext()) { 1296 HdmiCecFeatureAction action = iter.next(); 1297 action.finish(false); 1298 iter.remove(); 1299 } 1300 if (mPendingActionClearedCallback != null) { 1301 mPendingActionClearedCallback.onCleared(this); 1302 } 1303 } 1304 1305 /** 1306 * Send a key event to other CEC device. The logical address of target device will be given by 1307 * {@link #findKeyReceiverAddress}. 1308 * 1309 * @param keyCode key code defined in {@link android.view.KeyEvent} 1310 * @param isPressed {@code true} for key down event 1311 * @see #findKeyReceiverAddress() 1312 */ 1313 @ServiceThreadOnly sendKeyEvent(int keyCode, boolean isPressed)1314 protected void sendKeyEvent(int keyCode, boolean isPressed) { 1315 assertRunOnServiceThread(); 1316 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { 1317 Slog.w(TAG, "Unsupported key: " + keyCode); 1318 return; 1319 } 1320 List<SendKeyAction> action = getActions(SendKeyAction.class); 1321 int logicalAddress = findKeyReceiverAddress(); 1322 if (logicalAddress == Constants.ADDR_INVALID 1323 || logicalAddress == mDeviceInfo.getLogicalAddress()) { 1324 // Don't send key event to invalid device or itself. 1325 Slog.w( 1326 TAG, 1327 "Discard key event: " 1328 + keyCode 1329 + ", pressed:" 1330 + isPressed 1331 + ", receiverAddr=" 1332 + logicalAddress); 1333 } else if (!action.isEmpty()) { 1334 action.get(0).processKeyEvent(keyCode, isPressed); 1335 } else if (isPressed) { 1336 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 1337 } 1338 } 1339 1340 /** 1341 * Send a volume key event to other CEC device. The logical address of target device will be 1342 * given by {@link #findAudioReceiverAddress()}. 1343 * 1344 * @param keyCode key code defined in {@link android.view.KeyEvent} 1345 * @param isPressed {@code true} for key down event 1346 * @see #findAudioReceiverAddress() 1347 */ 1348 @ServiceThreadOnly sendVolumeKeyEvent(int keyCode, boolean isPressed)1349 protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) { 1350 assertRunOnServiceThread(); 1351 if (mService.getHdmiCecVolumeControl() 1352 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 1353 return; 1354 } 1355 if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) { 1356 Slog.w(TAG, "Not a volume key: " + keyCode); 1357 return; 1358 } 1359 List<SendKeyAction> action = getActions(SendKeyAction.class); 1360 int logicalAddress = findAudioReceiverAddress(); 1361 if (logicalAddress == Constants.ADDR_INVALID 1362 || mService.getAllCecLocalDevices().stream().anyMatch( 1363 device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) { 1364 // Don't send key event to invalid device or itself. 1365 Slog.w( 1366 TAG, 1367 "Discard volume key event: " 1368 + keyCode 1369 + ", pressed:" 1370 + isPressed 1371 + ", receiverAddr=" 1372 + logicalAddress); 1373 } else if (!action.isEmpty()) { 1374 action.get(0).processKeyEvent(keyCode, isPressed); 1375 } else if (isPressed) { 1376 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 1377 } 1378 } 1379 1380 /** 1381 * Returns the logical address of the device which will receive key events via {@link 1382 * #sendKeyEvent}. 1383 * 1384 * @see #sendKeyEvent(int, boolean) 1385 */ findKeyReceiverAddress()1386 protected int findKeyReceiverAddress() { 1387 Slog.w(TAG, "findKeyReceiverAddress is not implemented"); 1388 return Constants.ADDR_INVALID; 1389 } 1390 1391 /** 1392 * Returns the logical address of the audio receiver device which will receive volume key events 1393 * via {@link#sendVolumeKeyEvent}. 1394 * 1395 * @see #sendVolumeKeyEvent(int, boolean) 1396 */ findAudioReceiverAddress()1397 protected int findAudioReceiverAddress() { 1398 Slog.w(TAG, "findAudioReceiverAddress is not implemented"); 1399 return Constants.ADDR_INVALID; 1400 } 1401 1402 @ServiceThreadOnly invokeCallback(IHdmiControlCallback callback, int result)1403 void invokeCallback(IHdmiControlCallback callback, int result) { 1404 assertRunOnServiceThread(); 1405 if (callback == null) { 1406 return; 1407 } 1408 try { 1409 callback.onComplete(result); 1410 } catch (RemoteException e) { 1411 Slog.e(TAG, "Invoking callback failed:" + e); 1412 } 1413 } 1414 sendUserControlPressedAndReleased(int targetAddress, int cecKeycode)1415 void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { 1416 mService.sendCecCommand( 1417 HdmiCecMessageBuilder.buildUserControlPressed( 1418 mDeviceInfo.getLogicalAddress(), targetAddress, cecKeycode)); 1419 mService.sendCecCommand( 1420 HdmiCecMessageBuilder.buildUserControlReleased( 1421 mDeviceInfo.getLogicalAddress(), targetAddress)); 1422 } 1423 addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, String caller)1424 void addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, 1425 String caller) { 1426 ActiveSourceHistoryRecord record = new ActiveSourceHistoryRecord(activeSource, 1427 isActiveSource, caller); 1428 if (!mActiveSourceHistory.offer(record)) { 1429 mActiveSourceHistory.poll(); 1430 mActiveSourceHistory.offer(record); 1431 } 1432 } 1433 getActiveSourceHistory()1434 public ArrayBlockingQueue<HdmiCecController.Dumpable> getActiveSourceHistory() { 1435 return this.mActiveSourceHistory; 1436 } 1437 1438 /** Dump internal status of HdmiCecLocalDevice object. */ dump(final IndentingPrintWriter pw)1439 protected void dump(final IndentingPrintWriter pw) { 1440 pw.println("mDeviceType: " + mDeviceType); 1441 pw.println("mPreferredAddress: " + mPreferredAddress); 1442 pw.println("mDeviceInfo: " + mDeviceInfo); 1443 pw.println("mActiveSource: " + getActiveSource()); 1444 pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); 1445 } 1446 1447 /** Calculates the physical address for {@code activePortId}. 1448 * 1449 * <p>This method assumes current device physical address is valid. 1450 * <p>If the current device is already the leaf of the whole CEC system 1451 * and can't have devices under it, will return its own physical address. 1452 * 1453 * @param activePortId is the local active port Id 1454 * @return the calculated physical address of the port 1455 */ getActivePathOnSwitchFromActivePortId(@ocalActivePort int activePortId)1456 protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) { 1457 int myPhysicalAddress = mService.getPhysicalAddress(); 1458 int finalMask = activePortId << 8; 1459 int mask; 1460 for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { 1461 if ((myPhysicalAddress & mask) == 0) { 1462 break; 1463 } else { 1464 finalMask >>= 4; 1465 } 1466 } 1467 return finalMask | myPhysicalAddress; 1468 } 1469 1470 private static final class ActiveSourceHistoryRecord extends HdmiCecController.Dumpable { 1471 private final ActiveSource mActiveSource; 1472 private final boolean mIsActiveSource; 1473 private final String mCaller; 1474 ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, String caller)1475 private ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, 1476 String caller) { 1477 this.mActiveSource = mActiveSource; 1478 this.mIsActiveSource = mIsActiveSource; 1479 this.mCaller = caller; 1480 } 1481 1482 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1483 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1484 pw.print("time="); 1485 pw.print(sdf.format(new Date(mTime))); 1486 pw.print(" active source="); 1487 pw.print(mActiveSource); 1488 pw.print(" isActiveSource="); 1489 pw.print(mIsActiveSource); 1490 pw.print(" from="); 1491 pw.println(mCaller); 1492 } 1493 } 1494 } 1495