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