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