HdmiControlService.java revision a6ce7708d6124224399241503fadcafe0c4684d4
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.annotation.Nullable; 20import android.content.Context; 21import android.hardware.hdmi.HdmiCec; 22import android.hardware.hdmi.HdmiCecDeviceInfo; 23import android.hardware.hdmi.HdmiCecMessage; 24import android.hardware.hdmi.IHdmiControlCallback; 25import android.hardware.hdmi.IHdmiControlService; 26import android.hardware.hdmi.IHdmiHotplugEventListener; 27import android.os.Build; 28import android.os.Handler; 29import android.os.HandlerThread; 30import android.os.IBinder; 31import android.os.Looper; 32import android.os.RemoteException; 33import android.util.Slog; 34import android.util.SparseArray; 35import android.util.SparseIntArray; 36 37import com.android.internal.annotations.GuardedBy; 38import com.android.server.SystemService; 39import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 40import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 41 42import java.util.ArrayList; 43import java.util.Iterator; 44import java.util.LinkedList; 45import java.util.List; 46 47/** 48 * Provides a service for sending and processing HDMI control messages, 49 * HDMI-CEC and MHL control command, and providing the information on both standard. 50 */ 51public final class HdmiControlService extends SystemService { 52 private static final String TAG = "HdmiControlService"; 53 54 // TODO: Rename the permission to HDMI_CONTROL. 55 private static final String PERMISSION = "android.permission.HDMI_CEC"; 56 57 static final int SEND_RESULT_SUCCESS = 0; 58 static final int SEND_RESULT_NAK = -1; 59 static final int SEND_RESULT_FAILURE = -2; 60 61 static final int POLL_STRATEGY_MASK = 0x3; // first and second bit. 62 static final int POLL_STRATEGY_REMOTES_DEVICES = 0x1; 63 static final int POLL_STRATEGY_SYSTEM_AUDIO = 0x2; 64 65 static final int POLL_ITERATION_STRATEGY_MASK = 0x30000; // first and second bit. 66 static final int POLL_ITERATION_IN_ORDER = 0x10000; 67 static final int POLL_ITERATION_REVERSE_ORDER = 0x20000; 68 69 /** 70 * Interface to report send result. 71 */ 72 interface SendMessageCallback { 73 /** 74 * Called when {@link HdmiControlService#sendCecCommand} is completed. 75 * 76 * @param error result of send request. 77 * @see {@link #SEND_RESULT_SUCCESS} 78 * @see {@link #SEND_RESULT_NAK} 79 * @see {@link #SEND_RESULT_FAILURE} 80 */ 81 void onSendCompleted(int error); 82 } 83 84 /** 85 * Interface to get a list of available logical devices. 86 */ 87 interface DevicePollingCallback { 88 /** 89 * Called when device polling is finished. 90 * 91 * @param ackedAddress a list of logical addresses of available devices 92 */ 93 void onPollingFinished(List<Integer> ackedAddress); 94 } 95 96 // A thread to handle synchronous IO of CEC and MHL control service. 97 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 98 // and sparse call it shares a thread to handle IO operations. 99 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 100 101 // A collection of FeatureAction. 102 // Note that access to this collection should happen in service thread. 103 private final LinkedList<FeatureAction> mActions = new LinkedList<>(); 104 105 // Used to synchronize the access to the service. 106 private final Object mLock = new Object(); 107 108 // Type of logical devices hosted in the system. 109 @GuardedBy("mLock") 110 private final int[] mLocalDevices; 111 112 // List of listeners registered by callers that want to get notified of 113 // hotplug events. 114 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 115 116 // List of records for hotplug event listener to handle the the caller killed in action. 117 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 118 new ArrayList<>(); 119 120 private final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache(); 121 122 @Nullable 123 private HdmiCecController mCecController; 124 125 @Nullable 126 private HdmiMhlController mMhlController; 127 128 // Logical address of the active source. 129 @GuardedBy("mLock") 130 private int mActiveSource; 131 132 // Active routing path. Physical address of the active source but not all the time, such as 133 // when the new active source does not claim itself to be one. 134 @GuardedBy("mLock") 135 private int mActiveRoutingPath; 136 137 // Set to true while the service is in normal mode. While set to false, no input change is 138 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 139 // system upgrade, etc., a.k.a. "prohibit mode". 140 @GuardedBy("mLock") 141 private boolean mInputChangeEnabled; 142 143 @GuardedBy("mLock") 144 // Whether ARC is "enabled" or not. 145 // TODO: it may need to hold lock if it's accessed from others. 146 private boolean mArcStatusEnabled = false; 147 148 @GuardedBy("mLock") 149 // Whether SystemAudioMode is "On" or not. 150 private boolean mSystemAudioMode; 151 152 // Handler running on service thread. It's used to run a task in service thread. 153 private final Handler mHandler = new Handler(); 154 155 public HdmiControlService(Context context) { 156 super(context); 157 mLocalDevices = getContext().getResources().getIntArray( 158 com.android.internal.R.array.config_hdmiCecLogicalDeviceType); 159 // TODO: Get control flag from persistent storage 160 mInputChangeEnabled = true; 161 } 162 163 @Override 164 public void onStart() { 165 mIoThread.start(); 166 mCecController = HdmiCecController.create(this); 167 168 if (mCecController != null) { 169 initializeLocalDevices(mLocalDevices); 170 } else { 171 Slog.i(TAG, "Device does not support HDMI-CEC."); 172 } 173 174 mMhlController = HdmiMhlController.create(this); 175 if (mMhlController == null) { 176 Slog.i(TAG, "Device does not support MHL-control."); 177 } 178 179 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 180 181 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 182 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 183 } 184 185 private void initializeLocalDevices(final int[] deviceTypes) { 186 // A container for [Logical Address, Local device info]. 187 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 188 final SparseIntArray finished = new SparseIntArray(); 189 for (int type : deviceTypes) { 190 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 191 localDevice.init(); 192 mCecController.allocateLogicalAddress(type, 193 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 194 @Override 195 public void onAllocated(int deviceType, int logicalAddress) { 196 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 197 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 198 } else { 199 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 200 localDevice.setDeviceInfo(deviceInfo); 201 mCecController.addLocalDevice(deviceType, localDevice); 202 mCecController.addLogicalAddress(logicalAddress); 203 devices.append(logicalAddress, localDevice); 204 } 205 finished.append(deviceType, logicalAddress); 206 207 // Once finish address allocation for all devices, notify 208 // it to each device. 209 if (deviceTypes.length == finished.size()) { 210 notifyAddressAllocated(devices); 211 } 212 } 213 }); 214 } 215 } 216 217 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 218 for (int i = 0; i < devices.size(); ++i) { 219 int address = devices.keyAt(i); 220 HdmiCecLocalDevice device = devices.valueAt(i); 221 device.onAddressAllocated(address); 222 } 223 } 224 225 /** 226 * Returns {@link Looper} for IO operation. 227 * 228 * <p>Declared as package-private. 229 */ 230 Looper getIoLooper() { 231 return mIoThread.getLooper(); 232 } 233 234 /** 235 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 236 * for tasks that are running on main service thread. 237 * 238 * <p>Declared as package-private. 239 */ 240 Looper getServiceLooper() { 241 return mHandler.getLooper(); 242 } 243 244 int getActiveSource() { 245 synchronized (mLock) { 246 return mActiveSource; 247 } 248 } 249 250 int getActivePath() { 251 synchronized (mLock) { 252 return mActiveRoutingPath; 253 } 254 } 255 256 /** 257 * Returns the path (physical address) of the device at the top of the currently active 258 * routing path. Used to get the corresponding port address of the HDMI input of the TV. 259 */ 260 int getActiveInput() { 261 synchronized (mLock) { 262 return mActiveRoutingPath & HdmiConstants.ROUTING_PATH_TOP_MASK; 263 } 264 } 265 266 void updateActiveDevice(int logicalAddress, int physicalAddress) { 267 synchronized (mLock) { 268 mActiveSource = logicalAddress; 269 mActiveRoutingPath = physicalAddress; 270 } 271 } 272 273 void setInputChangeEnabled(boolean enabled) { 274 synchronized (mLock) { 275 mInputChangeEnabled = enabled; 276 } 277 } 278 279 /** 280 * Returns physical address of the device. 281 */ 282 int getPhysicalAddress() { 283 return mCecController.getPhysicalAddress(); 284 } 285 286 /** 287 * Returns vendor id of CEC service. 288 */ 289 int getVendorId() { 290 return mCecController.getVendorId(); 291 } 292 293 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 294 return mCecController.getDeviceInfo(logicalAddress); 295 } 296 297 /** 298 * Returns version of CEC. 299 */ 300 int getCecVersion() { 301 return mCecController.getVersion(); 302 } 303 304 /** 305 * Returns a list of {@link HdmiCecDeviceInfo}. 306 * 307 * @param includeLocalDevice whether to include local devices 308 */ 309 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 310 assertRunOnServiceThread(); 311 return mCecController.getDeviceInfoList(includeLocalDevice); 312 } 313 314 /** 315 * Add and start a new {@link FeatureAction} to the action queue. 316 * 317 * @param action {@link FeatureAction} to add and start 318 */ 319 void addAndStartAction(final FeatureAction action) { 320 // TODO: may need to check the number of stale actions. 321 runOnServiceThread(new Runnable() { 322 @Override 323 public void run() { 324 mActions.add(action); 325 action.start(); 326 } 327 }); 328 } 329 330 void setSystemAudioMode(boolean on) { 331 synchronized (mLock) { 332 mSystemAudioMode = on; 333 } 334 } 335 336 boolean getSystemAudioMode() { 337 synchronized (mLock) { 338 return mSystemAudioMode; 339 } 340 } 341 342 // See if we have an action of a given type in progress. 343 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 344 for (FeatureAction action : mActions) { 345 if (action.getClass().equals(clazz)) { 346 return true; 347 } 348 } 349 return false; 350 } 351 352 /** 353 * Remove the given {@link FeatureAction} object from the action queue. 354 * 355 * @param action {@link FeatureAction} to remove 356 */ 357 void removeAction(final FeatureAction action) { 358 assertRunOnServiceThread(); 359 mActions.remove(action); 360 } 361 362 // Remove all actions matched with the given Class type. 363 <T extends FeatureAction> void removeAction(final Class<T> clazz) { 364 removeActionExcept(clazz, null); 365 } 366 367 // Remove all actions matched with the given Class type besides |exception|. 368 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 369 final FeatureAction exception) { 370 assertRunOnServiceThread(); 371 Iterator<FeatureAction> iter = mActions.iterator(); 372 while (iter.hasNext()) { 373 FeatureAction action = iter.next(); 374 if (action != exception && action.getClass().equals(clazz)) { 375 action.clear(); 376 mActions.remove(action); 377 } 378 } 379 } 380 381 private void runOnServiceThread(Runnable runnable) { 382 mHandler.post(runnable); 383 } 384 385 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 386 mHandler.postAtFrontOfQueue(runnable); 387 } 388 389 private void assertRunOnServiceThread() { 390 if (Looper.myLooper() != mHandler.getLooper()) { 391 throw new IllegalStateException("Should run on service thread."); 392 } 393 } 394 395 /** 396 * Change ARC status into the given {@code enabled} status. 397 * 398 * @return {@code true} if ARC was in "Enabled" status 399 */ 400 boolean setArcStatus(boolean enabled) { 401 assertRunOnServiceThread(); 402 synchronized (mLock) { 403 boolean oldStatus = mArcStatusEnabled; 404 // 1. Enable/disable ARC circuit. 405 mCecController.setAudioReturnChannel(enabled); 406 407 // TODO: notify arc mode change to AudioManager. 408 409 // 2. Update arc status; 410 mArcStatusEnabled = enabled; 411 return oldStatus; 412 } 413 } 414 415 /** 416 * Transmit a CEC command to CEC bus. 417 * 418 * @param command CEC command to send out 419 * @param callback interface used to the result of send command 420 */ 421 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 422 mCecController.sendCommand(command, callback); 423 } 424 425 void sendCecCommand(HdmiCecMessage command) { 426 mCecController.sendCommand(command, null); 427 } 428 429 boolean handleCecCommand(HdmiCecMessage message) { 430 // Cache incoming message. Note that it caches only white-listed one. 431 mCecMessageCache.cacheMessage(message); 432 433 // Commands that queries system information replies directly instead 434 // of creating FeatureAction because they are state-less. 435 // TODO: move the leftover message to local device. 436 switch (message.getOpcode()) { 437 case HdmiCec.MESSAGE_INITIATE_ARC: 438 handleInitiateArc(message); 439 return true; 440 case HdmiCec.MESSAGE_TERMINATE_ARC: 441 handleTerminateArc(message); 442 return true; 443 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 444 handleSetSystemAudioMode(message); 445 return true; 446 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 447 handleSystemAudioModeStatus(message); 448 return true; 449 default: 450 if (dispatchMessageToAction(message)) { 451 return true; 452 } 453 break; 454 } 455 456 return dispatchMessageToLocalDevice(message); 457 } 458 459 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 460 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 461 if (device.dispatchMessage(message)) { 462 return true; 463 } 464 } 465 return false; 466 } 467 468 /** 469 * Called when a new hotplug event is issued. 470 * 471 * @param portNo hdmi port number where hot plug event issued. 472 * @param connected whether to be plugged in or not 473 */ 474 void onHotplug(int portNo, boolean connected) { 475 // TODO: Start "RequestArcInitiationAction" if ARC port. 476 } 477 478 /** 479 * Poll all remote devices. It sends <Polling Message> to all remote 480 * devices. 481 * 482 * @param callback an interface used to get a list of all remote devices' address 483 * @param pickStrategy strategy how to pick polling candidates 484 * @param retryCount the number of retry used to send polling message to remote devices 485 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 486 */ 487 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 488 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 489 } 490 491 private int checkPollStrategy(int pickStrategy) { 492 int strategy = pickStrategy & POLL_STRATEGY_MASK; 493 if (strategy == 0) { 494 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 495 } 496 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 497 if (iterationStrategy == 0) { 498 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 499 } 500 return strategy | iterationStrategy; 501 } 502 503 /** 504 * Launch device discovery sequence. It starts with clearing the existing device info list. 505 * Note that it assumes that logical address of all local devices is already allocated. 506 * 507 * @param sourceAddress a logical address of tv 508 */ 509 void launchDeviceDiscovery(final int sourceAddress) { 510 // At first, clear all existing device infos. 511 mCecController.clearDeviceInfoList(); 512 // TODO: flush cec message cache when CEC is turned off. 513 514 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 515 new DeviceDiscoveryCallback() { 516 @Override 517 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 518 for (HdmiCecDeviceInfo info : deviceInfos) { 519 addCecDevice(info); 520 } 521 522 // Add device info of all local devices. 523 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 524 addCecDevice(device.getDeviceInfo()); 525 } 526 527 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 528 sourceAddress)); 529 } 530 }); 531 addAndStartAction(action); 532 } 533 534 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 535 // TODO: find better name instead of model name. 536 String displayName = Build.MODEL; 537 return new HdmiCecDeviceInfo(logicalAddress, 538 getPhysicalAddress(), deviceType, getVendorId(), displayName); 539 } 540 541 private void handleInitiateArc(HdmiCecMessage message){ 542 // In case where <Initiate Arc> is started by <Request ARC Initiation> 543 // need to clean up RequestArcInitiationAction. 544 removeAction(RequestArcInitiationAction.class); 545 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 546 message.getDestination(), message.getSource(), true); 547 addAndStartAction(action); 548 } 549 550 private void handleTerminateArc(HdmiCecMessage message) { 551 // In case where <Terminate Arc> is started by <Request ARC Termination> 552 // need to clean up RequestArcInitiationAction. 553 // TODO: check conditions of power status by calling is_connected api 554 // to be added soon. 555 removeAction(RequestArcTerminationAction.class); 556 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 557 message.getDestination(), message.getSource(), false); 558 addAndStartAction(action); 559 } 560 561 private boolean dispatchMessageToAction(HdmiCecMessage message) { 562 for (FeatureAction action : mActions) { 563 if (action.processCommand(message)) { 564 return true; 565 } 566 } 567 Slog.w(TAG, "Unsupported cec command:" + message); 568 return false; 569 } 570 571 private void handleSetSystemAudioMode(HdmiCecMessage message) { 572 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 573 return; 574 } 575 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 576 message.getDestination(), message.getSource(), 577 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 578 addAndStartAction(action); 579 } 580 581 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 582 if (!isMessageForSystemAudio(message)) { 583 return; 584 } 585 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 586 } 587 588 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 589 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 590 || message.getDestination() != HdmiCec.ADDR_TV 591 || getAvrDeviceInfo() == null) { 592 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 593 return false; 594 } 595 return true; 596 } 597 598 // Record class that monitors the event of the caller of being killed. Used to clean up 599 // the listener list and record list accordingly. 600 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 601 private final IHdmiHotplugEventListener mListener; 602 603 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 604 mListener = listener; 605 } 606 607 @Override 608 public void binderDied() { 609 synchronized (mLock) { 610 mHotplugEventListenerRecords.remove(this); 611 mHotplugEventListeners.remove(mListener); 612 } 613 } 614 } 615 616 void addCecDevice(HdmiCecDeviceInfo info) { 617 mCecController.addDeviceInfo(info); 618 } 619 620 private void enforceAccessPermission() { 621 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 622 } 623 624 private final class BinderService extends IHdmiControlService.Stub { 625 @Override 626 public int[] getSupportedTypes() { 627 enforceAccessPermission(); 628 synchronized (mLock) { 629 return mLocalDevices; 630 } 631 } 632 633 @Override 634 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 635 enforceAccessPermission(); 636 runOnServiceThread(new Runnable() { 637 @Override 638 public void run() { 639 HdmiCecLocalDeviceTv tv = 640 (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 641 if (tv == null) { 642 Slog.w(TAG, "Local playback device not available"); 643 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 644 return; 645 } 646 tv.deviceSelect(logicalAddress, callback); 647 } 648 }); 649 } 650 651 652 @Override 653 public void oneTouchPlay(final IHdmiControlCallback callback) { 654 enforceAccessPermission(); 655 runOnServiceThread(new Runnable() { 656 @Override 657 public void run() { 658 HdmiControlService.this.oneTouchPlay(callback); 659 } 660 }); 661 } 662 663 @Override 664 public void queryDisplayStatus(final IHdmiControlCallback callback) { 665 enforceAccessPermission(); 666 runOnServiceThread(new Runnable() { 667 @Override 668 public void run() { 669 HdmiControlService.this.queryDisplayStatus(callback); 670 } 671 }); 672 } 673 674 @Override 675 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 676 enforceAccessPermission(); 677 runOnServiceThread(new Runnable() { 678 @Override 679 public void run() { 680 HdmiControlService.this.addHotplugEventListener(listener); 681 } 682 }); 683 } 684 685 @Override 686 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 687 enforceAccessPermission(); 688 runOnServiceThread(new Runnable() { 689 @Override 690 public void run() { 691 HdmiControlService.this.removeHotplugEventListener(listener); 692 } 693 }); 694 } 695 } 696 697 private void oneTouchPlay(IHdmiControlCallback callback) { 698 if (hasAction(OneTouchPlayAction.class)) { 699 Slog.w(TAG, "oneTouchPlay already in progress"); 700 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 701 return; 702 } 703 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 704 if (source == null) { 705 Slog.w(TAG, "Local playback device not available"); 706 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 707 return; 708 } 709 // TODO: Consider the case of multiple TV sets. For now we always direct the command 710 // to the primary one. 711 OneTouchPlayAction action = OneTouchPlayAction.create(this, 712 source.getDeviceInfo().getLogicalAddress(), 713 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 714 if (action == null) { 715 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 716 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 717 return; 718 } 719 addAndStartAction(action); 720 } 721 722 private void queryDisplayStatus(IHdmiControlCallback callback) { 723 if (hasAction(DevicePowerStatusAction.class)) { 724 Slog.w(TAG, "queryDisplayStatus already in progress"); 725 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 726 return; 727 } 728 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 729 if (source == null) { 730 Slog.w(TAG, "Local playback device not available"); 731 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 732 return; 733 } 734 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 735 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 736 if (action == null) { 737 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 738 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 739 return; 740 } 741 addAndStartAction(action); 742 } 743 744 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 745 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 746 try { 747 listener.asBinder().linkToDeath(record, 0); 748 } catch (RemoteException e) { 749 Slog.w(TAG, "Listener already died"); 750 return; 751 } 752 synchronized (mLock) { 753 mHotplugEventListenerRecords.add(record); 754 mHotplugEventListeners.add(listener); 755 } 756 } 757 758 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 759 synchronized (mLock) { 760 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 761 if (record.mListener.asBinder() == listener.asBinder()) { 762 listener.asBinder().unlinkToDeath(record, 0); 763 mHotplugEventListenerRecords.remove(record); 764 break; 765 } 766 } 767 mHotplugEventListeners.remove(listener); 768 } 769 } 770 771 private void invokeCallback(IHdmiControlCallback callback, int result) { 772 try { 773 callback.onComplete(result); 774 } catch (RemoteException e) { 775 Slog.e(TAG, "Invoking callback failed:" + e); 776 } 777 } 778 779 HdmiCecDeviceInfo getAvrDeviceInfo() { 780 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 781 } 782 783 void setAudioStatus(boolean mute, int volume) { 784 // TODO: Hook up with AudioManager. 785 } 786 787 boolean isInPresetInstallationMode() { 788 synchronized (mLock) { 789 return !mInputChangeEnabled; 790 } 791 } 792 793 /** 794 * Called when a device is removed or removal of device is detected. 795 * 796 * @param address a logical address of a device to be removed 797 */ 798 void removeCecDevice(int address) { 799 mCecController.removeDeviceInfo(address); 800 mCecMessageCache.flushMessagesFrom(address); 801 } 802 803 HdmiCecMessageCache getCecMessageCache() { 804 return mCecMessageCache; 805 } 806} 807