HdmiCecLocalDevice.java revision 119160a68195bcb2f5bdf4a269807e01228eca97
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 default: 164 return false; 165 } 166 } 167 168 @ServiceThreadOnly 169 private boolean dispatchMessageToAction(HdmiCecMessage message) { 170 assertRunOnServiceThread(); 171 for (FeatureAction action : mActions) { 172 if (action.processCommand(message)) { 173 return true; 174 } 175 } 176 return false; 177 } 178 179 @ServiceThreadOnly 180 protected boolean handleGivePhysicalAddress() { 181 assertRunOnServiceThread(); 182 183 int physicalAddress = mService.getPhysicalAddress(); 184 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 185 mAddress, physicalAddress, mDeviceType); 186 mService.sendCecCommand(cecMessage); 187 return true; 188 } 189 190 @ServiceThreadOnly 191 protected boolean handleGiveDeviceVendorId() { 192 assertRunOnServiceThread(); 193 int vendorId = mService.getVendorId(); 194 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 195 mAddress, vendorId); 196 mService.sendCecCommand(cecMessage); 197 return true; 198 } 199 200 @ServiceThreadOnly 201 protected boolean handleGetCecVersion(HdmiCecMessage message) { 202 assertRunOnServiceThread(); 203 int version = mService.getCecVersion(); 204 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 205 message.getSource(), version); 206 mService.sendCecCommand(cecMessage); 207 return true; 208 } 209 210 @ServiceThreadOnly 211 protected boolean handleActiveSource(HdmiCecMessage message) { 212 return false; 213 } 214 215 @ServiceThreadOnly 216 protected boolean handleInactiveSource(HdmiCecMessage message) { 217 return false; 218 } 219 220 @ServiceThreadOnly 221 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 222 return false; 223 } 224 225 @ServiceThreadOnly 226 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 227 assertRunOnServiceThread(); 228 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 229 mService.sendCecCommand( 230 HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 231 message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE, 232 Constants.ABORT_UNRECOGNIZED_MODE)); 233 return true; 234 } 235 236 @ServiceThreadOnly 237 protected boolean handleGiveOsdName(HdmiCecMessage message) { 238 assertRunOnServiceThread(); 239 // Note that since this method is called after logical address allocation is done, 240 // mDeviceInfo should not be null. 241 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 242 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 243 if (cecMessage != null) { 244 mService.sendCecCommand(cecMessage); 245 } else { 246 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 247 } 248 return true; 249 } 250 251 protected boolean handleRoutingChange(HdmiCecMessage message) { 252 return false; 253 } 254 255 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 256 return false; 257 } 258 259 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 260 return false; 261 } 262 263 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 264 return false; 265 } 266 267 protected boolean handleTerminateArc(HdmiCecMessage message) { 268 return false; 269 } 270 271 protected boolean handleInitiateArc(HdmiCecMessage message) { 272 return false; 273 } 274 275 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 276 return false; 277 } 278 279 @ServiceThreadOnly 280 protected boolean handleStandby(HdmiCecMessage message) { 281 assertRunOnServiceThread(); 282 // Seq #12 283 if (mService.isControlEnabled() && !mService.isProhibitMode() 284 && mService.isPowerOnOrTransient()) { 285 mService.standby(); 286 return true; 287 } 288 return false; 289 } 290 291 @ServiceThreadOnly 292 protected boolean handleUserControlPressed(HdmiCecMessage message) { 293 assertRunOnServiceThread(); 294 if (mService.isPowerOnOrTransient() && isPowerOffOrToggleCommand(message)) { 295 mService.standby(); 296 return true; 297 } else if (mService.isPowerStandbyOrTransient() && isPowerOnOrToggleCommand(message)) { 298 mService.wakeUp(); 299 return true; 300 } 301 return false; 302 } 303 304 private static boolean isPowerOnOrToggleCommand(HdmiCecMessage message) { 305 byte[] params = message.getParams(); 306 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 307 && params.length == 1 308 && (params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER 309 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION 310 || params[0] == HdmiCecKeycode.CEC_KEYCODE_POWER_TOGGLE_FUNCTION); 311 } 312 313 private static boolean isPowerOffOrToggleCommand(HdmiCecMessage message) { 314 byte[] params = message.getParams(); 315 return message.getOpcode() == Constants.MESSAGE_USER_CONTROL_PRESSED 316 && params.length == 1 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 @ServiceThreadOnly 364 final void handleAddressAllocated(int logicalAddress) { 365 assertRunOnServiceThread(); 366 mAddress = mPreferredAddress = logicalAddress; 367 onAddressAllocated(logicalAddress); 368 } 369 370 @ServiceThreadOnly 371 HdmiCecDeviceInfo getDeviceInfo() { 372 assertRunOnServiceThread(); 373 return mDeviceInfo; 374 } 375 376 @ServiceThreadOnly 377 void setDeviceInfo(HdmiCecDeviceInfo info) { 378 assertRunOnServiceThread(); 379 mDeviceInfo = info; 380 } 381 382 // Returns true if the logical address is same as the argument. 383 @ServiceThreadOnly 384 boolean isAddressOf(int addr) { 385 assertRunOnServiceThread(); 386 return addr == mAddress; 387 } 388 389 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 390 @ServiceThreadOnly 391 void clearAddress() { 392 assertRunOnServiceThread(); 393 mAddress = Constants.ADDR_UNREGISTERED; 394 } 395 396 @ServiceThreadOnly 397 void setPreferredAddress(int addr) { 398 assertRunOnServiceThread(); 399 mPreferredAddress = addr; 400 } 401 402 @ServiceThreadOnly 403 int getPreferredAddress() { 404 assertRunOnServiceThread(); 405 return mPreferredAddress; 406 } 407 408 @ServiceThreadOnly 409 void addAndStartAction(final FeatureAction action) { 410 assertRunOnServiceThread(); 411 if (mService.isPowerStandbyOrTransient()) { 412 Slog.w(TAG, "Skip the action during Standby: " + action); 413 return; 414 } 415 mActions.add(action); 416 action.start(); 417 } 418 419 // See if we have an action of a given type in progress. 420 @ServiceThreadOnly 421 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 422 assertRunOnServiceThread(); 423 for (FeatureAction action : mActions) { 424 if (action.getClass().equals(clazz)) { 425 return true; 426 } 427 } 428 return false; 429 } 430 431 // Returns all actions matched with given class type. 432 @ServiceThreadOnly 433 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 434 assertRunOnServiceThread(); 435 ArrayList<T> actions = new ArrayList<>(); 436 for (FeatureAction action : mActions) { 437 if (action.getClass().equals(clazz)) { 438 actions.add((T) action); 439 } 440 } 441 return actions; 442 } 443 444 /** 445 * Remove the given {@link FeatureAction} object from the action queue. 446 * 447 * @param action {@link FeatureAction} to remove 448 */ 449 @ServiceThreadOnly 450 void removeAction(final FeatureAction action) { 451 assertRunOnServiceThread(); 452 mActions.remove(action); 453 checkIfPendingActionsCleared(); 454 } 455 456 // Remove all actions matched with the given Class type. 457 @ServiceThreadOnly 458 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 459 assertRunOnServiceThread(); 460 removeActionExcept(clazz, null); 461 } 462 463 // Remove all actions matched with the given Class type besides |exception|. 464 @ServiceThreadOnly 465 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 466 final FeatureAction exception) { 467 assertRunOnServiceThread(); 468 Iterator<FeatureAction> iter = mActions.iterator(); 469 while (iter.hasNext()) { 470 FeatureAction action = iter.next(); 471 if (action != exception && action.getClass().equals(clazz)) { 472 action.clear(); 473 mActions.remove(action); 474 } 475 } 476 checkIfPendingActionsCleared(); 477 } 478 479 protected void checkIfPendingActionsCleared() { 480 if (mActions.isEmpty()) { 481 mService.onPendingActionsCleared(); 482 } 483 } 484 protected void assertRunOnServiceThread() { 485 if (Looper.myLooper() != mService.getServiceLooper()) { 486 throw new IllegalStateException("Should run on service thread."); 487 } 488 } 489 490 /** 491 * Called when a hot-plug event issued. 492 * 493 * @param portId id of port where a hot-plug event happened 494 * @param connected whether to connected or not on the event 495 */ 496 void onHotplug(int portId, boolean connected) { 497 } 498 499 final HdmiControlService getService() { 500 return mService; 501 } 502 503 @ServiceThreadOnly 504 final boolean isConnectedToArcPort(int path) { 505 assertRunOnServiceThread(); 506 return mService.isConnectedToArcPort(path); 507 } 508 509 int getActiveSource() { 510 synchronized (mLock) { 511 return mActiveSource; 512 } 513 } 514 515 void setActiveSource(int source) { 516 synchronized (mLock) { 517 mActiveSource = source; 518 } 519 } 520 521 int getActivePath() { 522 synchronized (mLock) { 523 return mActiveRoutingPath; 524 } 525 } 526 527 void setActivePath(int path) { 528 synchronized (mLock) { 529 mActiveRoutingPath = path; 530 } 531 } 532 533 /** 534 * Returns the ID of the active HDMI port. The active port is the one that has the active 535 * routing path connected to it directly or indirectly under the device hierarchy. 536 */ 537 int getActivePortId() { 538 synchronized (mLock) { 539 return mService.pathToPortId(mActiveRoutingPath); 540 } 541 } 542 543 /** 544 * Update the active port. 545 * 546 * @param portId the new active port id 547 */ 548 void setActivePortId(int portId) { 549 synchronized (mLock) { 550 // We update active routing path instead, since we get the active port id from 551 // the active routing path. 552 mActiveRoutingPath = mService.portIdToPath(portId); 553 } 554 } 555 556 void updateActiveDevice(int logicalAddress, int physicalAddress) { 557 synchronized (mLock) { 558 mActiveSource = logicalAddress; 559 mActiveRoutingPath = physicalAddress; 560 } 561 } 562 563 @ServiceThreadOnly 564 HdmiCecMessageCache getCecMessageCache() { 565 assertRunOnServiceThread(); 566 return mCecMessageCache; 567 } 568 569 @ServiceThreadOnly 570 int pathToPortId(int newPath) { 571 assertRunOnServiceThread(); 572 return mService.pathToPortId(newPath); 573 } 574 575 /** 576 * Called when the system started transition to standby mode. 577 * 578 * @param initiatedByCec true if this power sequence is initiated 579 * by the reception the CEC messages like <StandBy> 580 */ 581 protected void onTransitionToStandby(boolean initiatedByCec) { 582 // If there are no outstanding actions, we'll go to STANDBY state. 583 checkIfPendingActionsCleared(); 584 } 585 586 /** 587 * Called when the system goes to standby mode. 588 * 589 * @param initiatedByCec true if this power sequence is initiated 590 * by the reception the CEC messages like <StandBy> 591 */ 592 protected void onStandBy(boolean initiatedByCec) {} 593} 594