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