HdmiCecLocalDevice.java revision 75a77e7d6cbfc287c6126efd28b338b48b7ea70c
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[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 310 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION 311 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 312 } 313 314 private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { 315 byte[] params = message.getParams(); 316 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 317 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 318 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_OFF_FUNCTION 319 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 320 } 321 322 protected boolean handleTextViewOn(HdmiCecMessage message) { 323 return false; 324 } 325 326 protected boolean handleImageViewOn(HdmiCecMessage message) { 327 return false; 328 } 329 330 protected boolean handleSetStreamPath(HdmiCecMessage message) { 331 return false; 332 } 333 334 protected boolean handleGiveDevicePowerStatus(HdmiCecMessage message) { 335 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPowerStatus( 336 mAddress, message.getSource(), mService.getPowerStatus())); 337 return true; 338 } 339 340 protected boolean handleVendorCommand(HdmiCecMessage message) { 341 mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), 342 message.getParams(), false); 343 return true; 344 } 345 346 protected boolean handleVendorCommandWithId(HdmiCecMessage message) { 347 byte[] params = message.getParams(); 348 int vendorId = HdmiUtils.threeBytesToInt(params); 349 if (vendorId == mService.getVendorId()) { 350 mService.invokeVendorCommandListeners(mDeviceType, message.getSource(), params, true); 351 } else if (message.getDestination() != Constants.ADDR_BROADCAST && 352 message.getSource() != Constants.ADDR_UNREGISTERED) { 353 Slog.v(TAG, "Wrong direct vendor command. Replying with <Feature Abort>"); 354 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 355 message.getSource(), Constants.MESSAGE_VENDOR_COMMAND_WITH_ID, 356 Constants.ABORT_UNRECOGNIZED_MODE)); 357 } else { 358 Slog.v(TAG, "Wrong broadcast vendor command. Ignoring"); 359 } 360 return true; 361 } 362 363 protected boolean handleSetOsdName(HdmiCecMessage message) { 364 // The default behavior of <Set Osd Name> is doing nothing. 365 return true; 366 } 367 368 @ServiceThreadOnly 369 final void handleAddressAllocated(int logicalAddress) { 370 assertRunOnServiceThread(); 371 mAddress = mPreferredAddress = logicalAddress; 372 onAddressAllocated(logicalAddress); 373 } 374 375 @ServiceThreadOnly 376 HdmiCecDeviceInfo getDeviceInfo() { 377 assertRunOnServiceThread(); 378 return mDeviceInfo; 379 } 380 381 @ServiceThreadOnly 382 void setDeviceInfo(HdmiCecDeviceInfo info) { 383 assertRunOnServiceThread(); 384 mDeviceInfo = info; 385 } 386 387 // Returns true if the logical address is same as the argument. 388 @ServiceThreadOnly 389 boolean isAddressOf(int addr) { 390 assertRunOnServiceThread(); 391 return addr == mAddress; 392 } 393 394 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 395 @ServiceThreadOnly 396 void clearAddress() { 397 assertRunOnServiceThread(); 398 mAddress = Constants.ADDR_UNREGISTERED; 399 } 400 401 @ServiceThreadOnly 402 void setPreferredAddress(int addr) { 403 assertRunOnServiceThread(); 404 mPreferredAddress = addr; 405 } 406 407 @ServiceThreadOnly 408 int getPreferredAddress() { 409 assertRunOnServiceThread(); 410 return mPreferredAddress; 411 } 412 413 @ServiceThreadOnly 414 void addAndStartAction(final FeatureAction action) { 415 assertRunOnServiceThread(); 416 if (mService.isPowerStandbyOrTransient()) { 417 Slog.w(TAG, "Skip the action during Standby: " + action); 418 return; 419 } 420 mActions.add(action); 421 action.start(); 422 } 423 424 // See if we have an action of a given type in progress. 425 @ServiceThreadOnly 426 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 427 assertRunOnServiceThread(); 428 for (FeatureAction action : mActions) { 429 if (action.getClass().equals(clazz)) { 430 return true; 431 } 432 } 433 return false; 434 } 435 436 // Returns all actions matched with given class type. 437 @ServiceThreadOnly 438 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 439 assertRunOnServiceThread(); 440 ArrayList<T> actions = new ArrayList<>(); 441 for (FeatureAction action : mActions) { 442 if (action.getClass().equals(clazz)) { 443 actions.add((T) action); 444 } 445 } 446 return actions; 447 } 448 449 /** 450 * Remove the given {@link FeatureAction} object from the action queue. 451 * 452 * @param action {@link FeatureAction} to remove 453 */ 454 @ServiceThreadOnly 455 void removeAction(final FeatureAction action) { 456 assertRunOnServiceThread(); 457 mActions.remove(action); 458 checkIfPendingActionsCleared(); 459 } 460 461 // Remove all actions matched with the given Class type. 462 @ServiceThreadOnly 463 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 464 assertRunOnServiceThread(); 465 removeActionExcept(clazz, null); 466 } 467 468 // Remove all actions matched with the given Class type besides |exception|. 469 @ServiceThreadOnly 470 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 471 final FeatureAction exception) { 472 assertRunOnServiceThread(); 473 Iterator<FeatureAction> iter = mActions.iterator(); 474 while (iter.hasNext()) { 475 FeatureAction action = iter.next(); 476 if (action != exception && action.getClass().equals(clazz)) { 477 action.clear(); 478 mActions.remove(action); 479 } 480 } 481 checkIfPendingActionsCleared(); 482 } 483 484 protected void checkIfPendingActionsCleared() { 485 if (mActions.isEmpty()) { 486 mService.onPendingActionsCleared(); 487 } 488 } 489 protected void assertRunOnServiceThread() { 490 if (Looper.myLooper() != mService.getServiceLooper()) { 491 throw new IllegalStateException("Should run on service thread."); 492 } 493 } 494 495 /** 496 * Called when a hot-plug event issued. 497 * 498 * @param portId id of port where a hot-plug event happened 499 * @param connected whether to connected or not on the event 500 */ 501 void onHotplug(int portId, boolean connected) { 502 } 503 504 final HdmiControlService getService() { 505 return mService; 506 } 507 508 @ServiceThreadOnly 509 final boolean isConnectedToArcPort(int path) { 510 assertRunOnServiceThread(); 511 return mService.isConnectedToArcPort(path); 512 } 513 514 int getActiveSource() { 515 synchronized (mLock) { 516 return mActiveSource; 517 } 518 } 519 520 void setActiveSource(int source) { 521 synchronized (mLock) { 522 mActiveSource = source; 523 } 524 } 525 526 int getActivePath() { 527 synchronized (mLock) { 528 return mActiveRoutingPath; 529 } 530 } 531 532 void setActivePath(int path) { 533 synchronized (mLock) { 534 mActiveRoutingPath = path; 535 } 536 } 537 538 /** 539 * Returns the ID of the active HDMI port. The active port is the one that has the active 540 * routing path connected to it directly or indirectly under the device hierarchy. 541 */ 542 int getActivePortId() { 543 synchronized (mLock) { 544 return mService.pathToPortId(mActiveRoutingPath); 545 } 546 } 547 548 /** 549 * Update the active port. 550 * 551 * @param portId the new active port id 552 */ 553 void setActivePortId(int portId) { 554 synchronized (mLock) { 555 // We update active routing path instead, since we get the active port id from 556 // the active routing path. 557 mActiveRoutingPath = mService.portIdToPath(portId); 558 } 559 } 560 561 void updateActiveDevice(int logicalAddress, int physicalAddress) { 562 synchronized (mLock) { 563 mActiveSource = logicalAddress; 564 mActiveRoutingPath = physicalAddress; 565 } 566 } 567 568 @ServiceThreadOnly 569 HdmiCecMessageCache getCecMessageCache() { 570 assertRunOnServiceThread(); 571 return mCecMessageCache; 572 } 573 574 @ServiceThreadOnly 575 int pathToPortId(int newPath) { 576 assertRunOnServiceThread(); 577 return mService.pathToPortId(newPath); 578 } 579 580 /** 581 * Called when the system started transition to standby mode. 582 * 583 * @param initiatedByCec true if this power sequence is initiated 584 * by the reception the CEC messages like <StandBy> 585 */ 586 protected void onTransitionToStandby(boolean initiatedByCec) { 587 // If there are no outstanding actions, we'll go to STANDBY state. 588 checkIfPendingActionsCleared(); 589 } 590 591 /** 592 * Called when the system goes to standby mode. 593 * 594 * @param initiatedByCec true if this power sequence is initiated 595 * by the reception the CEC messages like <StandBy> 596 */ 597 protected void onStandBy(boolean initiatedByCec) {} 598} 599