HdmiCecLocalDevice.java revision b6591b8e5399099dc6b7693e0fc719b613aba89c
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 17package com.android.server.hdmi; 18 19import android.hardware.hdmi.HdmiCecDeviceInfo; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.os.SystemProperties; 24import android.util.Slog; 25 26import com.android.internal.annotations.GuardedBy; 27import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 28 29import java.util.ArrayList; 30import java.util.Iterator; 31import java.util.LinkedList; 32import java.util.List; 33 34/** 35 * Class that models a logical CEC device hosted in this system. Handles initialization, 36 * CEC commands that call for actions customized per device type. 37 */ 38abstract class HdmiCecLocalDevice { 39 private static final String TAG = "HdmiCecLocalDevice"; 40 41 private static final int MSG_DISABLE_DEVICE_TIMEOUT = 1; 42 // Timeout in millisecond for device clean up (5s). 43 // Normal actions timeout is 2s but some of them would have several sequence of timeout. 44 private static final int DEVICE_CLEANUP_TIMEOUT = 5000; 45 46 protected final HdmiControlService mService; 47 protected final int mDeviceType; 48 protected int mAddress; 49 protected int mPreferredAddress; 50 protected HdmiCecDeviceInfo mDeviceInfo; 51 52 // Logical address of the active source. 53 @GuardedBy("mLock") 54 private int mActiveSource; 55 56 // Active routing path. Physical address of the active source but not all the time, such as 57 // when the new active source does not claim itself to be one. Note that we don't keep 58 // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}. 59 @GuardedBy("mLock") 60 private int mActiveRoutingPath; 61 62 protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 63 protected final Object mLock; 64 65 // A collection of FeatureAction. 66 // Note that access to this collection should happen in service thread. 67 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 68 69 private final Handler mHandler = new Handler () { 70 @Override 71 public void handleMessage(Message msg) { 72 switch (msg.what) { 73 case MSG_DISABLE_DEVICE_TIMEOUT: 74 handleDisableDeviceTimeout(); 75 break; 76 } 77 } 78 }; 79 80 /** 81 * A callback interface to get notified when all pending action is cleared. 82 * It can be called when timeout happened. 83 */ 84 interface PendingActionClearedCallback { 85 void onCleared(HdmiCecLocalDevice device); 86 } 87 88 protected PendingActionClearedCallback mPendingActionClearedCallback; 89 90 protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { 91 mService = service; 92 mDeviceType = deviceType; 93 mAddress = Constants.ADDR_UNREGISTERED; 94 mLock = service.getServiceLock(); 95 } 96 97 // Factory method that returns HdmiCecLocalDevice of corresponding type. 98 static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) { 99 switch (deviceType) { 100 case HdmiCecDeviceInfo.DEVICE_TV: 101 return new HdmiCecLocalDeviceTv(service); 102 case HdmiCecDeviceInfo.DEVICE_PLAYBACK: 103 return new HdmiCecLocalDevicePlayback(service); 104 default: 105 return null; 106 } 107 } 108 109 @ServiceThreadOnly 110 void init() { 111 assertRunOnServiceThread(); 112 mPreferredAddress = getPreferredAddress(); 113 } 114 115 /** 116 * Called once a logical address of the local device is allocated. 117 */ 118 protected abstract void onAddressAllocated(int logicalAddress, boolean fromBootup); 119 120 /** 121 * Get the preferred logical address from system properties. 122 */ 123 protected abstract int getPreferredAddress(); 124 125 /** 126 * Set the preferred logical address to system properties. 127 */ 128 protected abstract void setPreferredAddress(int addr); 129 130 /** 131 * Dispatch incoming message. 132 * 133 * @param message incoming message 134 * @return true if consumed a message; otherwise, return false. 135 */ 136 @ServiceThreadOnly 137 final boolean dispatchMessage(HdmiCecMessage message) { 138 assertRunOnServiceThread(); 139 int dest = message.getDestination(); 140 if (dest != mAddress && dest != Constants.ADDR_BROADCAST) { 141 return false; 142 } 143 // Cache incoming message. Note that it caches only white-listed one. 144 mCecMessageCache.cacheMessage(message); 145 return onMessage(message); 146 } 147 148 @ServiceThreadOnly 149 protected final boolean onMessage(HdmiCecMessage message) { 150 assertRunOnServiceThread(); 151 if (dispatchMessageToAction(message)) { 152 return true; 153 } 154 switch (message.getOpcode()) { 155 case Constants.MESSAGE_ACTIVE_SOURCE: 156 return handleActiveSource(message); 157 case Constants.MESSAGE_INACTIVE_SOURCE: 158 return handleInactiveSource(message); 159 case Constants.MESSAGE_REQUEST_ACTIVE_SOURCE: 160 return handleRequestActiveSource(message); 161 case Constants.MESSAGE_GET_MENU_LANGUAGE: 162 return handleGetMenuLanguage(message); 163 case Constants.MESSAGE_GIVE_PHYSICAL_ADDRESS: 164 return handleGivePhysicalAddress(); 165 case Constants.MESSAGE_GIVE_OSD_NAME: 166 return handleGiveOsdName(message); 167 case Constants.MESSAGE_GIVE_DEVICE_VENDOR_ID: 168 return handleGiveDeviceVendorId(); 169 case Constants.MESSAGE_GET_CEC_VERSION: 170 return handleGetCecVersion(message); 171 case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: 172 return handleReportPhysicalAddress(message); 173 case Constants.MESSAGE_ROUTING_CHANGE: 174 return handleRoutingChange(message); 175 case Constants.MESSAGE_INITIATE_ARC: 176 return handleInitiateArc(message); 177 case Constants.MESSAGE_TERMINATE_ARC: 178 return handleTerminateArc(message); 179 case Constants.MESSAGE_SET_SYSTEM_AUDIO_MODE: 180 return handleSetSystemAudioMode(message); 181 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 182 return handleSystemAudioModeStatus(message); 183 case Constants.MESSAGE_REPORT_AUDIO_STATUS: 184 return handleReportAudioStatus(message); 185 case Constants.MESSAGE_STANDBY: 186 return handleStandby(message); 187 case Constants.MESSAGE_TEXT_VIEW_ON: 188 return handleTextViewOn(message); 189 case Constants.MESSAGE_IMAGE_VIEW_ON: 190 return handleImageViewOn(message); 191 case Constants.MESSAGE_USER_CONTROL_PRESSED: 192 return handleUserControlPressed(message); 193 case Constants.MESSAGE_SET_STREAM_PATH: 194 return handleSetStreamPath(message); 195 case Constants.MESSAGE_GIVE_DEVICE_POWER_STATUS: 196 return handleGiveDevicePowerStatus(message); 197 case Constants.MESSAGE_VENDOR_COMMAND: 198 return handleVendorCommand(message); 199 case Constants.MESSAGE_VENDOR_COMMAND_WITH_ID: 200 return handleVendorCommandWithId(message); 201 case Constants.MESSAGE_SET_OSD_NAME: 202 return handleSetOsdName(message); 203 case Constants.MESSAGE_RECORD_TV_SCREEN: 204 return handleRecordTvScreen(message); 205 default: 206 return false; 207 } 208 } 209 210 @ServiceThreadOnly 211 private boolean dispatchMessageToAction(HdmiCecMessage message) { 212 assertRunOnServiceThread(); 213 for (FeatureAction action : mActions) { 214 if (action.processCommand(message)) { 215 return true; 216 } 217 } 218 return false; 219 } 220 221 @ServiceThreadOnly 222 protected boolean handleGivePhysicalAddress() { 223 assertRunOnServiceThread(); 224 225 int physicalAddress = mService.getPhysicalAddress(); 226 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 227 mAddress, physicalAddress, mDeviceType); 228 mService.sendCecCommand(cecMessage); 229 return true; 230 } 231 232 @ServiceThreadOnly 233 protected boolean handleGiveDeviceVendorId() { 234 assertRunOnServiceThread(); 235 int vendorId = mService.getVendorId(); 236 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 237 mAddress, vendorId); 238 mService.sendCecCommand(cecMessage); 239 return true; 240 } 241 242 @ServiceThreadOnly 243 protected boolean handleGetCecVersion(HdmiCecMessage message) { 244 assertRunOnServiceThread(); 245 int version = mService.getCecVersion(); 246 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 247 message.getSource(), version); 248 mService.sendCecCommand(cecMessage); 249 return true; 250 } 251 252 @ServiceThreadOnly 253 protected boolean handleActiveSource(HdmiCecMessage message) { 254 return false; 255 } 256 257 @ServiceThreadOnly 258 protected boolean handleInactiveSource(HdmiCecMessage message) { 259 return false; 260 } 261 262 @ServiceThreadOnly 263 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 264 return false; 265 } 266 267 @ServiceThreadOnly 268 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 269 assertRunOnServiceThread(); 270 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 271 mService.sendCecCommand( 272 HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 273 message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE, 274 Constants.ABORT_UNRECOGNIZED_MODE)); 275 return true; 276 } 277 278 @ServiceThreadOnly 279 protected boolean handleGiveOsdName(HdmiCecMessage message) { 280 assertRunOnServiceThread(); 281 // Note that since this method is called after logical address allocation is done, 282 // mDeviceInfo should not be null. 283 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 284 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 285 if (cecMessage != null) { 286 mService.sendCecCommand(cecMessage); 287 } else { 288 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 289 } 290 return true; 291 } 292 293 protected boolean handleRoutingChange(HdmiCecMessage message) { 294 return false; 295 } 296 297 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 298 return false; 299 } 300 301 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 302 return false; 303 } 304 305 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 306 return false; 307 } 308 309 protected boolean handleTerminateArc(HdmiCecMessage message) { 310 return false; 311 } 312 313 protected boolean handleInitiateArc(HdmiCecMessage message) { 314 return false; 315 } 316 317 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 318 return false; 319 } 320 321 @ServiceThreadOnly 322 protected boolean handleStandby(HdmiCecMessage message) { 323 assertRunOnServiceThread(); 324 // Seq #12 325 if (mService.isControlEnabled() && !mService.isProhibitMode() 326 && mService.isPowerOnOrTransient()) { 327 mService.standby(); 328 return true; 329 } 330 return false; 331 } 332 333 @ServiceThreadOnly 334 protected boolean handleUserControlPressed(HdmiCecMessage message) { 335 assertRunOnServiceThread(); 336 if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { 337 mService.standby(); 338 return true; 339 } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { 340 mService.wakeUp(); 341 return true; 342 } 343 return false; 344 } 345 346 private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { 347 byte[] params = message.getParams(); 348 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 349 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 350 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION 351 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 352 } 353 354 private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { 355 byte[] params = message.getParams(); 356 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 357 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 358 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION 359 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 360 } 361 362 protected boolean handleTextViewOn(HdmiCecMessage message) { 363 return false; 364 } 365 366 protected boolean handleImageViewOn(HdmiCecMessage message) { 367 return false; 368 } 369 370 protected boolean handleSetStreamPath(HdmiCecMessage message) { 371 return false; 372 } 373 374 protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) { 375 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus( 376 mAddress, message.getSource(), mService.getPowerStatus())); 377 return true; 378 } 379 380 protected boolean handleVendorCommand(HdmiCecMessage message) { 381 mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), 382 message.getParams(), false); 383 return true; 384 } 385 386 protected boolean handleVendorCommandWithId(HdmiCecMessage message) { 387 byte[] params = message.getParams(); 388 int vendorId = HdmiUtils.threeBytesToInt(params); 389 if (vendorId == mService.getVendorId()) { 390 mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, true); 391 } else if (message.getDestination() != Constants.ADDR_BROADCAST && 392 message.getSource() != Constants.ADDR_UNREGISTERED) { 393 Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); 394 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 395 message.getSource(), Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, 396 Constants.ABORT_UNRECOGNIZED_MODE)); 397 } else { 398 Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); 399 } 400 return true; 401 } 402 403 protected boolean handleSetOsdName(HdmiCecMessage message) { 404 // The default behavior of <Set Osd Name> is doing nothing. 405 return true; 406 } 407 408 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 409 // The default behavior of <Record TV Screen> is replying <Feature Abort> with "Refused". 410 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 411 message.getSource(), message.getOpcode(), Constants.ABORT_REFUSED)); 412 return true; 413 } 414 415 @ServiceThreadOnly 416 final void handleAddressAllocated(int logicalAddress, boolean fromBootup) { 417 assertRunOnServiceThread(); 418 mAddress = mPreferredAddress = logicalAddress; 419 onAddressAllocated(logicalAddress, fromBootup); 420 setPreferredAddress(logicalAddress); 421 } 422 423 @ServiceThreadOnly 424 HdmiCecDeviceInfo getDeviceInfo() { 425 assertRunOnServiceThread(); 426 return mDeviceInfo; 427 } 428 429 @ServiceThreadOnly 430 void setDeviceInfo(HdmiCecDeviceInfo info) { 431 assertRunOnServiceThread(); 432 mDeviceInfo = info; 433 } 434 435 // Returns true if the logical address is same as the argument. 436 @ServiceThreadOnly 437 boolean isAddressOf(int addr) { 438 assertRunOnServiceThread(); 439 return addr == mAddress; 440 } 441 442 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 443 @ServiceThreadOnly 444 void clearAddress() { 445 assertRunOnServiceThread(); 446 mAddress = Constants.ADDR_UNREGISTERED; 447 } 448 449 @ServiceThreadOnly 450 void addAndStartAction(final FeatureAction action) { 451 assertRunOnServiceThread(); 452 if (mService.isPowerStandbyOrTransient()) { 453 Slog.w(TAG, "Skip the action during Standby: " + action); 454 return; 455 } 456 mActions.add(action); 457 action.start(); 458 } 459 460 // See if we have an action of a given type in progress. 461 @ServiceThreadOnly 462 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 463 assertRunOnServiceThread(); 464 for (FeatureAction action : mActions) { 465 if (action.getClass().equals(clazz)) { 466 return true; 467 } 468 } 469 return false; 470 } 471 472 // Returns all actions matched with given class type. 473 @ServiceThreadOnly 474 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 475 assertRunOnServiceThread(); 476 ArrayList<T> actions = new ArrayList<>(); 477 for (FeatureAction action : mActions) { 478 if (action.getClass().equals(clazz)) { 479 actions.add((T) action); 480 } 481 } 482 return actions; 483 } 484 485 /** 486 * Remove the given {@link FeatureAction} object from the action queue. 487 * 488 * @param action {@link FeatureAction} to remove 489 */ 490 @ServiceThreadOnly 491 void removeAction(final FeatureAction action) { 492 assertRunOnServiceThread(); 493 action.finish(false); 494 mActions.remove(action); 495 checkIfPendingActionsCleared(); 496 } 497 498 // Remove all actions matched with the given Class type. 499 @ServiceThreadOnly 500 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 501 assertRunOnServiceThread(); 502 removeActionExcept(clazz, null); 503 } 504 505 // Remove all actions matched with the given Class type besides |exception|. 506 @ServiceThreadOnly 507 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 508 final FeatureAction exception) { 509 assertRunOnServiceThread(); 510 Iterator<FeatureAction> iter = mActions.iterator(); 511 while (iter.hasNext()) { 512 FeatureAction action = iter.next(); 513 if (action != exception && action.getClass().equals(clazz)) { 514 action.finish(false); 515 iter.remove(); 516 } 517 } 518 checkIfPendingActionsCleared(); 519 } 520 521 protected void checkIfPendingActionsCleared() { 522 if (mActions.isEmpty() && mPendingActionClearedCallback != null) { 523 mPendingActionClearedCallback.onCleared(this); 524 } 525 } 526 527 protected void assertRunOnServiceThread() { 528 if (Looper.myLooper() != mService.getServiceLooper()) { 529 throw new IllegalStateException("Should run on service thread."); 530 } 531 } 532 533 /** 534 * Called when a hot-plug event issued. 535 * 536 * @param portId id of port where a hot-plug event happened 537 * @param connected whether to connected or not on the event 538 */ 539 void onHotplug(int portId, boolean connected) { 540 } 541 542 final HdmiControlService getService() { 543 return mService; 544 } 545 546 @ServiceThreadOnly 547 final boolean isConnectedToArcPort(int path) { 548 assertRunOnServiceThread(); 549 return mService.isConnectedToArcPort(path); 550 } 551 552 int getActiveSource() { 553 synchronized (mLock) { 554 return mActiveSource; 555 } 556 } 557 558 void setActiveSource(int source) { 559 synchronized (mLock) { 560 mActiveSource = source; 561 } 562 } 563 564 int getActivePath() { 565 synchronized (mLock) { 566 return mActiveRoutingPath; 567 } 568 } 569 570 void setActivePath(int path) { 571 synchronized (mLock) { 572 mActiveRoutingPath = path; 573 } 574 } 575 576 /** 577 * Returns the ID of the active HDMI port. The active port is the one that has the active 578 * routing path connected to it directly or indirectly under the device hierarchy. 579 */ 580 int getActivePortId() { 581 synchronized (mLock) { 582 return mService.pathToPortId(mActiveRoutingPath); 583 } 584 } 585 586 /** 587 * Update the active port. 588 * 589 * @param portId the new active port id 590 */ 591 void setActivePortId(int portId) { 592 synchronized (mLock) { 593 // We update active routing path instead, since we get the active port id from 594 // the active routing path. 595 mActiveRoutingPath = mService.portIdToPath(portId); 596 } 597 } 598 599 void updateActiveDevice(int logicalAddress, int physicalAddress) { 600 synchronized (mLock) { 601 mActiveSource = logicalAddress; 602 mActiveRoutingPath = physicalAddress; 603 } 604 } 605 606 @ServiceThreadOnly 607 HdmiCecMessageCache getCecMessageCache() { 608 assertRunOnServiceThread(); 609 return mCecMessageCache; 610 } 611 612 @ServiceThreadOnly 613 int pathToPortId(int newPath) { 614 assertRunOnServiceThread(); 615 return mService.pathToPortId(newPath); 616 } 617 618 /** 619 * Called when the system goes to standby mode. 620 * 621 * @param initiatedByCec true if this power sequence is initiated 622 * by the reception the CEC messages like <Standby> 623 */ 624 protected void onStandby(boolean initiatedByCec) {} 625 626 /** 627 * Disable device. {@code callback} is used to get notified when all pending 628 * actions are completed or timeout is issued. 629 * 630 * @param initiatedByCec true if this sequence is initiated 631 * by the reception the CEC messages like <Standby> 632 * @param origialCallback callback interface to get notified when all pending actions are 633 * cleared 634 */ 635 protected void disableDevice(boolean initiatedByCec, 636 final PendingActionClearedCallback origialCallback) { 637 mPendingActionClearedCallback = new PendingActionClearedCallback() { 638 @Override 639 public void onCleared(HdmiCecLocalDevice device) { 640 mHandler.removeMessages(MSG_DISABLE_DEVICE_TIMEOUT); 641 origialCallback.onCleared(device); 642 } 643 }; 644 mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_DISABLE_DEVICE_TIMEOUT), 645 DEVICE_CLEANUP_TIMEOUT); 646 } 647 648 @ServiceThreadOnly 649 private void handleDisableDeviceTimeout() { 650 assertRunOnServiceThread(); 651 652 // If all actions are not cleared in DEVICE_CLEANUP_TIMEOUT, enforce to finish them. 653 // onCleard will be called at the last action's finish method. 654 Iterator<FeatureAction> iter = mActions.iterator(); 655 while (iter.hasNext()) { 656 FeatureAction action = iter.next(); 657 action.finish(false); 658 iter.remove(); 659 } 660 } 661 662 /** 663 * Send a key event to other device. 664 * 665 * @param keyCode key code defined in {@link android.view.KeyEvent} 666 * @param isPressed {@code true} for key down event 667 */ 668 protected void sendKeyEvent(int keyCode, boolean isPressed) { 669 Slog.w(TAG, "sendKeyEvent not implemented"); 670 } 671} 672