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 (!mService.invokeVendorCommandListenersOnReceived( 931 mDeviceType, message.getSource(), message.getDestination(), params, true)) { 932 if (message.getDestination() == Constants.ADDR_BROADCAST 933 || message.getSource() == Constants.ADDR_UNREGISTERED) { 934 Slog.v(TAG, "Broadcast vendor command with no listeners. Ignoring"); 935 } else { 936 return Constants.ABORT_REFUSED; 937 } 938 } 939 return Constants.HANDLED; 940 } 941 sendStandby(int deviceId)942 protected void sendStandby(int deviceId) { 943 // Do nothing. 944 } 945 946 @Constants.HandleMessageResult handleSetOsdName(HdmiCecMessage message)947 protected int handleSetOsdName(HdmiCecMessage message) { 948 // <Set OSD name> is also handled in HdmiCecNetwork to update the local network state 949 return Constants.HANDLED; 950 } 951 952 @Constants.HandleMessageResult handleRecordTvScreen(HdmiCecMessage message)953 protected int handleRecordTvScreen(HdmiCecMessage message) { 954 return Constants.NOT_HANDLED; 955 } 956 957 @Constants.HandleMessageResult handleTimerClearedStatus(HdmiCecMessage message)958 protected int handleTimerClearedStatus(HdmiCecMessage message) { 959 return Constants.NOT_HANDLED; 960 } 961 962 @Constants.HandleMessageResult handleReportPowerStatus(HdmiCecMessage message)963 protected int handleReportPowerStatus(HdmiCecMessage message) { 964 // <Report Power Status> is also handled in HdmiCecNetwork to update the local network state 965 return Constants.HANDLED; 966 } 967 968 @Constants.HandleMessageResult handleTimerStatus(HdmiCecMessage message)969 protected int handleTimerStatus(HdmiCecMessage message) { 970 return Constants.NOT_HANDLED; 971 } 972 973 @Constants.HandleMessageResult handleRecordStatus(HdmiCecMessage message)974 protected int handleRecordStatus(HdmiCecMessage message) { 975 return Constants.NOT_HANDLED; 976 } 977 978 @ServiceThreadOnly handleAddressAllocated( int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason)979 final void handleAddressAllocated( 980 int logicalAddress, List<HdmiCecMessage> bufferedMessages, int reason) { 981 assertRunOnServiceThread(); 982 preprocessBufferedMessages(bufferedMessages); 983 mPreferredAddress = logicalAddress; 984 updateDeviceFeatures(); 985 if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { 986 reportFeatures(); 987 } 988 onAddressAllocated(logicalAddress, reason); 989 setPreferredAddress(logicalAddress); 990 } 991 getType()992 int getType() { 993 return mDeviceType; 994 } 995 getDeviceInfo()996 HdmiDeviceInfo getDeviceInfo() { 997 synchronized (mLock) { 998 return mDeviceInfo; 999 } 1000 } 1001 setDeviceInfo(HdmiDeviceInfo info)1002 void setDeviceInfo(HdmiDeviceInfo info) { 1003 synchronized (mLock) { 1004 mDeviceInfo = info; 1005 } 1006 } 1007 1008 // Returns true if the logical address is same as the argument. 1009 @ServiceThreadOnly isAddressOf(int addr)1010 boolean isAddressOf(int addr) { 1011 assertRunOnServiceThread(); 1012 return addr == mDeviceInfo.getLogicalAddress(); 1013 } 1014 1015 @ServiceThreadOnly addAndStartAction(final HdmiCecFeatureAction action)1016 void addAndStartAction(final HdmiCecFeatureAction action) { 1017 assertRunOnServiceThread(); 1018 mActions.add(action); 1019 if (mService.isPowerStandby() || !mService.isAddressAllocated()) { 1020 if (action.getClass() == ResendCecCommandAction.class) { 1021 Slog.i(TAG, "Not ready to start ResendCecCommandAction. " 1022 + "This action is cancelled."); 1023 removeAction(action); 1024 return; 1025 } 1026 Slog.i(TAG, "Not ready to start action. Queued for deferred start:" + action); 1027 return; 1028 } 1029 action.start(); 1030 } 1031 1032 @ServiceThreadOnly addAndStartAction(final HdmiCecFeatureAction action, final boolean remove)1033 void addAndStartAction(final HdmiCecFeatureAction action, final boolean remove) { 1034 assertRunOnServiceThread(); 1035 if (hasAction(action.getClass()) && remove) { 1036 // If the action is currently running, remove it and restart it. 1037 Slog.i(TAG, action.getClass().getName() + " is in progress. Restarting."); 1038 removeAction(action.getClass()); 1039 } 1040 addAndStartAction(action); 1041 } 1042 1043 @ServiceThreadOnly startNewAvbAudioStatusAction(int targetAddress)1044 void startNewAvbAudioStatusAction(int targetAddress) { 1045 assertRunOnServiceThread(); 1046 addAndStartAction(new AbsoluteVolumeAudioStatusAction(this, targetAddress), true); 1047 } 1048 1049 @ServiceThreadOnly removeAvbAudioStatusAction()1050 void removeAvbAudioStatusAction() { 1051 assertRunOnServiceThread(); 1052 removeAction(AbsoluteVolumeAudioStatusAction.class); 1053 } 1054 1055 @ServiceThreadOnly updateAvbVolume(int volumeIndex)1056 void updateAvbVolume(int volumeIndex) { 1057 assertRunOnServiceThread(); 1058 for (AbsoluteVolumeAudioStatusAction action : 1059 getActions(AbsoluteVolumeAudioStatusAction.class)) { 1060 action.updateVolume(volumeIndex); 1061 } 1062 } 1063 1064 /** 1065 * If AVB has been enabled, request the System Audio device's audio status and notify 1066 * AudioService of its response. 1067 */ 1068 @ServiceThreadOnly requestAndUpdateAvbAudioStatus()1069 void requestAndUpdateAvbAudioStatus() { 1070 assertRunOnServiceThread(); 1071 for (AbsoluteVolumeAudioStatusAction action : 1072 getActions(AbsoluteVolumeAudioStatusAction.class)) { 1073 action.requestAndUpdateAudioStatus(); 1074 } 1075 } 1076 1077 /** 1078 * Determines whether {@code targetAddress} supports <Set Audio Volume Level>. Does two things 1079 * in parallel: send <Give Features> (to get <Report Features> in response), 1080 * and send <Set Audio Volume Level> (to see if it gets a <Feature Abort> in response). 1081 */ 1082 @ServiceThreadOnly querySetAudioVolumeLevelSupport(int targetAddress)1083 void querySetAudioVolumeLevelSupport(int targetAddress) { 1084 assertRunOnServiceThread(); 1085 1086 // Send <Give Features> if using CEC 2.0 or above. 1087 if (mService.getCecVersion() >= HdmiControlManager.HDMI_CEC_VERSION_2_0) { 1088 mService.sendCecCommand(HdmiCecMessageBuilder.buildGiveFeatures( 1089 getDeviceInfo().getLogicalAddress(), targetAddress)); 1090 } 1091 1092 // If we don't already have a {@link SetAudioVolumeLevelDiscoveryAction} for the target 1093 // device, start one. 1094 List<SetAudioVolumeLevelDiscoveryAction> savlDiscoveryActions = 1095 getActions(SetAudioVolumeLevelDiscoveryAction.class); 1096 if (savlDiscoveryActions.stream().noneMatch(a -> a.getTargetAddress() == targetAddress)) { 1097 addAndStartAction(new SetAudioVolumeLevelDiscoveryAction(this, targetAddress, 1098 new IHdmiControlCallback.Stub() { 1099 @Override 1100 public void onComplete(int result) { 1101 if (result == HdmiControlManager.RESULT_SUCCESS) { 1102 getService().checkAndUpdateAbsoluteVolumeBehavior(); 1103 } 1104 } 1105 })); 1106 } 1107 } 1108 1109 @ServiceThreadOnly startQueuedActions()1110 void startQueuedActions() { 1111 assertRunOnServiceThread(); 1112 // Use copied action list in that start() may remove itself. 1113 for (HdmiCecFeatureAction action : new ArrayList<>(mActions)) { 1114 if (!action.started()) { 1115 Slog.i(TAG, "Starting queued action:" + action); 1116 action.start(); 1117 } 1118 } 1119 } 1120 1121 // See if we have an action of a given type in progress. 1122 @ServiceThreadOnly hasAction(final Class<T> clazz)1123 <T extends HdmiCecFeatureAction> boolean hasAction(final Class<T> clazz) { 1124 assertRunOnServiceThread(); 1125 for (HdmiCecFeatureAction action : mActions) { 1126 if (action.getClass().equals(clazz)) { 1127 return true; 1128 } 1129 } 1130 return false; 1131 } 1132 1133 // Returns all actions matched with given class type. 1134 @VisibleForTesting 1135 @ServiceThreadOnly getActions(final Class<T> clazz)1136 <T extends HdmiCecFeatureAction> List<T> getActions(final Class<T> clazz) { 1137 assertRunOnServiceThread(); 1138 List<T> actions = Collections.<T>emptyList(); 1139 for (HdmiCecFeatureAction action : mActions) { 1140 if (action.getClass().equals(clazz)) { 1141 if (actions.isEmpty()) { 1142 actions = new ArrayList<T>(); 1143 } 1144 actions.add((T) action); 1145 } 1146 } 1147 return actions; 1148 } 1149 1150 /** 1151 * Remove the given {@link HdmiCecFeatureAction} object from the action queue. 1152 * 1153 * @param action {@link HdmiCecFeatureAction} to remove 1154 */ 1155 @ServiceThreadOnly removeAction(final HdmiCecFeatureAction action)1156 void removeAction(final HdmiCecFeatureAction action) { 1157 assertRunOnServiceThread(); 1158 action.finish(false); 1159 mActions.remove(action); 1160 checkIfPendingActionsCleared(); 1161 } 1162 1163 // Remove all actions matched with the given Class type. 1164 @ServiceThreadOnly removeAction(final Class<T> clazz)1165 <T extends HdmiCecFeatureAction> void removeAction(final Class<T> clazz) { 1166 assertRunOnServiceThread(); 1167 removeActionExcept(clazz, null); 1168 } 1169 1170 // Remove all running actions. 1171 @ServiceThreadOnly removeAllActions()1172 void removeAllActions() { 1173 assertRunOnServiceThread(); 1174 for (HdmiCecFeatureAction action : mActions) { 1175 action.finish(false); 1176 } 1177 mActions.clear(); 1178 } 1179 1180 // Remove all actions matched with the given Class type besides |exception|. 1181 @ServiceThreadOnly removeActionExcept( final Class<T> clazz, final HdmiCecFeatureAction exception)1182 <T extends HdmiCecFeatureAction> void removeActionExcept( 1183 final Class<T> clazz, final HdmiCecFeatureAction exception) { 1184 assertRunOnServiceThread(); 1185 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 1186 while (iter.hasNext()) { 1187 HdmiCecFeatureAction action = iter.next(); 1188 if (action != exception && action.getClass().equals(clazz)) { 1189 action.finish(false); 1190 iter.remove(); 1191 } 1192 } 1193 checkIfPendingActionsCleared(); 1194 } 1195 checkIfPendingActionsCleared()1196 protected void checkIfPendingActionsCleared() { 1197 if (mActions.isEmpty() && mPendingActionClearedCallback != null) { 1198 PendingActionClearedCallback callback = mPendingActionClearedCallback; 1199 // To prevent from calling the callback again during handling the callback itself. 1200 mPendingActionClearedCallback = null; 1201 callback.onCleared(this); 1202 } 1203 } 1204 assertRunOnServiceThread()1205 protected void assertRunOnServiceThread() { 1206 if (Looper.myLooper() != mService.getServiceLooper()) { 1207 throw new IllegalStateException("Should run on service thread."); 1208 } 1209 } 1210 1211 /** 1212 * Called when a hot-plug event issued. 1213 * 1214 * @param portId id of port where a hot-plug event happened 1215 * @param connected whether to connected or not on the event 1216 */ onHotplug(int portId, boolean connected)1217 void onHotplug(int portId, boolean connected) {} 1218 getService()1219 final HdmiControlService getService() { 1220 return mService; 1221 } 1222 1223 @ServiceThreadOnly isConnectedToArcPort(int path)1224 final boolean isConnectedToArcPort(int path) { 1225 assertRunOnServiceThread(); 1226 return mService.isConnectedToArcPort(path); 1227 } 1228 getActiveSource()1229 ActiveSource getActiveSource() { 1230 return mService.getLocalActiveSource(); 1231 } 1232 setActiveSource(ActiveSource newActive, String caller)1233 void setActiveSource(ActiveSource newActive, String caller) { 1234 setActiveSource(newActive.logicalAddress, newActive.physicalAddress, caller); 1235 } 1236 setActiveSource(HdmiDeviceInfo info, String caller)1237 void setActiveSource(HdmiDeviceInfo info, String caller) { 1238 setActiveSource(info.getLogicalAddress(), info.getPhysicalAddress(), caller); 1239 } 1240 setActiveSource(int logicalAddress, int physicalAddress, String caller)1241 void setActiveSource(int logicalAddress, int physicalAddress, String caller) { 1242 mService.setActiveSource(logicalAddress, physicalAddress, caller); 1243 mService.setLastInputForMhl(Constants.INVALID_PORT_ID); 1244 } 1245 getActivePath()1246 int getActivePath() { 1247 synchronized (mLock) { 1248 return mActiveRoutingPath; 1249 } 1250 } 1251 setActivePath(int path)1252 void setActivePath(int path) { 1253 synchronized (mLock) { 1254 mActiveRoutingPath = path; 1255 } 1256 mService.setActivePortId(pathToPortId(path)); 1257 } 1258 1259 /** 1260 * Returns the ID of the active HDMI port. The active port is the one that has the active 1261 * routing path connected to it directly or indirectly under the device hierarchy. 1262 */ getActivePortId()1263 int getActivePortId() { 1264 synchronized (mLock) { 1265 return mService.pathToPortId(mActiveRoutingPath); 1266 } 1267 } 1268 1269 /** 1270 * Update the active port. 1271 * 1272 * @param portId the new active port id 1273 */ setActivePortId(int portId)1274 void setActivePortId(int portId) { 1275 // We update active routing path instead, since we get the active port id from 1276 // the active routing path. 1277 setActivePath(mService.portIdToPath(portId)); 1278 } 1279 1280 // Returns the id of the port that the target device is connected to. getPortId(int physicalAddress)1281 int getPortId(int physicalAddress) { 1282 return mService.pathToPortId(physicalAddress); 1283 } 1284 1285 @ServiceThreadOnly getCecMessageCache()1286 HdmiCecMessageCache getCecMessageCache() { 1287 assertRunOnServiceThread(); 1288 return mCecMessageCache; 1289 } 1290 1291 @ServiceThreadOnly pathToPortId(int newPath)1292 int pathToPortId(int newPath) { 1293 assertRunOnServiceThread(); 1294 return mService.pathToPortId(newPath); 1295 } 1296 1297 /** 1298 * Called when the system goes to standby mode. 1299 * 1300 * @param initiatedByCec true if this power sequence is initiated by the reception the CEC 1301 * messages like <Standby> 1302 * @param standbyAction Intent action that drives the standby process, either {@link 1303 * HdmiControlService#STANDBY_SCREEN_OFF} or {@link HdmiControlService#STANDBY_SHUTDOWN} 1304 * @param callback callback invoked after the standby process for the local device is completed. 1305 */ onStandby(boolean initiatedByCec, int standbyAction, StandbyCompletedCallback callback)1306 protected void onStandby(boolean initiatedByCec, int standbyAction, 1307 StandbyCompletedCallback callback) {} 1308 onStandby(boolean initiatedByCec, int standbyAction)1309 protected void onStandby(boolean initiatedByCec, int standbyAction) { 1310 onStandby(initiatedByCec, standbyAction, null); 1311 } 1312 1313 /** 1314 * Called when the initialization of local devices is complete. 1315 */ onInitializeCecComplete(int initiatedBy)1316 protected void onInitializeCecComplete(int initiatedBy) {} 1317 1318 /** 1319 * Disable device. {@code callback} is used to get notified when all pending actions are 1320 * completed or timeout is issued. 1321 * 1322 * @param initiatedByCec true if this sequence is initiated by the reception the CEC messages 1323 * like <Standby> 1324 * @param originalCallback callback interface to get notified when all pending actions are 1325 * cleared 1326 */ disableDevice( boolean initiatedByCec, final PendingActionClearedCallback originalCallback)1327 protected void disableDevice( 1328 boolean initiatedByCec, final PendingActionClearedCallback originalCallback) { 1329 removeAction(SetAudioVolumeLevelDiscoveryAction.class); 1330 removeAction(ActiveSourceAction.class); 1331 removeAction(ResendCecCommandAction.class); 1332 1333 mPendingActionClearedCallback = 1334 new PendingActionClearedCallback() { 1335 @Override 1336 public void onCleared(HdmiCecLocalDevice device) { 1337 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); 1338 originalCallback.onCleared(device); 1339 } 1340 }; 1341 mHandler.sendMessageDelayed( 1342 Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), DEVICE_CLEANUP_TIMEOUT); 1343 } 1344 1345 @ServiceThreadOnly handleDisableDeviceTimeout()1346 private void handleDisableDeviceTimeout() { 1347 assertRunOnServiceThread(); 1348 1349 // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. 1350 // onCleard will be called at the last action's finish method. 1351 Iterator<HdmiCecFeatureAction> iter = mActions.iterator(); 1352 while (iter.hasNext()) { 1353 HdmiCecFeatureAction action = iter.next(); 1354 action.finish(false); 1355 iter.remove(); 1356 } 1357 if (mPendingActionClearedCallback != null) { 1358 PendingActionClearedCallback callback = mPendingActionClearedCallback; 1359 // To prevent from calling the callback again during handling the callback itself. 1360 mPendingActionClearedCallback = null; 1361 callback.onCleared(this); 1362 } 1363 } 1364 1365 /** 1366 * Send a key event to other CEC device. The logical address of target device will be given by 1367 * {@link #findKeyReceiverAddress}. 1368 * 1369 * @param keyCode key code defined in {@link android.view.KeyEvent} 1370 * @param isPressed {@code true} for key down event 1371 * @see #findKeyReceiverAddress() 1372 */ 1373 @ServiceThreadOnly sendKeyEvent(int keyCode, boolean isPressed)1374 protected void sendKeyEvent(int keyCode, boolean isPressed) { 1375 assertRunOnServiceThread(); 1376 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { 1377 Slog.w(TAG, "Unsupported key: " + keyCode); 1378 return; 1379 } 1380 List<SendKeyAction> action = getActions(SendKeyAction.class); 1381 int logicalAddress = findKeyReceiverAddress(); 1382 if (logicalAddress == Constants.ADDR_INVALID 1383 || logicalAddress == mDeviceInfo.getLogicalAddress()) { 1384 // Don't send key event to invalid device or itself. 1385 Slog.w( 1386 TAG, 1387 "Discard key event: " 1388 + keyCode 1389 + ", pressed:" 1390 + isPressed 1391 + ", receiverAddr=" 1392 + logicalAddress); 1393 } else if (!action.isEmpty()) { 1394 action.get(0).processKeyEvent(keyCode, isPressed); 1395 } else if (isPressed) { 1396 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 1397 } 1398 } 1399 1400 /** 1401 * Send a volume key event to other CEC device. The logical address of target device will be 1402 * given by {@link #findAudioReceiverAddress()}. 1403 * 1404 * @param keyCode key code defined in {@link android.view.KeyEvent} 1405 * @param isPressed {@code true} for key down event 1406 * @see #findAudioReceiverAddress() 1407 */ 1408 @ServiceThreadOnly sendVolumeKeyEvent(int keyCode, boolean isPressed)1409 protected void sendVolumeKeyEvent(int keyCode, boolean isPressed) { 1410 assertRunOnServiceThread(); 1411 if (mService.getHdmiCecVolumeControl() 1412 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 1413 return; 1414 } 1415 if (!HdmiCecKeycode.isVolumeKeycode(keyCode)) { 1416 Slog.w(TAG, "Not a volume key: " + keyCode); 1417 return; 1418 } 1419 List<SendKeyAction> action = getActions(SendKeyAction.class); 1420 int logicalAddress = findAudioReceiverAddress(); 1421 if (logicalAddress == Constants.ADDR_INVALID 1422 || mService.getAllCecLocalDevices().stream().anyMatch( 1423 device -> device.getDeviceInfo().getLogicalAddress() == logicalAddress)) { 1424 // Don't send key event to invalid device or itself. 1425 Slog.w( 1426 TAG, 1427 "Discard volume key event: " 1428 + keyCode 1429 + ", pressed:" 1430 + isPressed 1431 + ", receiverAddr=" 1432 + logicalAddress); 1433 } else if (!action.isEmpty()) { 1434 action.get(0).processKeyEvent(keyCode, isPressed); 1435 } else if (isPressed) { 1436 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 1437 } 1438 } 1439 1440 /** 1441 * Returns the logical address of the device which will receive key events via {@link 1442 * #sendKeyEvent}. 1443 * 1444 * @see #sendKeyEvent(int, boolean) 1445 */ findKeyReceiverAddress()1446 protected int findKeyReceiverAddress() { 1447 Slog.w(TAG, "findKeyReceiverAddress is not implemented"); 1448 return Constants.ADDR_INVALID; 1449 } 1450 1451 /** 1452 * Returns the logical address of the audio receiver device which will receive volume key events 1453 * via {@link#sendVolumeKeyEvent}. 1454 * 1455 * @see #sendVolumeKeyEvent(int, boolean) 1456 */ findAudioReceiverAddress()1457 protected int findAudioReceiverAddress() { 1458 Slog.w(TAG, "findAudioReceiverAddress is not implemented"); 1459 return Constants.ADDR_INVALID; 1460 } 1461 1462 @ServiceThreadOnly invokeCallback(IHdmiControlCallback callback, int result)1463 void invokeCallback(IHdmiControlCallback callback, int result) { 1464 assertRunOnServiceThread(); 1465 if (callback == null) { 1466 return; 1467 } 1468 try { 1469 callback.onComplete(result); 1470 } catch (RemoteException e) { 1471 Slog.e(TAG, "Invoking callback failed:" + e); 1472 } 1473 } 1474 1475 @ServiceThreadOnly 1476 @VisibleForTesting invokeStandbyCompletedCallback(StandbyCompletedCallback callback)1477 public void invokeStandbyCompletedCallback(StandbyCompletedCallback callback) { 1478 assertRunOnServiceThread(); 1479 if (callback == null) { 1480 return; 1481 } 1482 callback.onStandbyCompleted(); 1483 } 1484 sendUserControlPressedAndReleased(int targetAddress, int cecKeycode)1485 void sendUserControlPressedAndReleased(int targetAddress, int cecKeycode) { 1486 mService.sendCecCommand( 1487 HdmiCecMessageBuilder.buildUserControlPressed( 1488 mDeviceInfo.getLogicalAddress(), targetAddress, cecKeycode)); 1489 mService.sendCecCommand( 1490 HdmiCecMessageBuilder.buildUserControlReleased( 1491 mDeviceInfo.getLogicalAddress(), targetAddress)); 1492 } 1493 addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, String caller)1494 void addActiveSourceHistoryItem(ActiveSource activeSource, boolean isActiveSource, 1495 String caller) { 1496 ActiveSourceHistoryRecord record = new ActiveSourceHistoryRecord(activeSource, 1497 isActiveSource, caller); 1498 if (!mActiveSourceHistory.offer(record)) { 1499 mActiveSourceHistory.poll(); 1500 mActiveSourceHistory.offer(record); 1501 } 1502 } 1503 getActiveSourceHistory()1504 public ArrayBlockingQueue<HdmiCecController.Dumpable> getActiveSourceHistory() { 1505 return this.mActiveSourceHistory; 1506 } 1507 1508 /** Dump internal status of HdmiCecLocalDevice object. */ dump(final IndentingPrintWriter pw)1509 protected void dump(final IndentingPrintWriter pw) { 1510 pw.println("mDeviceType: " + mDeviceType); 1511 pw.println("mPreferredAddress: " + mPreferredAddress); 1512 pw.println("mDeviceInfo: " + mDeviceInfo); 1513 pw.println("mActiveSource: " + getActiveSource()); 1514 pw.println(String.format("mActiveRoutingPath: 0x%04x", mActiveRoutingPath)); 1515 } 1516 1517 /** Calculates the physical address for {@code activePortId}. 1518 * 1519 * <p>This method assumes current device physical address is valid. 1520 * <p>If the current device is already the leaf of the whole CEC system 1521 * and can't have devices under it, will return its own physical address. 1522 * 1523 * @param activePortId is the local active port Id 1524 * @return the calculated physical address of the port 1525 */ getActivePathOnSwitchFromActivePortId(@ocalActivePort int activePortId)1526 protected int getActivePathOnSwitchFromActivePortId(@LocalActivePort int activePortId) { 1527 int myPhysicalAddress = mService.getPhysicalAddress(); 1528 int finalMask = activePortId << 8; 1529 int mask; 1530 for (mask = 0x0F00; mask > 0x000F; mask >>= 4) { 1531 if ((myPhysicalAddress & mask) == 0) { 1532 break; 1533 } else { 1534 finalMask >>= 4; 1535 } 1536 } 1537 return finalMask | myPhysicalAddress; 1538 } 1539 1540 private static final class ActiveSourceHistoryRecord extends HdmiCecController.Dumpable { 1541 private final ActiveSource mActiveSource; 1542 private final boolean mIsActiveSource; 1543 private final String mCaller; 1544 ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, String caller)1545 private ActiveSourceHistoryRecord(ActiveSource mActiveSource, boolean mIsActiveSource, 1546 String caller) { 1547 this.mActiveSource = mActiveSource; 1548 this.mIsActiveSource = mIsActiveSource; 1549 this.mCaller = caller; 1550 } 1551 1552 @Override dump(final IndentingPrintWriter pw, SimpleDateFormat sdf)1553 void dump(final IndentingPrintWriter pw, SimpleDateFormat sdf) { 1554 pw.print("time="); 1555 pw.print(sdf.format(new Date(mTime))); 1556 pw.print(" active source="); 1557 pw.print(mActiveSource); 1558 pw.print(" isActiveSource="); 1559 pw.print(mIsActiveSource); 1560 pw.print(" from="); 1561 pw.println(mCaller); 1562 } 1563 } 1564 } 1565