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