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