HdmiCecLocalDevice.java revision c0c20d0522d7756d80f011e7a54bf3b51c78df41
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 default: 160 return false; 161 } 162 } 163 164 @ServiceThreadOnly 165 private boolean dispatchMessageToAction(HdmiCecMessage message) { 166 assertRunOnServiceThread(); 167 for (FeatureAction action : mActions) { 168 if (action.processCommand(message)) { 169 return true; 170 } 171 } 172 return false; 173 } 174 175 @ServiceThreadOnly 176 protected boolean handleGivePhysicalAddress() { 177 assertRunOnServiceThread(); 178 179 int physicalAddress = mService.getPhysicalAddress(); 180 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 181 mAddress, physicalAddress, mDeviceType); 182 mService.sendCecCommand(cecMessage); 183 return true; 184 } 185 186 @ServiceThreadOnly 187 protected boolean handleGiveDeviceVendorId() { 188 assertRunOnServiceThread(); 189 int vendorId = mService.getVendorId(); 190 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 191 mAddress, vendorId); 192 mService.sendCecCommand(cecMessage); 193 return true; 194 } 195 196 @ServiceThreadOnly 197 protected boolean handleGetCecVersion(HdmiCecMessage message) { 198 assertRunOnServiceThread(); 199 int version = mService.getCecVersion(); 200 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 201 message.getSource(), version); 202 mService.sendCecCommand(cecMessage); 203 return true; 204 } 205 206 @ServiceThreadOnly 207 protected boolean handleActiveSource(HdmiCecMessage message) { 208 return false; 209 } 210 211 @ServiceThreadOnly 212 protected boolean handleInactiveSource(HdmiCecMessage message) { 213 return false; 214 } 215 216 @ServiceThreadOnly 217 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 218 return false; 219 } 220 221 @ServiceThreadOnly 222 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 223 assertRunOnServiceThread(); 224 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 225 mService.sendCecCommand( 226 HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 227 message.getSource(), Constants.MESSAGE_GET_MENU_LANGUAGE, 228 Constants.ABORT_UNRECOGNIZED_MODE)); 229 return true; 230 } 231 232 @ServiceThreadOnly 233 protected boolean handleGiveOsdName(HdmiCecMessage message) { 234 assertRunOnServiceThread(); 235 // Note that since this method is called after logical address allocation is done, 236 // mDeviceInfo should not be null. 237 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 238 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 239 if (cecMessage != null) { 240 mService.sendCecCommand(cecMessage); 241 } else { 242 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 243 } 244 return true; 245 } 246 247 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 248 return false; 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 @ServiceThreadOnly 341 final void handleAddressAllocated(int logicalAddress) { 342 assertRunOnServiceThread(); 343 mAddress = mPreferredAddress = logicalAddress; 344 onAddressAllocated(logicalAddress); 345 } 346 347 @ServiceThreadOnly 348 HdmiCecDeviceInfo getDeviceInfo() { 349 assertRunOnServiceThread(); 350 return mDeviceInfo; 351 } 352 353 @ServiceThreadOnly 354 void setDeviceInfo(HdmiCecDeviceInfo info) { 355 assertRunOnServiceThread(); 356 mDeviceInfo = info; 357 } 358 359 // Returns true if the logical address is same as the argument. 360 @ServiceThreadOnly 361 boolean isAddressOf(int addr) { 362 assertRunOnServiceThread(); 363 return addr == mAddress; 364 } 365 366 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 367 @ServiceThreadOnly 368 void clearAddress() { 369 assertRunOnServiceThread(); 370 mAddress = Constants.ADDR_UNREGISTERED; 371 } 372 373 @ServiceThreadOnly 374 void setPreferredAddress(int addr) { 375 assertRunOnServiceThread(); 376 mPreferredAddress = addr; 377 } 378 379 @ServiceThreadOnly 380 int getPreferredAddress() { 381 assertRunOnServiceThread(); 382 return mPreferredAddress; 383 } 384 385 @ServiceThreadOnly 386 void addAndStartAction(final FeatureAction action) { 387 assertRunOnServiceThread(); 388 if (mService.isPowerStandbyOrTransient()) { 389 Slog.w(TAG, "Skip the action during Standby: " + action); 390 return; 391 } 392 mActions.add(action); 393 action.start(); 394 } 395 396 // See if we have an action of a given type in progress. 397 @ServiceThreadOnly 398 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 399 assertRunOnServiceThread(); 400 for (FeatureAction action : mActions) { 401 if (action.getClass().equals(clazz)) { 402 return true; 403 } 404 } 405 return false; 406 } 407 408 // Returns all actions matched with given class type. 409 @ServiceThreadOnly 410 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 411 assertRunOnServiceThread(); 412 ArrayList<T> actions = new ArrayList<>(); 413 for (FeatureAction action : mActions) { 414 if (action.getClass().equals(clazz)) { 415 actions.add((T) action); 416 } 417 } 418 return actions; 419 } 420 421 /** 422 * Remove the given {@link FeatureAction} object from the action queue. 423 * 424 * @param action {@link FeatureAction} to remove 425 */ 426 @ServiceThreadOnly 427 void removeAction(final FeatureAction action) { 428 assertRunOnServiceThread(); 429 mActions.remove(action); 430 checkIfPendingActionsCleared(); 431 } 432 433 // Remove all actions matched with the given Class type. 434 @ServiceThreadOnly 435 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 436 assertRunOnServiceThread(); 437 removeActionExcept(clazz, null); 438 } 439 440 // Remove all actions matched with the given Class type besides |exception|. 441 @ServiceThreadOnly 442 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 443 final FeatureAction exception) { 444 assertRunOnServiceThread(); 445 Iterator<FeatureAction> iter = mActions.iterator(); 446 while (iter.hasNext()) { 447 FeatureAction action = iter.next(); 448 if (action != exception && action.getClass().equals(clazz)) { 449 action.clear(); 450 mActions.remove(action); 451 } 452 } 453 checkIfPendingActionsCleared(); 454 } 455 456 protected void checkIfPendingActionsCleared() { 457 if (mActions.isEmpty()) { 458 mService.onPendingActionsCleared(); 459 } 460 } 461 protected void assertRunOnServiceThread() { 462 if (Looper.myLooper() != mService.getServiceLooper()) { 463 throw new IllegalStateException("Should run on service thread."); 464 } 465 } 466 467 /** 468 * Called when a hot-plug event issued. 469 * 470 * @param portId id of port where a hot-plug event happened 471 * @param connected whether to connected or not on the event 472 */ 473 void onHotplug(int portId, boolean connected) { 474 } 475 476 final HdmiControlService getService() { 477 return mService; 478 } 479 480 @ServiceThreadOnly 481 final boolean isConnectedToArcPort(int path) { 482 assertRunOnServiceThread(); 483 return mService.isConnectedToArcPort(path); 484 } 485 486 int getActiveSource() { 487 synchronized (mLock) { 488 return mActiveSource; 489 } 490 } 491 492 void setActiveSource(int source) { 493 synchronized (mLock) { 494 mActiveSource = source; 495 } 496 } 497 498 int getActivePath() { 499 synchronized (mLock) { 500 return mActiveRoutingPath; 501 } 502 } 503 504 void setActivePath(int path) { 505 synchronized (mLock) { 506 mActiveRoutingPath = path; 507 } 508 } 509 510 /** 511 * Returns the ID of the active HDMI port. The active port is the one that has the active 512 * routing path connected to it directly or indirectly under the device hierarchy. 513 */ 514 int getActivePortId() { 515 synchronized (mLock) { 516 return mService.pathToPortId(mActiveRoutingPath); 517 } 518 } 519 520 /** 521 * Update the active port. 522 * 523 * @param portId the new active port id 524 */ 525 void setActivePortId(int portId) { 526 synchronized (mLock) { 527 // We update active routing path instead, since we get the active port id from 528 // the active routing path. 529 mActiveRoutingPath = mService.portIdToPath(portId); 530 } 531 } 532 533 void updateActiveDevice(int logicalAddress, int physicalAddress) { 534 synchronized (mLock) { 535 mActiveSource = logicalAddress; 536 mActiveRoutingPath = physicalAddress; 537 } 538 } 539 540 @ServiceThreadOnly 541 HdmiCecMessageCache getCecMessageCache() { 542 assertRunOnServiceThread(); 543 return mCecMessageCache; 544 } 545 546 @ServiceThreadOnly 547 int pathToPortId(int newPath) { 548 assertRunOnServiceThread(); 549 return mService.pathToPortId(newPath); 550 } 551 552 /** 553 * Called when the system started transition to standby mode. 554 * 555 * @param initiatedByCec true if this power sequence is initiated 556 * by the reception the CEC messages like <StandBy> 557 */ 558 protected void onTransitionToStandby(boolean initiatedByCec) { 559 // If there are no outstanding actions, we'll go to STANDBY state. 560 checkIfPendingActionsCleared(); 561 } 562 563 /** 564 * Called when the system goes to standby mode. 565 * 566 * @param initiatedByCec true if this power sequence is initiated 567 * by the reception the CEC messages like <StandBy> 568 */ 569 protected void onStandBy(boolean initiatedByCec) {} 570} 571