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