HdmiCecLocalDevice.java revision 8fa36b110be29d92a9aba070fa4666eefb14b584
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_INITIATE_ARC: 147 return handleInitiateArc(message); 148 case HdmiCec.MESSAGE_TERMINATE_ARC: 149 return handleTerminateArc(message); 150 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 151 return handleSetSystemAudioMode(message); 152 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 153 return handleSystemAudioModeStatus(message); 154 case HdmiCec.MESSAGE_REPORT_AUDIO_STATUS: 155 return handleReportAudioStatus(message); 156 default: 157 return false; 158 } 159 } 160 161 @ServiceThreadOnly 162 private boolean dispatchMessageToAction(HdmiCecMessage message) { 163 assertRunOnServiceThread(); 164 for (FeatureAction action : mActions) { 165 if (action.processCommand(message)) { 166 return true; 167 } 168 } 169 return false; 170 } 171 172 @ServiceThreadOnly 173 protected boolean handleGivePhysicalAddress() { 174 assertRunOnServiceThread(); 175 176 int physicalAddress = mService.getPhysicalAddress(); 177 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 178 mAddress, physicalAddress, mDeviceType); 179 mService.sendCecCommand(cecMessage); 180 return true; 181 } 182 183 @ServiceThreadOnly 184 protected boolean handleGiveDeviceVendorId() { 185 assertRunOnServiceThread(); 186 int vendorId = mService.getVendorId(); 187 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 188 mAddress, vendorId); 189 mService.sendCecCommand(cecMessage); 190 return true; 191 } 192 193 @ServiceThreadOnly 194 protected boolean handleGetCecVersion(HdmiCecMessage message) { 195 assertRunOnServiceThread(); 196 int version = mService.getCecVersion(); 197 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 198 message.getSource(), version); 199 mService.sendCecCommand(cecMessage); 200 return true; 201 } 202 203 @ServiceThreadOnly 204 protected boolean handleActiveSource(HdmiCecMessage message) { 205 return false; 206 } 207 208 @ServiceThreadOnly 209 protected boolean handleInactiveSource(HdmiCecMessage message) { 210 return false; 211 } 212 213 @ServiceThreadOnly 214 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 215 return false; 216 } 217 218 @ServiceThreadOnly 219 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 220 assertRunOnServiceThread(); 221 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 222 mService.sendCecCommand( 223 HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 224 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 225 HdmiConstants.ABORT_UNRECOGNIZED_MODE)); 226 return true; 227 } 228 229 @ServiceThreadOnly 230 protected boolean handleGiveOsdName(HdmiCecMessage message) { 231 assertRunOnServiceThread(); 232 // Note that since this method is called after logical address allocation is done, 233 // mDeviceInfo should not be null. 234 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 235 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 236 if (cecMessage != null) { 237 mService.sendCecCommand(cecMessage); 238 } else { 239 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 240 } 241 return true; 242 } 243 244 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 245 return false; 246 } 247 248 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 249 return false; 250 } 251 252 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 253 return false; 254 } 255 256 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 257 return false; 258 } 259 260 protected boolean handleTerminateArc(HdmiCecMessage message) { 261 return false; 262 } 263 264 protected boolean handleInitiateArc(HdmiCecMessage message) { 265 return false; 266 } 267 268 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 269 return false; 270 } 271 272 @ServiceThreadOnly 273 final void handleAddressAllocated(int logicalAddress) { 274 assertRunOnServiceThread(); 275 mAddress = mPreferredAddress = logicalAddress; 276 onAddressAllocated(logicalAddress); 277 } 278 279 @ServiceThreadOnly 280 HdmiCecDeviceInfo getDeviceInfo() { 281 assertRunOnServiceThread(); 282 return mDeviceInfo; 283 } 284 285 @ServiceThreadOnly 286 void setDeviceInfo(HdmiCecDeviceInfo info) { 287 assertRunOnServiceThread(); 288 mDeviceInfo = info; 289 } 290 291 // Returns true if the logical address is same as the argument. 292 @ServiceThreadOnly 293 boolean isAddressOf(int addr) { 294 assertRunOnServiceThread(); 295 return addr == mAddress; 296 } 297 298 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 299 @ServiceThreadOnly 300 void clearAddress() { 301 assertRunOnServiceThread(); 302 mAddress = HdmiCec.ADDR_UNREGISTERED; 303 } 304 305 @ServiceThreadOnly 306 void setPreferredAddress(int addr) { 307 assertRunOnServiceThread(); 308 mPreferredAddress = addr; 309 } 310 311 @ServiceThreadOnly 312 int getPreferredAddress() { 313 assertRunOnServiceThread(); 314 return mPreferredAddress; 315 } 316 317 @ServiceThreadOnly 318 void addAndStartAction(final FeatureAction action) { 319 assertRunOnServiceThread(); 320 mActions.add(action); 321 action.start(); 322 } 323 324 // See if we have an action of a given type in progress. 325 @ServiceThreadOnly 326 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 327 assertRunOnServiceThread(); 328 for (FeatureAction action : mActions) { 329 if (action.getClass().equals(clazz)) { 330 return true; 331 } 332 } 333 return false; 334 } 335 336 // Returns all actions matched with given class type. 337 @ServiceThreadOnly 338 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 339 assertRunOnServiceThread(); 340 ArrayList<T> actions = new ArrayList<>(); 341 for (FeatureAction action : mActions) { 342 if (action.getClass().equals(clazz)) { 343 actions.add((T) action); 344 } 345 } 346 return actions; 347 } 348 349 /** 350 * Remove the given {@link FeatureAction} object from the action queue. 351 * 352 * @param action {@link FeatureAction} to remove 353 */ 354 @ServiceThreadOnly 355 void removeAction(final FeatureAction action) { 356 assertRunOnServiceThread(); 357 mActions.remove(action); 358 } 359 360 // Remove all actions matched with the given Class type. 361 @ServiceThreadOnly 362 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 363 assertRunOnServiceThread(); 364 removeActionExcept(clazz, null); 365 } 366 367 // Remove all actions matched with the given Class type besides |exception|. 368 @ServiceThreadOnly 369 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 370 final FeatureAction exception) { 371 assertRunOnServiceThread(); 372 Iterator<FeatureAction> iter = mActions.iterator(); 373 while (iter.hasNext()) { 374 FeatureAction action = iter.next(); 375 if (action != exception && action.getClass().equals(clazz)) { 376 action.clear(); 377 mActions.remove(action); 378 } 379 } 380 } 381 382 protected void assertRunOnServiceThread() { 383 if (Looper.myLooper() != mService.getServiceLooper()) { 384 throw new IllegalStateException("Should run on service thread."); 385 } 386 } 387 388 /** 389 * Called when a hot-plug event issued. 390 * 391 * @param portId id of port where a hot-plug event happened 392 * @param connected whether to connected or not on the event 393 */ 394 void onHotplug(int portId, boolean connected) { 395 } 396 397 final HdmiControlService getService() { 398 return mService; 399 } 400 401 @ServiceThreadOnly 402 final boolean isConnectedToArcPort(int path) { 403 assertRunOnServiceThread(); 404 return mService.isConnectedToArcPort(path); 405 } 406 407 int getActiveSource() { 408 synchronized (mLock) { 409 return mActiveSource; 410 } 411 } 412 413 void setActiveSource(int source) { 414 synchronized (mLock) { 415 mActiveSource = source; 416 } 417 } 418 419 int getActivePath() { 420 synchronized (mLock) { 421 return mActiveRoutingPath; 422 } 423 } 424 425 void setActivePath(int path) { 426 synchronized (mLock) { 427 mActiveRoutingPath = path; 428 } 429 } 430 431 /** 432 * Returns the ID of the active HDMI port. The active port is the one that has the active 433 * routing path connected to it directly or indirectly under the device hierarchy. 434 */ 435 int getActivePortId() { 436 synchronized (mLock) { 437 return mService.pathToPortId(mActiveRoutingPath); 438 } 439 } 440 441 /** 442 * Update the active port. 443 * 444 * @param portId the new active port id 445 */ 446 void setActivePortId(int portId) { 447 synchronized (mLock) { 448 // We update active routing path instead, since we get the active port id from 449 // the active routing path. 450 mActiveRoutingPath = mService.portIdToPath(portId); 451 } 452 } 453 454 void updateActiveDevice(int logicalAddress, int physicalAddress) { 455 synchronized (mLock) { 456 mActiveSource = logicalAddress; 457 mActiveRoutingPath = physicalAddress; 458 } 459 } 460 461 void setInputChangeEnabled(boolean enabled) { 462 synchronized (mLock) { 463 mInputChangeEnabled = enabled; 464 } 465 } 466 467 boolean isInPresetInstallationMode() { 468 // TODO: Change this to check the right flag. 469 synchronized (mLock) { 470 return !mInputChangeEnabled; 471 } 472 } 473 474 boolean isHdmiControlEnabled() { 475 synchronized (mLock) { 476 return !mInputChangeEnabled; 477 } 478 } 479 480 /** 481 * Whether the given path is located in the tail of current active path. 482 * 483 * @param path to be tested 484 * @return true if the given path is located in the tail of current active path; otherwise, 485 * false 486 */ 487 // TODO: move this to local device tv. 488 boolean isTailOfActivePath(int path) { 489 synchronized (mLock) { 490 // If active routing path is internal source, return false. 491 if (mActiveRoutingPath == 0) { 492 return false; 493 } 494 for (int i = 12; i >= 0; i -= 4) { 495 int curActivePath = (mActiveRoutingPath >> i) & 0xF; 496 if (curActivePath == 0) { 497 return true; 498 } else { 499 int curPath = (path >> i) & 0xF; 500 if (curPath != curActivePath) { 501 return false; 502 } 503 } 504 } 505 return false; 506 } 507 } 508 509 @ServiceThreadOnly 510 HdmiCecMessageCache getCecMessageCache() { 511 assertRunOnServiceThread(); 512 return mCecMessageCache; 513 } 514 515 @ServiceThreadOnly 516 int pathToPortId(int newPath) { 517 assertRunOnServiceThread(); 518 return mService.pathToPortId(newPath); 519 } 520} 521