HdmiControlService.java revision 092b445ef898e3c1e5b2918b554480940f0f5a28
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.Handler; 28import android.os.HandlerThread; 29import android.os.IBinder; 30import android.os.Looper; 31import android.os.RemoteException; 32import android.util.Slog; 33import android.util.SparseArray; 34import android.util.SparseIntArray; 35 36import com.android.internal.annotations.GuardedBy; 37import com.android.server.SystemService; 38import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 39import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 40 41import java.util.ArrayList; 42import java.util.Iterator; 43import java.util.LinkedList; 44import java.util.List; 45import java.util.Locale; 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 @GuardedBy("mLock") 129 // Whether ARC is "enabled" or not. 130 // TODO: it may need to hold lock if it's accessed from others. 131 private boolean mArcStatusEnabled = false; 132 133 @GuardedBy("mLock") 134 // Whether SystemAudioMode is "On" or not. 135 private boolean mSystemAudioMode; 136 137 // Handler running on service thread. It's used to run a task in service thread. 138 private final Handler mHandler = new Handler(); 139 140 public HdmiControlService(Context context) { 141 super(context); 142 mLocalDevices = getContext().getResources().getIntArray( 143 com.android.internal.R.array.config_hdmiCecLogicalDeviceType); 144 } 145 146 @Override 147 public void onStart() { 148 mIoThread.start(); 149 mCecController = HdmiCecController.create(this); 150 151 if (mCecController != null) { 152 initializeLocalDevices(mLocalDevices); 153 } else { 154 Slog.i(TAG, "Device does not support HDMI-CEC."); 155 } 156 157 mMhlController = HdmiMhlController.create(this); 158 if (mMhlController == null) { 159 Slog.i(TAG, "Device does not support MHL-control."); 160 } 161 162 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 163 164 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 165 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 166 } 167 168 private void initializeLocalDevices(final int[] deviceTypes) { 169 // A container for [Logical Address, Local device info]. 170 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 171 final SparseIntArray finished = new SparseIntArray(); 172 for (int type : deviceTypes) { 173 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 174 localDevice.init(); 175 mCecController.allocateLogicalAddress(type, 176 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 177 @Override 178 public void onAllocated(int deviceType, int logicalAddress) { 179 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 180 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 181 } else { 182 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 183 localDevice.setDeviceInfo(deviceInfo); 184 mCecController.addLocalDevice(deviceType, localDevice); 185 mCecController.addLogicalAddress(logicalAddress); 186 devices.append(logicalAddress, localDevice); 187 } 188 finished.append(deviceType, logicalAddress); 189 190 // Once finish address allocation for all devices, notify 191 // it to each device. 192 if (deviceTypes.length == finished.size()) { 193 notifyAddressAllocated(devices); 194 } 195 } 196 }); 197 } 198 } 199 200 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 201 for (int i = 0; i < devices.size(); ++i) { 202 int address = devices.keyAt(i); 203 HdmiCecLocalDevice device = devices.valueAt(i); 204 device.onAddressAllocated(address); 205 } 206 } 207 208 /** 209 * Returns {@link Looper} for IO operation. 210 * 211 * <p>Declared as package-private. 212 */ 213 Looper getIoLooper() { 214 return mIoThread.getLooper(); 215 } 216 217 /** 218 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 219 * for tasks that are running on main service thread. 220 * 221 * <p>Declared as package-private. 222 */ 223 Looper getServiceLooper() { 224 return mHandler.getLooper(); 225 } 226 227 /** 228 * Returns physical address of the device. 229 */ 230 int getPhysicalAddress() { 231 return mCecController.getPhysicalAddress(); 232 } 233 234 /** 235 * Returns vendor id of CEC service. 236 */ 237 int getVendorId() { 238 return mCecController.getVendorId(); 239 } 240 241 /** 242 * Returns version of CEC. 243 */ 244 int getCecVersion() { 245 return mCecController.getVersion(); 246 } 247 248 /** 249 * Returns a list of {@link HdmiCecDeviceInfo}. 250 * 251 * @param includeLocalDevice whether to include local devices 252 */ 253 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 254 assertRunOnServiceThread(); 255 return mCecController.getDeviceInfoList(includeLocalDevice); 256 } 257 258 /** 259 * Add and start a new {@link FeatureAction} to the action queue. 260 * 261 * @param action {@link FeatureAction} to add and start 262 */ 263 void addAndStartAction(final FeatureAction action) { 264 // TODO: may need to check the number of stale actions. 265 runOnServiceThread(new Runnable() { 266 @Override 267 public void run() { 268 mActions.add(action); 269 action.start(); 270 } 271 }); 272 } 273 274 void setSystemAudioMode(boolean on) { 275 synchronized (mLock) { 276 mSystemAudioMode = on; 277 } 278 } 279 280 boolean getSystemAudioMode() { 281 synchronized (mLock) { 282 return mSystemAudioMode; 283 } 284 } 285 286 // See if we have an action of a given type in progress. 287 <T extends FeatureAction> boolean hasAction(final Class<T> clazz) { 288 for (FeatureAction action : mActions) { 289 if (action.getClass().equals(clazz)) { 290 return true; 291 } 292 } 293 return false; 294 } 295 296 /** 297 * Remove the given {@link FeatureAction} object from the action queue. 298 * 299 * @param action {@link FeatureAction} to remove 300 */ 301 void removeAction(final FeatureAction action) { 302 assertRunOnServiceThread(); 303 mActions.remove(action); 304 } 305 306 // Remove all actions matched with the given Class type. 307 private <T extends FeatureAction> void removeAction(final Class<T> clazz) { 308 removeActionExcept(clazz, null); 309 } 310 311 // Remove all actions matched with the given Class type besides |exception|. 312 <T extends FeatureAction> void removeActionExcept(final Class<T> clazz, 313 final FeatureAction exception) { 314 assertRunOnServiceThread(); 315 Iterator<FeatureAction> iter = mActions.iterator(); 316 while (iter.hasNext()) { 317 FeatureAction action = iter.next(); 318 if (action != exception && action.getClass().equals(clazz)) { 319 action.clear(); 320 mActions.remove(action); 321 } 322 } 323 } 324 325 private void runOnServiceThread(Runnable runnable) { 326 mHandler.post(runnable); 327 } 328 329 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 330 mHandler.postAtFrontOfQueue(runnable); 331 } 332 333 private void assertRunOnServiceThread() { 334 if (Looper.myLooper() != mHandler.getLooper()) { 335 throw new IllegalStateException("Should run on service thread."); 336 } 337 } 338 339 /** 340 * Change ARC status into the given {@code enabled} status. 341 * 342 * @return {@code true} if ARC was in "Enabled" status 343 */ 344 boolean setArcStatus(boolean enabled) { 345 assertRunOnServiceThread(); 346 synchronized (mLock) { 347 boolean oldStatus = mArcStatusEnabled; 348 // 1. Enable/disable ARC circuit. 349 mCecController.setAudioReturnChannel(enabled); 350 351 // TODO: notify arc mode change to AudioManager. 352 353 // 2. Update arc status; 354 mArcStatusEnabled = enabled; 355 return oldStatus; 356 } 357 } 358 359 /** 360 * Transmit a CEC command to CEC bus. 361 * 362 * @param command CEC command to send out 363 * @param callback interface used to the result of send command 364 */ 365 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 366 mCecController.sendCommand(command, callback); 367 } 368 369 void sendCecCommand(HdmiCecMessage command) { 370 mCecController.sendCommand(command, null); 371 } 372 373 boolean handleCecCommand(HdmiCecMessage message) { 374 // Cache incoming message. Note that it caches only white-listed one. 375 mCecMessageCache.cacheMessage(message); 376 377 // Commands that queries system information replies directly instead 378 // of creating FeatureAction because they are state-less. 379 // TODO: move the leftover message to local device. 380 switch (message.getOpcode()) { 381 case HdmiCec.MESSAGE_INITIATE_ARC: 382 handleInitiateArc(message); 383 return true; 384 case HdmiCec.MESSAGE_TERMINATE_ARC: 385 handleTerminateArc(message); 386 return true; 387 case HdmiCec.MESSAGE_SET_SYSTEM_AUDIO_MODE: 388 handleSetSystemAudioMode(message); 389 return true; 390 case HdmiCec.MESSAGE_SYSTEM_AUDIO_MODE_STATUS: 391 handleSystemAudioModeStatus(message); 392 return true; 393 default: 394 if (dispatchMessageToAction(message)) { 395 return true; 396 } 397 break; 398 } 399 400 return dispatchMessageToLocalDevice(message); 401 } 402 403 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 404 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 405 if (device.dispatchMessage(message)) { 406 return true; 407 } 408 } 409 return false; 410 } 411 412 /** 413 * Called when a new hotplug event is issued. 414 * 415 * @param portNo hdmi port number where hot plug event issued. 416 * @param connected whether to be plugged in or not 417 */ 418 void onHotplug(int portNo, boolean connected) { 419 // TODO: Start "RequestArcInitiationAction" if ARC port. 420 } 421 422 /** 423 * Poll all remote devices. It sends <Polling Message> to all remote 424 * devices. 425 * 426 * @param callback an interface used to get a list of all remote devices' address 427 * @param pickStrategy strategy how to pick polling candidates 428 * @param retryCount the number of retry used to send polling message to remote devices 429 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 430 */ 431 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 432 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 433 } 434 435 private int checkPollStrategy(int pickStrategy) { 436 int strategy = pickStrategy & POLL_STRATEGY_MASK; 437 if (strategy == 0) { 438 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 439 } 440 int iterationStrategy = pickStrategy & POLL_ITERATION_STRATEGY_MASK; 441 if (iterationStrategy == 0) { 442 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 443 } 444 return strategy | iterationStrategy; 445 } 446 447 /** 448 * Launch device discovery sequence. It starts with clearing the existing device info list. 449 * Note that it assumes that logical address of all local devices is already allocated. 450 * 451 * @param sourceAddress a logical address of tv 452 */ 453 void launchDeviceDiscovery(final int sourceAddress) { 454 // At first, clear all existing device infos. 455 mCecController.clearDeviceInfoList(); 456 // TODO: flush cec message cache when CEC is turned off. 457 458 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, sourceAddress, 459 new DeviceDiscoveryCallback() { 460 @Override 461 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 462 for (HdmiCecDeviceInfo info : deviceInfos) { 463 addCecDevice(info); 464 } 465 466 // Add device info of all local devices. 467 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 468 addCecDevice(device.getDeviceInfo()); 469 } 470 471 addAndStartAction(new HotplugDetectionAction(HdmiControlService.this, 472 sourceAddress)); 473 } 474 }); 475 addAndStartAction(action); 476 } 477 478 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 479 // TODO: get device name read from system configuration. 480 String displayName = HdmiCec.getDefaultDeviceName(logicalAddress); 481 return new HdmiCecDeviceInfo(logicalAddress, 482 getPhysicalAddress(), deviceType, getVendorId(), displayName); 483 } 484 485 private void handleInitiateArc(HdmiCecMessage message){ 486 // In case where <Initiate Arc> is started by <Request ARC Initiation> 487 // need to clean up RequestArcInitiationAction. 488 removeAction(RequestArcInitiationAction.class); 489 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 490 message.getDestination(), message.getSource(), true); 491 addAndStartAction(action); 492 } 493 494 private void handleTerminateArc(HdmiCecMessage message) { 495 // In case where <Terminate Arc> is started by <Request ARC Termination> 496 // need to clean up RequestArcInitiationAction. 497 // TODO: check conditions of power status by calling is_connected api 498 // to be added soon. 499 removeAction(RequestArcTerminationAction.class); 500 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 501 message.getDestination(), message.getSource(), false); 502 addAndStartAction(action); 503 } 504 505 private boolean dispatchMessageToAction(HdmiCecMessage message) { 506 for (FeatureAction action : mActions) { 507 if (action.processCommand(message)) { 508 return true; 509 } 510 } 511 Slog.w(TAG, "Unsupported cec command:" + message); 512 return false; 513 } 514 515 private void handleSetSystemAudioMode(HdmiCecMessage message) { 516 if (dispatchMessageToAction(message) || !isMessageForSystemAudio(message)) { 517 return; 518 } 519 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 520 message.getDestination(), message.getSource(), 521 HdmiUtils.parseCommandParamSystemAudioStatus(message)); 522 addAndStartAction(action); 523 } 524 525 private void handleSystemAudioModeStatus(HdmiCecMessage message) { 526 if (!isMessageForSystemAudio(message)) { 527 return; 528 } 529 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 530 } 531 532 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 533 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 534 || message.getDestination() != HdmiCec.ADDR_TV 535 || getAvrDeviceInfo() == null) { 536 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 537 return false; 538 } 539 return true; 540 } 541 542 // Record class that monitors the event of the caller of being killed. Used to clean up 543 // the listener list and record list accordingly. 544 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 545 private final IHdmiHotplugEventListener mListener; 546 547 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 548 mListener = listener; 549 } 550 551 @Override 552 public void binderDied() { 553 synchronized (mLock) { 554 mHotplugEventListenerRecords.remove(this); 555 mHotplugEventListeners.remove(mListener); 556 } 557 } 558 } 559 560 void addCecDevice(HdmiCecDeviceInfo info) { 561 mCecController.addDeviceInfo(info); 562 } 563 564 private void enforceAccessPermission() { 565 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 566 } 567 568 private final class BinderService extends IHdmiControlService.Stub { 569 @Override 570 public int[] getSupportedTypes() { 571 enforceAccessPermission(); 572 synchronized (mLock) { 573 return mLocalDevices; 574 } 575 } 576 577 @Override 578 public void oneTouchPlay(final IHdmiControlCallback callback) { 579 enforceAccessPermission(); 580 runOnServiceThread(new Runnable() { 581 @Override 582 public void run() { 583 HdmiControlService.this.oneTouchPlay(callback); 584 } 585 }); 586 } 587 588 @Override 589 public void queryDisplayStatus(final IHdmiControlCallback callback) { 590 enforceAccessPermission(); 591 runOnServiceThread(new Runnable() { 592 @Override 593 public void run() { 594 HdmiControlService.this.queryDisplayStatus(callback); 595 } 596 }); 597 } 598 599 @Override 600 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 601 enforceAccessPermission(); 602 runOnServiceThread(new Runnable() { 603 @Override 604 public void run() { 605 HdmiControlService.this.addHotplugEventListener(listener); 606 } 607 }); 608 } 609 610 @Override 611 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 612 enforceAccessPermission(); 613 runOnServiceThread(new Runnable() { 614 @Override 615 public void run() { 616 HdmiControlService.this.removeHotplugEventListener(listener); 617 } 618 }); 619 } 620 } 621 622 private void oneTouchPlay(IHdmiControlCallback callback) { 623 if (hasAction(OneTouchPlayAction.class)) { 624 Slog.w(TAG, "oneTouchPlay already in progress"); 625 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 626 return; 627 } 628 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 629 if (source == null) { 630 Slog.w(TAG, "Local playback device not available"); 631 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 632 return; 633 } 634 // TODO: Consider the case of multiple TV sets. For now we always direct the command 635 // to the primary one. 636 OneTouchPlayAction action = OneTouchPlayAction.create(this, 637 source.getDeviceInfo().getLogicalAddress(), 638 source.getDeviceInfo().getPhysicalAddress(), HdmiCec.ADDR_TV, callback); 639 if (action == null) { 640 Slog.w(TAG, "Cannot initiate oneTouchPlay"); 641 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 642 return; 643 } 644 addAndStartAction(action); 645 } 646 647 private void queryDisplayStatus(IHdmiControlCallback callback) { 648 if (hasAction(DevicePowerStatusAction.class)) { 649 Slog.w(TAG, "queryDisplayStatus already in progress"); 650 invokeCallback(callback, HdmiCec.RESULT_ALREADY_IN_PROGRESS); 651 return; 652 } 653 HdmiCecLocalDevice source = mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 654 if (source == null) { 655 Slog.w(TAG, "Local playback device not available"); 656 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 657 return; 658 } 659 DevicePowerStatusAction action = DevicePowerStatusAction.create(this, 660 source.getDeviceInfo().getLogicalAddress(), HdmiCec.ADDR_TV, callback); 661 if (action == null) { 662 Slog.w(TAG, "Cannot initiate queryDisplayStatus"); 663 invokeCallback(callback, HdmiCec.RESULT_EXCEPTION); 664 return; 665 } 666 addAndStartAction(action); 667 } 668 669 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 670 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 671 try { 672 listener.asBinder().linkToDeath(record, 0); 673 } catch (RemoteException e) { 674 Slog.w(TAG, "Listener already died"); 675 return; 676 } 677 synchronized (mLock) { 678 mHotplugEventListenerRecords.add(record); 679 mHotplugEventListeners.add(listener); 680 } 681 } 682 683 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 684 synchronized (mLock) { 685 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 686 if (record.mListener.asBinder() == listener.asBinder()) { 687 listener.asBinder().unlinkToDeath(record, 0); 688 mHotplugEventListenerRecords.remove(record); 689 break; 690 } 691 } 692 mHotplugEventListeners.remove(listener); 693 } 694 } 695 696 private void invokeCallback(IHdmiControlCallback callback, int result) { 697 try { 698 callback.onComplete(result); 699 } catch (RemoteException e) { 700 Slog.e(TAG, "Invoking callback failed:" + e); 701 } 702 } 703 704 HdmiCecDeviceInfo getAvrDeviceInfo() { 705 return mCecController.getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 706 } 707 708 void setAudioStatus(boolean mute, int volume) { 709 // TODO: Hook up with AudioManager. 710 } 711 712 boolean isInPresetInstallationMode() { 713 // TODO: Implement this. 714 return false; 715 } 716 717 /** 718 * Called when a device is removed or removal of device is detected. 719 * 720 * @param address a logical address of a device to be removed 721 */ 722 void removeCecDevice(int address) { 723 mCecController.removeDeviceInfo(address); 724 mCecMessageCache.flushMessagesFrom(address); 725 } 726 727 HdmiCecMessageCache getCecMessageCache() { 728 return mCecMessageCache; 729 } 730} 731