HdmiCecLocalDevice.java revision a062a9339add79a84862a34e363e3e454a6ec435
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; 26 27import java.util.ArrayList; 28import java.util.Iterator; 29import java.util.LinkedList; 30import java.util.List; 31 32/** 33 * Class that models a logical CEC device hosted in this system. Handles initialization, 34 * CEC commands that call for actions customized per device type. 35 */ 36abstract class HdmiCecLocalDevice { 37 private static final String TAG = "HdmiCecLocalDevice"; 38 39 protected final HdmiControlService mService; 40 protected final int mDeviceType; 41 protected int mAddress; 42 protected int mPreferredAddress; 43 protected HdmiCecDeviceInfo mDeviceInfo; 44 45 // Logical address of the active source. 46 @GuardedBy("mLock") 47 private int mActiveSource; 48 49 // Active routing path. Physical address of the active source but not all the time, such as 50 // when the new active source does not claim itself to be one. Note that we don't keep 51 // the active port id (or active input) since it can be gotten by {@link #pathToPortId(int)}. 52 @GuardedBy("mLock") 53 private int mActiveRoutingPath; 54 55 // Set to true while the service is in normal mode. While set to false, no input change is 56 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 57 // system upgrade, etc., a.k.a. "prohibit mode". 58 @GuardedBy("mLock") 59 private boolean mInputChangeEnabled; 60 61 protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 62 protected final Object mLock; 63 64 // A collection of FeatureAction. 65 // Note that access to this collection should happen in service thread. 66 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 67 68 protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) { 69 mService = service; 70 mDeviceType = deviceType; 71 mAddress = HdmiCec.ADDR_UNREGISTERED; 72 mLock = service.getServiceLock(); 73 74 // TODO: Get control flag from persistent storage 75 mInputChangeEnabled = true; 76 } 77 78 // Factory method that returns HdmiCecLocalDevice of corresponding type. 79 static HdmiCecLocalDevice create(HdmiControlService service, int deviceType) { 80 switch (deviceType) { 81 case HdmiCec.DEVICE_TV: 82 return new HdmiCecLocalDeviceTv(service); 83 case HdmiCec.DEVICE_PLAYBACK: 84 return new HdmiCecLocalDevicePlayback(service); 85 default: 86 return null; 87 } 88 } 89 90 void init() { 91 mPreferredAddress = HdmiCec.ADDR_UNREGISTERED; 92 // TODO: load preferred address from permanent storage. 93 } 94 95 /** 96 * Called once a logical address of the local device is allocated. 97 */ 98 protected abstract void onAddressAllocated(int logicalAddress); 99 100 /** 101 * Dispatch incoming message. 102 * 103 * @param message incoming message 104 * @return true if consumed a message; otherwise, return false. 105 */ 106 final boolean dispatchMessage(HdmiCecMessage message) { 107 assertRunOnServiceThread(); 108 109 int dest = message.getDestination(); 110 if (dest != mAddress && dest != HdmiCec.ADDR_BROADCAST) { 111 return false; 112 } 113 // Cache incoming message. Note that it caches only white-listed one. 114 mCecMessageCache.cacheMessage(message); 115 return onMessage(message); 116 } 117 118 protected final boolean onMessage(HdmiCecMessage message) { 119 assertRunOnServiceThread(); 120 121 if (dispatchMessageToAction(message)) { 122 return true; 123 } 124 switch (message.getOpcode()) { 125 case HdmiCec.MESSAGE_GET_MENU_LANGUAGE: 126 return handleGetMenuLanguage(message); 127 case HdmiCec.MESSAGE_GIVE_PHYSICAL_ADDRESS: 128 return handleGivePhysicalAddress(); 129 case HdmiCec.MESSAGE_GIVE_OSD_NAME: 130 return handleGiveOsdName(message); 131 case HdmiCec.MESSAGE_GIVE_DEVICE_VENDOR_ID: 132 return handleGiveDeviceVendorId(); 133 case HdmiCec.MESSAGE_GET_CEC_VERSION: 134 return handleGetCecVersion(message); 135 case HdmiCec.MESSAGE_REPORT_PHYSICAL_ADDRESS: 136 return handleReportPhysicalAddress(message); 137 case HdmiCec.MESSAGE_INITIATE_ARC: 138 return handleInitiateArc(message); 139 case HdmiCec.MESSAGE_TERMINATE_ARC: 140 return handleTerminateArc(message); 141 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 142 return handleSetSystemAudioMode(message); 143 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 144 return handleSystemAudioModeStatus(message); 145 default: 146 return false; 147 } 148 } 149 150 private boolean dispatchMessageToAction(HdmiCecMessage message) { 151 for (FeatureAction action : mActions) { 152 if (action.processCommand(message)) { 153 return true; 154 } 155 } 156 return false; 157 } 158 159 protected boolean handleGivePhysicalAddress() { 160 assertRunOnServiceThread(); 161 162 int physicalAddress = mService.getPhysicalAddress(); 163 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 164 mAddress, physicalAddress, mDeviceType); 165 mService.sendCecCommand(cecMessage); 166 return true; 167 } 168 169 protected boolean handleGiveDeviceVendorId() { 170 assertRunOnServiceThread(); 171 172 int vendorId = mService.getVendorId(); 173 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 174 mAddress, vendorId); 175 mService.sendCecCommand(cecMessage); 176 return true; 177 } 178 179 protected boolean handleGetCecVersion(HdmiCecMessage message) { 180 assertRunOnServiceThread(); 181 182 int version = mService.getCecVersion(); 183 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildCecVersion(message.getDestination(), 184 message.getSource(), version); 185 mService.sendCecCommand(cecMessage); 186 return true; 187 } 188 189 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 190 assertRunOnServiceThread(); 191 192 Slog.w(TAG, "Only TV can handle <Get Menu Language>:" + message.toString()); 193 mService.sendCecCommand( 194 HdmiCecMessageBuilder.buildFeatureAbortCommand(mAddress, 195 message.getSource(), HdmiCec.MESSAGE_GET_MENU_LANGUAGE, 196 HdmiConstants.ABORT_UNRECOGNIZED_MODE)); 197 return true; 198 } 199 200 protected boolean handleGiveOsdName(HdmiCecMessage message) { 201 assertRunOnServiceThread(); 202 203 // Note that since this method is called after logical address allocation is done, 204 // mDeviceInfo should not be null. 205 HdmiCecMessage cecMessage = HdmiCecMessageBuilder.buildSetOsdNameCommand( 206 mAddress, message.getSource(), mDeviceInfo.getDisplayName()); 207 if (cecMessage != null) { 208 mService.sendCecCommand(cecMessage); 209 } else { 210 Slog.w(TAG, "Failed to build <Get Osd Name>:" + mDeviceInfo.getDisplayName()); 211 } 212 return true; 213 } 214 215 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 216 return false; 217 } 218 219 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 220 return false; 221 } 222 223 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 224 return false; 225 } 226 227 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 228 return false; 229 } 230 231 protected boolean handleTerminateArc(HdmiCecMessage message) { 232 return false; 233 } 234 235 protected boolean handleInitiateArc(HdmiCecMessage message) { 236 return false; 237 } 238 239 final void handleAddressAllocated(int logicalAddress) { 240 assertRunOnServiceThread(); 241 242 mAddress = mPreferredAddress = logicalAddress; 243 onAddressAllocated(logicalAddress); 244 } 245 246 HdmiCecDeviceInfo getDeviceInfo() { 247 assertRunOnServiceThread(); 248 return mDeviceInfo; 249 } 250 251 void setDeviceInfo(HdmiCecDeviceInfo info) { 252 assertRunOnServiceThread(); 253 mDeviceInfo = info; 254 } 255 256 // Returns true if the logical address is same as the argument. 257 boolean isAddressOf(int addr) { 258 assertRunOnServiceThread(); 259 return addr == mAddress; 260 } 261 262 // Resets the logical address to unregistered(15), meaning the logical device is invalid. 263 void clearAddress() { 264 assertRunOnServiceThread(); 265 mAddress = HdmiCec.ADDR_UNREGISTERED; 266 } 267 268 void setPreferredAddress(int addr) { 269 assertRunOnServiceThread(); 270 mPreferredAddress = addr; 271 } 272 273 int getPreferredAddress() { 274 assertRunOnServiceThread(); 275 return mPreferredAddress; 276 } 277 278 void addAndStartAction(final FeatureAction action) { 279 assertRunOnServiceThread(); 280 mActions.add(action); 281 action.start(); 282 } 283 284 // See if we have an action of a given type in progress. 285 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 286 assertRunOnServiceThread(); 287 for (FeatureAction action : mActions) { 288 if (action.getClass().equals(clazz)) { 289 return true; 290 } 291 } 292 return false; 293 } 294 295 // Returns all actions matched with given class type. 296 <T extends FeatureAction> List<T> getActions(final Class<T> clazz) { 297 assertRunOnServiceThread(); 298 ArrayList<T> actions = new ArrayList<>(); 299 for (FeatureAction action : mActions) { 300 if (action.getClass().equals(clazz)) { 301 actions.add((T) action); 302 } 303 } 304 return actions; 305 } 306 307 /** 308 * Remove the given {@link FeatureAction} object from the action queue. 309 * 310 * @param action {@link FeatureAction} to remove 311 */ 312 void removeAction(final FeatureAction action) { 313 assertRunOnServiceThread(); 314 mActions.remove(action); 315 } 316 317 // Remove all actions matched with the given Class type. 318 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 319 removeActionExcept(clazz, null); 320 } 321 322 // Remove all actions matched with the given Class type besides |exception|. 323 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 324 final FeatureAction exception) { 325 assertRunOnServiceThread(); 326 Iterator<FeatureAction> iter = mActions.iterator(); 327 while (iter.hasNext()) { 328 FeatureAction action = iter.next(); 329 if (action != exception && action.getClass().equals(clazz)) { 330 action.clear(); 331 mActions.remove(action); 332 } 333 } 334 } 335 336 protected void assertRunOnServiceThread() { 337 if (Looper.myLooper() != mService.getServiceLooper()) { 338 throw new IllegalStateException("Should run on service thread."); 339 } 340 } 341 342 /** 343 * Called when a hot-plug event issued. 344 * 345 * @param portId id of port where a hot-plug event happened 346 * @param connected whether to connected or not on the event 347 */ 348 void onHotplug(int portId, boolean connected) { 349 } 350 351 final HdmiControlService getService() { 352 return mService; 353 } 354 355 final boolean isConnectedToArcPort(int path) { 356 return mService.isConnectedToArcPort(path); 357 } 358 359 int getActiveSource() { 360 synchronized (mLock) { 361 return mActiveSource; 362 } 363 } 364 365 /** 366 * Returns the active routing path. 367 */ 368 int getActivePath() { 369 synchronized (mLock) { 370 return mActiveRoutingPath; 371 } 372 } 373 374 /** 375 * Returns the ID of the active HDMI port. The active port is the one that has the active 376 * routing path connected to it directly or indirectly under the device hierarchy. 377 */ 378 int getActivePortId() { 379 synchronized (mLock) { 380 return mService.pathToPortId(mActiveRoutingPath); 381 } 382 } 383 384 /** 385 * Update the active port. 386 * 387 * @param portId the new active port id 388 */ 389 void setActivePortId(int portId) { 390 synchronized (mLock) { 391 // We update active routing path instead, since we get the active port id from 392 // the active routing path. 393 mActiveRoutingPath = mService.portIdToPath(portId); 394 } 395 } 396 397 void updateActiveDevice(int logicalAddress, int physicalAddress) { 398 synchronized (mLock) { 399 mActiveSource = logicalAddress; 400 mActiveRoutingPath = physicalAddress; 401 } 402 } 403 404 void setInputChangeEnabled(boolean enabled) { 405 synchronized (mLock) { 406 mInputChangeEnabled = enabled; 407 } 408 } 409 410 boolean isInPresetInstallationMode() { 411 synchronized (mLock) { 412 return !mInputChangeEnabled; 413 } 414 } 415 416 /** 417 * Whether the given path is located in the tail of current active path. 418 * 419 * @param path to be tested 420 * @return true if the given path is located in the tail of current active path; otherwise, 421 * false 422 */ 423 // TODO: move this to local device tv. 424 boolean isTailOfActivePath(int path) { 425 synchronized (mLock) { 426 // If active routing path is internal source, return false. 427 if (mActiveRoutingPath == 0) { 428 return false; 429 } 430 for (int i = 12; i >= 0; i -= 4) { 431 int curActivePath = (mActiveRoutingPath >> i) & 0xF; 432 if (curActivePath == 0) { 433 return true; 434 } else { 435 int curPath = (path >> i) & 0xF; 436 if (curPath != curActivePath) { 437 return false; 438 } 439 } 440 } 441 return false; 442 } 443 } 444 445 HdmiCecMessageCache getCecMessageCache() { 446 assertRunOnServiceThread(); 447 return mCecMessageCache; 448 } 449 450 int pathToPortId(int newPath) { 451 assertRunOnServiceThread(); 452 return mService.pathToPortId(newPath); 453 } 454} 455