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