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