HdmiCecLocalDeviceTv.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.hardware.hdmi.IHdmiControlCallback; 23import android.os.RemoteException; 24import android.util.Slog; 25import android.util.SparseArray; 26 27import com.android.internal.annotations.GuardedBy; 28import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 29 30import java.util.ArrayList; 31import java.util.Collections; 32import java.util.List; 33import java.util.Locale; 34 35/** 36 * Represent a logical device of type TV residing in Android system. 37 */ 38final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 39 private static final String TAG = "HdmiCecLocalDeviceTv"; 40 41 // Whether ARC is "enabled" or not. 42 @GuardedBy("mLock") 43 private boolean mArcStatusEnabled = false; 44 45 @GuardedBy("mLock") 46 // Whether SystemAudioMode is "On" or not. 47 private boolean mSystemAudioMode; 48 49 // Map-like container of all cec devices including local ones. 50 // A logical address of device is used as key of container. 51 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>(); 52 53 HdmiCecLocalDeviceTv(HdmiControlService service) { 54 super(service, HdmiCec.DEVICE_TV); 55 56 // TODO: load system audio mode and set it to mSystemAudioMode. 57 } 58 59 @Override 60 protected void onAddressAllocated(int logicalAddress) { 61 assertRunOnServiceThread(); 62 // TODO: vendor-specific initialization here. 63 64 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 65 mAddress, mService.getPhysicalAddress(), mDeviceType)); 66 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 67 mAddress, mService.getVendorId())); 68 69 launchDeviceDiscovery(); 70 // TODO: Start routing control action, device discovery action. 71 } 72 73 /** 74 * Performs the action 'device select', or 'one touch play' initiated by TV. 75 * 76 * @param targetAddress logical address of the device to select 77 * @param callback callback object to report the result with 78 */ 79 void deviceSelect(int targetAddress, IHdmiControlCallback callback) { 80 assertRunOnServiceThread(); 81 HdmiCecDeviceInfo targetDevice = mService.getDeviceInfo(targetAddress); 82 if (targetDevice == null) { 83 invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE); 84 return; 85 } 86 removeAction(DeviceSelectAction.class); 87 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); 88 } 89 90 /** 91 * Performs the action routing control. 92 * 93 * @param portId new HDMI port to route to 94 * @param callback callback object to report the result with 95 */ 96 void portSelect(int portId, IHdmiControlCallback callback) { 97 assertRunOnServiceThread(); 98 if (isInPresetInstallationMode()) { 99 invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE); 100 return; 101 } 102 // Make sure this call does not stem from <Active Source> message reception, in 103 // which case the two ports will be the same. 104 if (portId == getActivePortId()) { 105 invokeCallback(callback, HdmiCec.RESULT_SUCCESS); 106 return; 107 } 108 setActivePortId(portId); 109 110 // TODO: Return immediately if the operation is triggered by <Text/Image View On> 111 // TODO: Handle invalid port id / active input which should be treated as an 112 // internal tuner. 113 114 removeAction(RoutingControlAction.class); 115 116 int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId())); 117 int newPath = mService.portIdToPath(portId); 118 HdmiCecMessage routingChange = 119 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 120 mService.sendCecCommand(routingChange); 121 addAndStartAction(new RoutingControlAction(this, newPath, callback)); 122 } 123 124 /** 125 * Sends key to a target CEC device. 126 * 127 * @param keyCode key code to send. Defined in {@link KeyEvent}. 128 * @param isPressed true if this is keypress event 129 */ 130 void sendKeyEvent(int keyCode, boolean isPressed) { 131 assertRunOnServiceThread(); 132 List<SendKeyAction> action = getActions(SendKeyAction.class); 133 if (!action.isEmpty()) { 134 action.get(0).processKeyEvent(keyCode, isPressed); 135 } else { 136 if (isPressed) { 137 addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode)); 138 } else { 139 Slog.w(TAG, "Discard key release event"); 140 } 141 } 142 } 143 144 private static void invokeCallback(IHdmiControlCallback callback, int result) { 145 try { 146 callback.onComplete(result); 147 } catch (RemoteException e) { 148 Slog.e(TAG, "Invoking callback failed:" + e); 149 } 150 } 151 152 @Override 153 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 154 assertRunOnServiceThread(); 155 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 156 mAddress, Locale.getDefault().getISO3Language()); 157 // TODO: figure out how to handle failed to get language code. 158 if (command != null) { 159 mService.sendCecCommand(command); 160 } else { 161 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 162 } 163 return true; 164 } 165 166 @Override 167 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 168 assertRunOnServiceThread(); 169 // Ignore if [Device Discovery Action] is going on. 170 if (hasAction(DeviceDiscoveryAction.class)) { 171 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 172 + "because Device Discovery Action is on-going:" + message); 173 return true; 174 } 175 176 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 177 int logicalAddress = message.getSource(); 178 179 // If it is a new device and connected to the tail of active path, 180 // it's required to change routing path. 181 boolean requireRoutingChange = !isInDeviceList(physicalAddress, logicalAddress) 182 && isTailOfActivePath(physicalAddress); 183 addAndStartAction(new NewDeviceAction(this, message.getSource(), physicalAddress, 184 requireRoutingChange)); 185 return true; 186 } 187 188 @Override 189 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 190 assertRunOnServiceThread(); 191 List<VendorSpecificAction> actions = Collections.emptyList(); 192 // TODO: Call mService.getActions(VendorSpecificAction.class) to get all the actions. 193 194 // We assume that there can be multiple vendor-specific command actions running 195 // at the same time. Pass the message to each action to see if one of them needs it. 196 for (VendorSpecificAction action : actions) { 197 if (action.processCommand(message)) { 198 return true; 199 } 200 } 201 // Handle the message here if it is not already consumed by one of the running actions. 202 // Respond with a appropriate vendor-specific command or <Feature Abort>, or create another 203 // vendor-specific action: 204 // 205 // mService.addAndStartAction(new VendorSpecificAction(mService, mAddress)); 206 // 207 // For now, simply reply with <Feature Abort> and mark it consumed by returning true. 208 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand( 209 message.getDestination(), message.getSource(), message.getOpcode(), 210 HdmiConstants.ABORT_REFUSED)); 211 return true; 212 } 213 214 private void launchDeviceDiscovery() { 215 assertRunOnServiceThread(); 216 clearDeviceInfoList(); 217 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 218 new DeviceDiscoveryCallback() { 219 @Override 220 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 221 for (HdmiCecDeviceInfo info : deviceInfos) { 222 addCecDevice(info); 223 } 224 225 // Since we removed all devices when it's start and 226 // device discovery action does not poll local devices, 227 // we should put device info of local device manually here 228 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 229 addCecDevice(device.getDeviceInfo()); 230 } 231 232 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 233 234 // If there is AVR, initiate System Audio Auto initiation action, 235 // which turns on and off system audio according to last system 236 // audio setting. 237 HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); 238 if (avrInfo != null) { 239 addAndStartAction(new SystemAudioAutoInitiationAction( 240 HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); 241 } 242 } 243 }); 244 addAndStartAction(action); 245 } 246 247 // Clear all device info. 248 private void clearDeviceInfoList() { 249 assertRunOnServiceThread(); 250 mDeviceInfos.clear(); 251 } 252 253 void setSystemAudioMode(boolean on) { 254 synchronized (mLock) { 255 if (on != mSystemAudioMode) { 256 mSystemAudioMode = on; 257 // TODO: Need to set the preference for SystemAudioMode. 258 // TODO: Need to handle the notification of changing the mode and 259 // to identify the notification should be handled in the service or TvSettings. 260 } 261 } 262 } 263 264 boolean getSystemAudioMode() { 265 synchronized (mLock) { 266 assertRunOnServiceThread(); 267 return mSystemAudioMode; 268 } 269 } 270 271 /** 272 * Change ARC status into the given {@code enabled} status. 273 * 274 * @return {@code true} if ARC was in "Enabled" status 275 */ 276 boolean setArcStatus(boolean enabled) { 277 synchronized (mLock) { 278 boolean oldStatus = mArcStatusEnabled; 279 // 1. Enable/disable ARC circuit. 280 mService.setAudioReturnChannel(enabled); 281 282 // TODO: notify arc mode change to AudioManager. 283 284 // 2. Update arc status; 285 mArcStatusEnabled = enabled; 286 return oldStatus; 287 } 288 } 289 290 /** 291 * Returns whether ARC is enabled or not. 292 */ 293 boolean getArcStatus() { 294 synchronized (mLock) { 295 return mArcStatusEnabled; 296 } 297 } 298 299 void setAudioStatus(boolean mute, int volume) { 300 mService.setAudioStatus(mute, volume); 301 } 302 303 @Override 304 protected boolean handleInitiateArc(HdmiCecMessage message) { 305 assertRunOnServiceThread(); 306 // In case where <Initiate Arc> is started by <Request ARC Initiation> 307 // need to clean up RequestArcInitiationAction. 308 removeAction(RequestArcInitiationAction.class); 309 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 310 message.getSource(), true); 311 addAndStartAction(action); 312 return true; 313 } 314 315 @Override 316 protected boolean handleTerminateArc(HdmiCecMessage message) { 317 assertRunOnServiceThread(); 318 // In case where <Terminate Arc> is started by <Request ARC Termination> 319 // need to clean up RequestArcInitiationAction. 320 // TODO: check conditions of power status by calling is_connected api 321 // to be added soon. 322 removeAction(RequestArcTerminationAction.class); 323 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 324 message.getSource(), false); 325 addAndStartAction(action); 326 return true; 327 } 328 329 @Override 330 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 331 assertRunOnServiceThread(); 332 if (!isMessageForSystemAudio(message)) { 333 return false; 334 } 335 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 336 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message)); 337 addAndStartAction(action); 338 return true; 339 } 340 341 @Override 342 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 343 assertRunOnServiceThread(); 344 if (!isMessageForSystemAudio(message)) { 345 return false; 346 } 347 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 348 return true; 349 } 350 351 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 352 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 353 || message.getDestination() != HdmiCec.ADDR_TV 354 || getAvrDeviceInfo() == null) { 355 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 356 return false; 357 } 358 return true; 359 } 360 361 /** 362 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 363 * logical address as new device info's. 364 * 365 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 366 * 367 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 368 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 369 * that has the same logical address as new one has. 370 */ 371 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 372 assertRunOnServiceThread(); 373 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 374 if (oldDeviceInfo != null) { 375 removeDeviceInfo(deviceInfo.getLogicalAddress()); 376 } 377 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 378 return oldDeviceInfo; 379 } 380 381 /** 382 * Remove a device info corresponding to the given {@code logicalAddress}. 383 * It returns removed {@link HdmiCecDeviceInfo} if exists. 384 * 385 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 386 * 387 * @param logicalAddress logical address of device to be removed 388 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 389 */ 390 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 391 assertRunOnServiceThread(); 392 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 393 if (deviceInfo != null) { 394 mDeviceInfos.remove(logicalAddress); 395 } 396 return deviceInfo; 397 } 398 399 /** 400 * Return a list of all {@link HdmiCecDeviceInfo}. 401 * 402 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 403 */ 404 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 405 assertRunOnServiceThread(); 406 if (includelLocalDevice) { 407 return HdmiUtils.sparseArrayToList(mDeviceInfos); 408 } else { 409 410 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 411 for (int i = 0; i < mDeviceInfos.size(); ++i) { 412 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 413 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 414 infoList.add(info); 415 } 416 } 417 return infoList; 418 } 419 } 420 421 private boolean isLocalDeviceAddress(int address) { 422 assertRunOnServiceThread(); 423 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 424 if (device.isAddressOf(address)) { 425 return true; 426 } 427 } 428 return false; 429 } 430 431 /** 432 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 433 * 434 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 435 * 436 * @param logicalAddress logical address to be retrieved 437 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 438 * Returns null if no logical address matched 439 */ 440 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 441 assertRunOnServiceThread(); 442 return mDeviceInfos.get(logicalAddress); 443 } 444 445 HdmiCecDeviceInfo getAvrDeviceInfo() { 446 assertRunOnServiceThread(); 447 return getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 448 } 449 450 /** 451 * Called when a device is newly added or a new device is detected. 452 * 453 * @param info device info of a new device. 454 */ 455 final void addCecDevice(HdmiCecDeviceInfo info) { 456 assertRunOnServiceThread(); 457 addDeviceInfo(info); 458 459 // TODO: announce new device detection. 460 } 461 462 /** 463 * Called when a device is removed or removal of device is detected. 464 * 465 * @param address a logical address of a device to be removed 466 */ 467 final void removeCecDevice(int address) { 468 assertRunOnServiceThread(); 469 removeDeviceInfo(address); 470 mCecMessageCache.flushMessagesFrom(address); 471 472 // TODO: announce a device removal. 473 } 474 475 /** 476 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 477 * the given routing path. CEC devices use routing path for its physical address to 478 * describe the hierarchy of the devices in the network. 479 * 480 * @param path routing path or physical address 481 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 482 */ 483 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 484 assertRunOnServiceThread(); 485 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 486 if (info.getPhysicalAddress() == path) { 487 return info; 488 } 489 } 490 return null; 491 } 492 493 /** 494 * Whether a device of the specified physical address and logical address exists 495 * in a device info list. However, both are minimal condition and it could 496 * be different device from the original one. 497 * 498 * @param physicalAddress physical address of a device to be searched 499 * @param logicalAddress logical address of a device to be searched 500 * @return true if exist; otherwise false 501 */ 502 boolean isInDeviceList(int physicalAddress, int logicalAddress) { 503 assertRunOnServiceThread(); 504 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 505 if (device == null) { 506 return false; 507 } 508 return device.getPhysicalAddress() == physicalAddress; 509 } 510 511 @Override 512 void onHotplug(int portNo, boolean connected) { 513 assertRunOnServiceThread(); 514 // TODO: delegate onHotplug event to each local device. 515 516 // Tv device will have permanent HotplugDetectionAction. 517 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 518 if (!hotplugActions.isEmpty()) { 519 // Note that hotplug action is single action running on a machine. 520 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 521 hotplugActions.get(0).pollAllDevicesNow(); 522 } 523 } 524 525 boolean canChangeSystemAudio() { 526 // TODO: implement this. 527 // return true if no system audio control sequence is running. 528 return false; 529 } 530} 531