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