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