HdmiCecLocalDeviceTv.java revision 187d01765b935d07936f74343b4f4af590c239a1
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 private static void invokeCallback(IHdmiControlCallback callback, int result) { 91 try { 92 callback.onComplete(result); 93 } catch (RemoteException e) { 94 Slog.e(TAG, "Invoking callback failed:" + e); 95 } 96 } 97 98 @Override 99 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 100 assertRunOnServiceThread(); 101 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 102 mAddress, Locale.getDefault().getISO3Language()); 103 // TODO: figure out how to handle failed to get language code. 104 if (command != null) { 105 mService.sendCecCommand(command); 106 } else { 107 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 108 } 109 return true; 110 } 111 112 @Override 113 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 114 assertRunOnServiceThread(); 115 // Ignore if [Device Discovery Action] is going on. 116 if (hasAction(DeviceDiscoveryAction.class)) { 117 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 118 + "because Device Discovery Action is on-going:" + message); 119 return true; 120 } 121 122 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 123 int logicalAddress = message.getSource(); 124 125 // If it is a new device and connected to the tail of active path, 126 // it's required to change routing path. 127 boolean requireRoutingChange = !isInDeviceList(physicalAddress, logicalAddress) 128 && isTailOfActivePath(physicalAddress); 129 addAndStartAction(new NewDeviceAction(this, message.getSource(), physicalAddress, 130 requireRoutingChange)); 131 return true; 132 } 133 134 @Override 135 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 136 assertRunOnServiceThread(); 137 List<VendorSpecificAction> actions = Collections.emptyList(); 138 // TODO: Call mService.getActions(VendorSpecificAction.class) to get all the actions. 139 140 // We assume that there can be multiple vendor-specific command actions running 141 // at the same time. Pass the message to each action to see if one of them needs it. 142 for (VendorSpecificAction action : actions) { 143 if (action.processCommand(message)) { 144 return true; 145 } 146 } 147 // Handle the message here if it is not already consumed by one of the running actions. 148 // Respond with a appropriate vendor-specific command or <Feature Abort>, or create another 149 // vendor-specific action: 150 // 151 // mService.addAndStartAction(new VendorSpecificAction(mService, mAddress)); 152 // 153 // For now, simply reply with <Feature Abort> and mark it consumed by returning true. 154 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand( 155 message.getDestination(), message.getSource(), message.getOpcode(), 156 HdmiConstants.ABORT_REFUSED)); 157 return true; 158 } 159 160 private void launchDeviceDiscovery() { 161 assertRunOnServiceThread(); 162 clearDeviceInfoList(); 163 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 164 new DeviceDiscoveryCallback() { 165 @Override 166 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 167 for (HdmiCecDeviceInfo info : deviceInfos) { 168 addCecDevice(info); 169 } 170 171 // Since we removed all devices when it's start and 172 // device discovery action does not poll local devices, 173 // we should put device info of local device manually here 174 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 175 addCecDevice(device.getDeviceInfo()); 176 } 177 178 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 179 180 // If there is AVR, initiate System Audio Auto initiation action, 181 // which turns on and off system audio according to last system 182 // audio setting. 183 HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); 184 if (avrInfo != null) { 185 addAndStartAction(new SystemAudioAutoInitiationAction( 186 HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); 187 } 188 } 189 }); 190 addAndStartAction(action); 191 } 192 193 // Clear all device info. 194 private void clearDeviceInfoList() { 195 assertRunOnServiceThread(); 196 mDeviceInfos.clear(); 197 } 198 199 void setSystemAudioMode(boolean on) { 200 synchronized (mLock) { 201 if (on != mSystemAudioMode) { 202 mSystemAudioMode = on; 203 // TODO: Need to set the preference for SystemAudioMode. 204 // TODO: Need to handle the notification of changing the mode and 205 // to identify the notification should be handled in the service or TvSettings. 206 } 207 } 208 } 209 210 boolean getSystemAudioMode() { 211 synchronized (mLock) { 212 assertRunOnServiceThread(); 213 return mSystemAudioMode; 214 } 215 } 216 217 /** 218 * Change ARC status into the given {@code enabled} status. 219 * 220 * @return {@code true} if ARC was in "Enabled" status 221 */ 222 boolean setArcStatus(boolean enabled) { 223 synchronized (mLock) { 224 boolean oldStatus = mArcStatusEnabled; 225 // 1. Enable/disable ARC circuit. 226 mService.setAudioReturnChannel(enabled); 227 228 // TODO: notify arc mode change to AudioManager. 229 230 // 2. Update arc status; 231 mArcStatusEnabled = enabled; 232 return oldStatus; 233 } 234 } 235 236 /** 237 * Returns whether ARC is enabled or not. 238 */ 239 boolean getArcStatus() { 240 synchronized (mLock) { 241 return mArcStatusEnabled; 242 } 243 } 244 245 void setAudioStatus(boolean mute, int volume) { 246 mService.setAudioStatus(mute, volume); 247 } 248 249 @Override 250 protected boolean handleInitiateArc(HdmiCecMessage message) { 251 assertRunOnServiceThread(); 252 // In case where <Initiate Arc> is started by <Request ARC Initiation> 253 // need to clean up RequestArcInitiationAction. 254 removeAction(RequestArcInitiationAction.class); 255 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 256 message.getSource(), true); 257 addAndStartAction(action); 258 return true; 259 } 260 261 @Override 262 protected boolean handleTerminateArc(HdmiCecMessage message) { 263 assertRunOnServiceThread(); 264 // In case where <Terminate Arc> is started by <Request ARC Termination> 265 // need to clean up RequestArcInitiationAction. 266 // TODO: check conditions of power status by calling is_connected api 267 // to be added soon. 268 removeAction(RequestArcTerminationAction.class); 269 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 270 message.getSource(), false); 271 addAndStartAction(action); 272 return true; 273 } 274 275 @Override 276 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 277 assertRunOnServiceThread(); 278 if (!isMessageForSystemAudio(message)) { 279 return false; 280 } 281 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 282 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message)); 283 addAndStartAction(action); 284 return true; 285 } 286 287 @Override 288 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 289 assertRunOnServiceThread(); 290 if (!isMessageForSystemAudio(message)) { 291 return false; 292 } 293 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 294 return true; 295 } 296 297 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 298 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 299 || message.getDestination() != HdmiCec.ADDR_TV 300 || getAvrDeviceInfo() == null) { 301 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 302 return false; 303 } 304 return true; 305 } 306 307 /** 308 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 309 * logical address as new device info's. 310 * 311 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 312 * 313 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 314 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 315 * that has the same logical address as new one has. 316 */ 317 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 318 assertRunOnServiceThread(); 319 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 320 if (oldDeviceInfo != null) { 321 removeDeviceInfo(deviceInfo.getLogicalAddress()); 322 } 323 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 324 return oldDeviceInfo; 325 } 326 327 /** 328 * Remove a device info corresponding to the given {@code logicalAddress}. 329 * It returns removed {@link HdmiCecDeviceInfo} if exists. 330 * 331 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 332 * 333 * @param logicalAddress logical address of device to be removed 334 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 335 */ 336 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 337 assertRunOnServiceThread(); 338 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 339 if (deviceInfo != null) { 340 mDeviceInfos.remove(logicalAddress); 341 } 342 return deviceInfo; 343 } 344 345 /** 346 * Return a list of all {@link HdmiCecDeviceInfo}. 347 * 348 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 349 */ 350 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 351 assertRunOnServiceThread(); 352 if (includelLocalDevice) { 353 return HdmiUtils.sparseArrayToList(mDeviceInfos); 354 } else { 355 356 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 357 for (int i = 0; i < mDeviceInfos.size(); ++i) { 358 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 359 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 360 infoList.add(info); 361 } 362 } 363 return infoList; 364 } 365 } 366 367 private boolean isLocalDeviceAddress(int address) { 368 assertRunOnServiceThread(); 369 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 370 if (device.isAddressOf(address)) { 371 return true; 372 } 373 } 374 return false; 375 } 376 377 /** 378 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 379 * 380 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 381 * 382 * @param logicalAddress logical address to be retrieved 383 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 384 * Returns null if no logical address matched 385 */ 386 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 387 assertRunOnServiceThread(); 388 return mDeviceInfos.get(logicalAddress); 389 } 390 391 HdmiCecDeviceInfo getAvrDeviceInfo() { 392 assertRunOnServiceThread(); 393 return getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 394 } 395 396 /** 397 * Called when a device is newly added or a new device is detected. 398 * 399 * @param info device info of a new device. 400 */ 401 final void addCecDevice(HdmiCecDeviceInfo info) { 402 assertRunOnServiceThread(); 403 addDeviceInfo(info); 404 405 // TODO: announce new device detection. 406 } 407 408 /** 409 * Called when a device is removed or removal of device is detected. 410 * 411 * @param address a logical address of a device to be removed 412 */ 413 final void removeCecDevice(int address) { 414 assertRunOnServiceThread(); 415 removeDeviceInfo(address); 416 mCecMessageCache.flushMessagesFrom(address); 417 418 // TODO: announce a device removal. 419 } 420 421 /** 422 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 423 * the given routing path. CEC devices use routing path for its physical address to 424 * describe the hierarchy of the devices in the network. 425 * 426 * @param path routing path or physical address 427 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 428 */ 429 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 430 assertRunOnServiceThread(); 431 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 432 if (info.getPhysicalAddress() == path) { 433 return info; 434 } 435 } 436 return null; 437 } 438 439 /** 440 * Whether a device of the specified physical address and logical address exists 441 * in a device info list. However, both are minimal condition and it could 442 * be different device from the original one. 443 * 444 * @param physicalAddress physical address of a device to be searched 445 * @param logicalAddress logical address of a device to be searched 446 * @return true if exist; otherwise false 447 */ 448 boolean isInDeviceList(int physicalAddress, int logicalAddress) { 449 assertRunOnServiceThread(); 450 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 451 if (device == null) { 452 return false; 453 } 454 return device.getPhysicalAddress() == physicalAddress; 455 } 456 457 @Override 458 void onHotplug(int portNo, boolean connected) { 459 assertRunOnServiceThread(); 460 // TODO: delegate onHotplug event to each local device. 461 462 // Tv device will have permanent HotplugDetectionAction. 463 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 464 if (!hotplugActions.isEmpty()) { 465 // Note that hotplug action is single action running on a machine. 466 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 467 hotplugActions.get(0).pollAllDevicesNow(); 468 } 469 } 470 471 boolean canChangeSystemAudio() { 472 // TODO: implement this. 473 // return true if no system audio control sequence is running. 474 return false; 475 } 476} 477