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