HdmiControlService.java revision 1de514256fd3015cf45256f3198ab5472024af9b
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.HdmiHotplugEvent; 25import android.hardware.hdmi.HdmiPortInfo; 26import android.hardware.hdmi.IHdmiControlCallback; 27import android.hardware.hdmi.IHdmiControlService; 28import android.hardware.hdmi.IHdmiDeviceEventListener; 29import android.hardware.hdmi.IHdmiHotplugEventListener; 30import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 31import android.media.AudioManager; 32import android.os.Build; 33import android.os.Handler; 34import android.os.HandlerThread; 35import android.os.IBinder; 36import android.os.Looper; 37import android.os.RemoteException; 38import android.util.Log; 39import android.util.Slog; 40import android.util.SparseArray; 41import android.util.SparseIntArray; 42 43import com.android.internal.annotations.GuardedBy; 44import com.android.server.SystemService; 45import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 46import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 47 48import java.util.ArrayList; 49import java.util.Collections; 50import java.util.List; 51 52/** 53 * Provides a service for sending and processing HDMI control messages, 54 * HDMI-CEC and MHL control command, and providing the information on both standard. 55 */ 56public final class HdmiControlService extends SystemService { 57 private static final String TAG = "HdmiControlService"; 58 59 // TODO: Rename the permission to HDMI_CONTROL. 60 private static final String PERMISSION = "android.permission.HDMI_CEC"; 61 62 /** 63 * Interface to report send result. 64 */ 65 interface SendMessageCallback { 66 /** 67 * Called when {@link HdmiControlService#sendCecCommand} is completed. 68 * 69 * @param error result of send request. 70 * @see {@link #SEND_RESULT_SUCCESS} 71 * @see {@link #SEND_RESULT_NAK} 72 * @see {@link #SEND_RESULT_FAILURE} 73 */ 74 void onSendCompleted(int error); 75 } 76 77 /** 78 * Interface to get a list of available logical devices. 79 */ 80 interface DevicePollingCallback { 81 /** 82 * Called when device polling is finished. 83 * 84 * @param ackedAddress a list of logical addresses of available devices 85 */ 86 void onPollingFinished(List<Integer> ackedAddress); 87 } 88 89 // A thread to handle synchronous IO of CEC and MHL control service. 90 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 91 // and sparse call it shares a thread to handle IO operations. 92 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 93 94 // Used to synchronize the access to the service. 95 private final Object mLock = new Object(); 96 97 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 98 private final List<Integer> mLocalDevices; 99 100 // List of listeners registered by callers that want to get notified of 101 // hotplug events. 102 @GuardedBy("mLock") 103 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 104 105 // List of records for hotplug event listener to handle the the caller killed in action. 106 @GuardedBy("mLock") 107 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 108 new ArrayList<>(); 109 110 // List of listeners registered by callers that want to get notified of 111 // device status events. 112 @GuardedBy("mLock") 113 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 114 115 // List of records for device event listener to handle the the caller killed in action. 116 @GuardedBy("mLock") 117 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 118 new ArrayList<>(); 119 120 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 121 // handling will be disabled and no request will be handled. 122 @GuardedBy("mLock") 123 private boolean mHdmiControlEnabled; 124 125 // List of listeners registered by callers that want to get notified of 126 // system audio mode changes. 127 private final ArrayList<IHdmiSystemAudioModeChangeListener> 128 mSystemAudioModeChangeListeners = new ArrayList<>(); 129 // List of records for system audio mode change to handle the the caller killed in action. 130 private final ArrayList<SystemAudioModeChangeListenerRecord> 131 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 132 133 // Handler used to run a task in service thread. 134 private final Handler mHandler = new Handler(); 135 136 @Nullable 137 private HdmiCecController mCecController; 138 139 @Nullable 140 private HdmiMhlController mMhlController; 141 142 // HDMI port information. Stored in the unmodifiable list to keep the static information 143 // from being modified. 144 private List<HdmiPortInfo> mPortInfo; 145 146 public HdmiControlService(Context context) { 147 super(context); 148 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 149 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 150 } 151 152 @Override 153 public void onStart() { 154 mIoThread.start(); 155 mCecController = HdmiCecController.create(this); 156 157 if (mCecController != null) { 158 initializeLocalDevices(mLocalDevices); 159 } else { 160 Slog.i(TAG, "Device does not support HDMI-CEC."); 161 } 162 163 mMhlController = HdmiMhlController.create(this); 164 if (mMhlController == null) { 165 Slog.i(TAG, "Device does not support MHL-control."); 166 } 167 mPortInfo = initPortInfo(); 168 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 169 170 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 171 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 172 mHdmiControlEnabled = true; 173 } 174 175 @ServiceThreadOnly 176 private void initializeLocalDevices(final List<Integer> deviceTypes) { 177 assertRunOnServiceThread(); 178 // A container for [Logical Address, Local device info]. 179 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 180 final SparseIntArray finished = new SparseIntArray(); 181 mCecController.clearLogicalAddress(); 182 for (int type : deviceTypes) { 183 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 184 localDevice.init(); 185 mCecController.allocateLogicalAddress(type, 186 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 187 @Override 188 public void onAllocated(int deviceType, int logicalAddress) { 189 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 190 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 191 } else { 192 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 193 localDevice.setDeviceInfo(deviceInfo); 194 mCecController.addLocalDevice(deviceType, localDevice); 195 mCecController.addLogicalAddress(logicalAddress); 196 devices.append(logicalAddress, localDevice); 197 } 198 finished.append(deviceType, logicalAddress); 199 200 // Address allocation completed for all devices. Notify each device. 201 if (deviceTypes.size() == finished.size()) { 202 notifyAddressAllocated(devices); 203 } 204 } 205 }); 206 } 207 } 208 209 @ServiceThreadOnly 210 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 211 assertRunOnServiceThread(); 212 for (int i = 0; i < devices.size(); ++i) { 213 int address = devices.keyAt(i); 214 HdmiCecLocalDevice device = devices.valueAt(i); 215 device.handleAddressAllocated(address); 216 } 217 } 218 219 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 220 // keep them in one place. 221 @ServiceThreadOnly 222 private List<HdmiPortInfo> initPortInfo() { 223 assertRunOnServiceThread(); 224 HdmiPortInfo[] cecPortInfo = null; 225 226 // CEC HAL provides majority of the info while MHL does only MHL support flag for 227 // each port. Return empty array if CEC HAL didn't provide the info. 228 if (mCecController != null) { 229 cecPortInfo = mCecController.getPortInfos(); 230 } 231 if (cecPortInfo == null) { 232 return Collections.emptyList(); 233 } 234 235 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 236 if (mMhlController != null) { 237 // TODO: Implement plumbing logic to get MHL port information. 238 // mhlPortInfo = mMhlController.getPortInfos(); 239 } 240 241 // Use the id (port number) to find the matched info between CEC and MHL to combine them 242 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 243 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 244 for (int i = 0; i < cecPortInfo.length; ++i) { 245 HdmiPortInfo cec = cecPortInfo[i]; 246 int id = cec.getId(); 247 boolean mhlInfoFound = false; 248 for (HdmiPortInfo mhl : mhlPortInfo) { 249 if (id == mhl.getId()) { 250 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 251 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 252 mhlInfoFound = true; 253 break; 254 } 255 } 256 if (!mhlInfoFound) { 257 result.add(cec); 258 } 259 } 260 261 return Collections.unmodifiableList(result); 262 } 263 264 /** 265 * Returns HDMI port information for the given port id. 266 * 267 * @param portId HDMI port id 268 * @return {@link HdmiPortInfo} for the given port 269 */ 270 HdmiPortInfo getPortInfo(int portId) { 271 // mPortInfo is an unmodifiable list and the only reference to its inner list. 272 // No lock is necessary. 273 for (HdmiPortInfo info : mPortInfo) { 274 if (portId == info.getId()) { 275 return info; 276 } 277 } 278 return null; 279 } 280 281 /** 282 * Returns the routing path (physical address) of the HDMI port for the given 283 * port id. 284 */ 285 int portIdToPath(int portId) { 286 HdmiPortInfo portInfo = getPortInfo(portId); 287 if (portInfo == null) { 288 Slog.e(TAG, "Cannot find the port info: " + portId); 289 return HdmiConstants.INVALID_PHYSICAL_ADDRESS; 290 } 291 return portInfo.getAddress(); 292 } 293 294 /** 295 * Returns the id of HDMI port located at the top of the hierarchy of 296 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 297 * the port id to be returned is the ID associated with the port address 298 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 299 */ 300 int pathToPortId(int path) { 301 int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK; 302 for (HdmiPortInfo info : mPortInfo) { 303 if (portAddress == info.getAddress()) { 304 return info.getId(); 305 } 306 } 307 return HdmiConstants.INVALID_PORT_ID; 308 } 309 310 /** 311 * Returns {@link Looper} for IO operation. 312 * 313 * <p>Declared as package-private. 314 */ 315 Looper getIoLooper() { 316 return mIoThread.getLooper(); 317 } 318 319 /** 320 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 321 * for tasks that are running on main service thread. 322 * 323 * <p>Declared as package-private. 324 */ 325 Looper getServiceLooper() { 326 return mHandler.getLooper(); 327 } 328 329 /** 330 * Returns physical address of the device. 331 */ 332 int getPhysicalAddress() { 333 return mCecController.getPhysicalAddress(); 334 } 335 336 /** 337 * Returns vendor id of CEC service. 338 */ 339 int getVendorId() { 340 return mCecController.getVendorId(); 341 } 342 343 @ServiceThreadOnly 344 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 345 assertRunOnServiceThread(); 346 HdmiCecLocalDeviceTv tv = tv(); 347 if (tv == null) { 348 return null; 349 } 350 return tv.getDeviceInfo(logicalAddress); 351 } 352 353 /** 354 * Returns version of CEC. 355 */ 356 int getCecVersion() { 357 return mCecController.getVersion(); 358 } 359 360 /** 361 * Whether a device of the specified physical address is connected to ARC enabled port. 362 */ 363 boolean isConnectedToArcPort(int physicalAddress) { 364 for (HdmiPortInfo portInfo : mPortInfo) { 365 if (hasSameTopPort(portInfo.getAddress(), physicalAddress) 366 && portInfo.isArcSupported()) { 367 return true; 368 } 369 } 370 return false; 371 } 372 373 void runOnServiceThread(Runnable runnable) { 374 mHandler.post(runnable); 375 } 376 377 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 378 mHandler.postAtFrontOfQueue(runnable); 379 } 380 381 private void assertRunOnServiceThread() { 382 if (Looper.myLooper() != mHandler.getLooper()) { 383 throw new IllegalStateException("Should run on service thread."); 384 } 385 } 386 387 /** 388 * Transmit a CEC command to CEC bus. 389 * 390 * @param command CEC command to send out 391 * @param callback interface used to the result of send command 392 */ 393 @ServiceThreadOnly 394 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 395 assertRunOnServiceThread(); 396 mCecController.sendCommand(command, callback); 397 } 398 399 @ServiceThreadOnly 400 void sendCecCommand(HdmiCecMessage command) { 401 assertRunOnServiceThread(); 402 mCecController.sendCommand(command, null); 403 } 404 405 @ServiceThreadOnly 406 boolean handleCecCommand(HdmiCecMessage message) { 407 assertRunOnServiceThread(); 408 return dispatchMessageToLocalDevice(message); 409 } 410 411 void setAudioReturnChannel(boolean enabled) { 412 mCecController.setAudioReturnChannel(enabled); 413 } 414 415 @ServiceThreadOnly 416 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 417 assertRunOnServiceThread(); 418 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 419 if (device.dispatchMessage(message) 420 && message.getDestination() != HdmiCec.ADDR_BROADCAST) { 421 return true; 422 } 423 } 424 425 if (message.getDestination() != HdmiCec.ADDR_BROADCAST) { 426 Slog.w(TAG, "Unhandled cec command:" + message); 427 } 428 return false; 429 } 430 431 /** 432 * Called when a new hotplug event is issued. 433 * 434 * @param portNo hdmi port number where hot plug event issued. 435 * @param connected whether to be plugged in or not 436 */ 437 @ServiceThreadOnly 438 void onHotplug(int portNo, boolean connected) { 439 assertRunOnServiceThread(); 440 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 441 device.onHotplug(portNo, connected); 442 } 443 announceHotplugEvent(portNo, connected); 444 } 445 446 /** 447 * Poll all remote devices. It sends <Polling Message> to all remote 448 * devices. 449 * 450 * @param callback an interface used to get a list of all remote devices' address 451 * @param sourceAddress a logical address of source device where sends polling message 452 * @param pickStrategy strategy how to pick polling candidates 453 * @param retryCount the number of retry used to send polling message to remote devices 454 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 455 */ 456 @ServiceThreadOnly 457 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 458 int retryCount) { 459 assertRunOnServiceThread(); 460 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 461 retryCount); 462 } 463 464 private int checkPollStrategy(int pickStrategy) { 465 int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK; 466 if (strategy == 0) { 467 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 468 } 469 int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK; 470 if (iterationStrategy == 0) { 471 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 472 } 473 return strategy | iterationStrategy; 474 } 475 476 List<HdmiCecLocalDevice> getAllLocalDevices() { 477 assertRunOnServiceThread(); 478 return mCecController.getLocalDeviceList(); 479 } 480 481 Object getServiceLock() { 482 return mLock; 483 } 484 485 void setAudioStatus(boolean mute, int volume) { 486 // TODO: Hook up with AudioManager. 487 } 488 489 void announceSystemAudioModeChange(boolean enabled) { 490 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 491 invokeSystemAudioModeChange(listener, enabled); 492 } 493 } 494 495 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 496 // TODO: find better name instead of model name. 497 String displayName = Build.MODEL; 498 return new HdmiCecDeviceInfo(logicalAddress, 499 getPhysicalAddress(), deviceType, getVendorId(), displayName); 500 } 501 502 // Record class that monitors the event of the caller of being killed. Used to clean up 503 // the listener list and record list accordingly. 504 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 505 private final IHdmiHotplugEventListener mListener; 506 507 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 508 mListener = listener; 509 } 510 511 @Override 512 public void binderDied() { 513 synchronized (mLock) { 514 mHotplugEventListenerRecords.remove(this); 515 mHotplugEventListeners.remove(mListener); 516 } 517 } 518 } 519 520 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 521 private final IHdmiDeviceEventListener mListener; 522 523 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 524 mListener = listener; 525 } 526 527 @Override 528 public void binderDied() { 529 synchronized (mLock) { 530 mDeviceEventListenerRecords.remove(this); 531 mDeviceEventListeners.remove(mListener); 532 } 533 } 534 } 535 536 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 537 private IHdmiSystemAudioModeChangeListener mListener; 538 539 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 540 mListener = listener; 541 } 542 543 @Override 544 public void binderDied() { 545 synchronized (mLock) { 546 mSystemAudioModeChangeListenerRecords.remove(this); 547 mSystemAudioModeChangeListeners.remove(mListener); 548 } 549 } 550 } 551 552 private void enforceAccessPermission() { 553 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 554 } 555 556 private final class BinderService extends IHdmiControlService.Stub { 557 @Override 558 public int[] getSupportedTypes() { 559 enforceAccessPermission(); 560 // mLocalDevices is an unmodifiable list - no lock necesary. 561 int[] localDevices = new int[mLocalDevices.size()]; 562 for (int i = 0; i < localDevices.length; ++i) { 563 localDevices[i] = mLocalDevices.get(i); 564 } 565 return localDevices; 566 } 567 568 @Override 569 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 570 enforceAccessPermission(); 571 runOnServiceThread(new Runnable() { 572 @Override 573 public void run() { 574 HdmiCecLocalDeviceTv tv = tv(); 575 if (tv == null) { 576 Slog.w(TAG, "Local tv device not available"); 577 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 578 return; 579 } 580 tv.deviceSelect(logicalAddress, callback); 581 } 582 }); 583 } 584 585 @Override 586 public void portSelect(final int portId, final IHdmiControlCallback callback) { 587 enforceAccessPermission(); 588 runOnServiceThread(new Runnable() { 589 @Override 590 public void run() { 591 HdmiCecLocalDeviceTv tv = tv(); 592 if (tv == null) { 593 Slog.w(TAG, "Local tv device not available"); 594 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 595 return; 596 } 597 tv.doManualPortSwitching(portId, callback); 598 } 599 }); 600 } 601 602 @Override 603 public void sendKeyEvent(final int keyCode, final boolean isPressed) { 604 enforceAccessPermission(); 605 runOnServiceThread(new Runnable() { 606 @Override 607 public void run() { 608 // TODO: sendKeyEvent is for TV device only for now. Allow other 609 // local devices of different types to use this as well. 610 HdmiCecLocalDeviceTv tv = tv(); 611 if (tv == null) { 612 Slog.w(TAG, "Local tv device not available"); 613 return; 614 } 615 tv.sendKeyEvent(keyCode, isPressed); 616 } 617 }); 618 } 619 620 @Override 621 public void oneTouchPlay(final IHdmiControlCallback callback) { 622 enforceAccessPermission(); 623 runOnServiceThread(new Runnable() { 624 @Override 625 public void run() { 626 HdmiControlService.this.oneTouchPlay(callback); 627 } 628 }); 629 } 630 631 @Override 632 public void queryDisplayStatus(final IHdmiControlCallback callback) { 633 enforceAccessPermission(); 634 runOnServiceThread(new Runnable() { 635 @Override 636 public void run() { 637 HdmiControlService.this.queryDisplayStatus(callback); 638 } 639 }); 640 } 641 642 @Override 643 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 644 enforceAccessPermission(); 645 runOnServiceThread(new Runnable() { 646 @Override 647 public void run() { 648 HdmiControlService.this.addHotplugEventListener(listener); 649 } 650 }); 651 } 652 653 @Override 654 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 655 enforceAccessPermission(); 656 runOnServiceThread(new Runnable() { 657 @Override 658 public void run() { 659 HdmiControlService.this.removeHotplugEventListener(listener); 660 } 661 }); 662 } 663 664 @Override 665 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 666 enforceAccessPermission(); 667 runOnServiceThread(new Runnable() { 668 @Override 669 public void run() { 670 HdmiControlService.this.addDeviceEventListener(listener); 671 } 672 }); 673 } 674 675 @Override 676 public List<HdmiPortInfo> getPortInfo() { 677 enforceAccessPermission(); 678 return mPortInfo; 679 } 680 681 @Override 682 public boolean canChangeSystemAudioMode() { 683 enforceAccessPermission(); 684 HdmiCecLocalDeviceTv tv = tv(); 685 if (tv == null) { 686 return false; 687 } 688 return tv.hasSystemAudioDevice(); 689 } 690 691 @Override 692 public boolean getSystemAudioMode() { 693 enforceAccessPermission(); 694 HdmiCecLocalDeviceTv tv = tv(); 695 if (tv == null) { 696 return false; 697 } 698 return tv.getSystemAudioMode(); 699 } 700 701 @Override 702 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 703 enforceAccessPermission(); 704 runOnServiceThread(new Runnable() { 705 @Override 706 public void run() { 707 HdmiCecLocalDeviceTv tv = tv(); 708 if (tv == null) { 709 Slog.w(TAG, "Local tv device not available"); 710 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 711 return; 712 } 713 tv.changeSystemAudioMode(enabled, callback); 714 } 715 }); 716 } 717 718 @Override 719 public void addSystemAudioModeChangeListener( 720 final IHdmiSystemAudioModeChangeListener listener) { 721 enforceAccessPermission(); 722 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 723 } 724 725 @Override 726 public void removeSystemAudioModeChangeListener( 727 final IHdmiSystemAudioModeChangeListener listener) { 728 enforceAccessPermission(); 729 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 730 } 731 732 @Override 733 public void setControlEnabled(final boolean enabled) { 734 enforceAccessPermission(); 735 synchronized (mLock) { 736 mHdmiControlEnabled = enabled; 737 } 738 // TODO: Stop the running actions when disabled, and start 739 // address allocation/device discovery when enabled. 740 if (!enabled) { 741 return; 742 } 743 runOnServiceThread(new Runnable() { 744 @Override 745 public void run() { 746 HdmiCecLocalDeviceTv tv = tv(); 747 if (tv == null) { 748 return; 749 } 750 int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED; 751 mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value); 752 if (mMhlController != null) { 753 mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value); 754 } 755 tv.routingAtEnableTime(); 756 } 757 }); 758 } 759 760 @Override 761 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 762 final int maxIndex) { 763 enforceAccessPermission(); 764 runOnServiceThread(new Runnable() { 765 @Override 766 public void run() { 767 HdmiCecLocalDeviceTv tv = tv(); 768 if (tv == null) { 769 Slog.w(TAG, "Local tv device not available"); 770 return; 771 } 772 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 773 } 774 }); 775 } 776 777 @Override 778 public void setSystemAudioMute(final boolean mute) { 779 enforceAccessPermission(); 780 runOnServiceThread(new Runnable() { 781 @Override 782 public void run() { 783 HdmiCecLocalDeviceTv tv = tv(); 784 if (tv == null) { 785 Slog.w(TAG, "Local tv device not available"); 786 return; 787 } 788 tv.changeMute(mute); 789 } 790 }); 791 } 792 793 @Override 794 public void setArcMode(final boolean enabled) { 795 enforceAccessPermission(); 796 runOnServiceThread(new Runnable() { 797 @Override 798 public void run() { 799 HdmiCecLocalDeviceTv tv = tv(); 800 if (tv == null) { 801 Log.w(TAG, "Local tv device not available to change arc mode."); 802 return; 803 } 804 } 805 }); 806 } 807 808 @Override 809 public void setOption(final int key, final int value) { 810 if (!isTvDevice()) { 811 return; 812 } 813 switch (key) { 814 case HdmiCec.OPTION_CEC_AUTO_WAKEUP: 815 mCecController.setOption(key, value); 816 break; 817 case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF: 818 // No need to pass this option to HAL. 819 tv().setAutoDeviceOff(value == HdmiCec.ENABLED); 820 break; 821 case HdmiCec.OPTION_MHL_INPUT_SWITCHING: // Fall through 822 case HdmiCec.OPTION_MHL_POWER_CHARGE: 823 if (mMhlController != null) { 824 mMhlController.setOption(key, value); 825 } 826 break; 827 } 828 } 829 830 private boolean isTvDevice() { 831 return tv() != null; 832 } 833 } 834 835 @ServiceThreadOnly 836 private void oneTouchPlay(final IHdmiControlCallback callback) { 837 assertRunOnServiceThread(); 838 HdmiCecLocalDevicePlayback source = playback(); 839 if (source == null) { 840 Slog.w(TAG, "Local playback device not available"); 841 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 842 return; 843 } 844 source.oneTouchPlay(callback); 845 } 846 847 @ServiceThreadOnly 848 private void queryDisplayStatus(final IHdmiControlCallback callback) { 849 assertRunOnServiceThread(); 850 HdmiCecLocalDevicePlayback source = playback(); 851 if (source == null) { 852 Slog.w(TAG, "Local playback device not available"); 853 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 854 return; 855 } 856 source.queryDisplayStatus(callback); 857 } 858 859 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 860 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 861 try { 862 listener.asBinder().linkToDeath(record, 0); 863 } catch (RemoteException e) { 864 Slog.w(TAG, "Listener already died"); 865 return; 866 } 867 synchronized (mLock) { 868 mHotplugEventListenerRecords.add(record); 869 mHotplugEventListeners.add(listener); 870 } 871 } 872 873 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 874 synchronized (mLock) { 875 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 876 if (record.mListener.asBinder() == listener.asBinder()) { 877 listener.asBinder().unlinkToDeath(record, 0); 878 mHotplugEventListenerRecords.remove(record); 879 break; 880 } 881 } 882 mHotplugEventListeners.remove(listener); 883 } 884 } 885 886 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 887 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 888 try { 889 listener.asBinder().linkToDeath(record, 0); 890 } catch (RemoteException e) { 891 Slog.w(TAG, "Listener already died"); 892 return; 893 } 894 synchronized (mLock) { 895 mDeviceEventListeners.add(listener); 896 mDeviceEventListenerRecords.add(record); 897 } 898 } 899 900 void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { 901 synchronized (mLock) { 902 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 903 try { 904 listener.onStatusChanged(device, activated); 905 } catch (RemoteException e) { 906 Slog.e(TAG, "Failed to report device event:" + e); 907 } 908 } 909 } 910 } 911 912 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 913 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 914 listener); 915 try { 916 listener.asBinder().linkToDeath(record, 0); 917 } catch (RemoteException e) { 918 Slog.w(TAG, "Listener already died"); 919 return; 920 } 921 synchronized (mLock) { 922 mSystemAudioModeChangeListeners.add(listener); 923 mSystemAudioModeChangeListenerRecords.add(record); 924 } 925 } 926 927 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 928 synchronized (mLock) { 929 for (SystemAudioModeChangeListenerRecord record : 930 mSystemAudioModeChangeListenerRecords) { 931 if (record.mListener.asBinder() == listener) { 932 listener.asBinder().unlinkToDeath(record, 0); 933 mSystemAudioModeChangeListenerRecords.remove(record); 934 break; 935 } 936 } 937 mSystemAudioModeChangeListeners.remove(listener); 938 } 939 } 940 941 private void invokeCallback(IHdmiControlCallback callback, int result) { 942 try { 943 callback.onComplete(result); 944 } catch (RemoteException e) { 945 Slog.e(TAG, "Invoking callback failed:" + e); 946 } 947 } 948 949 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 950 boolean enabled) { 951 try { 952 listener.onStatusChanged(enabled); 953 } catch (RemoteException e) { 954 Slog.e(TAG, "Invoking callback failed:" + e); 955 } 956 } 957 958 private void announceHotplugEvent(int portId, boolean connected) { 959 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 960 synchronized (mLock) { 961 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 962 invokeHotplugEventListenerLocked(listener, event); 963 } 964 } 965 } 966 967 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 968 HdmiHotplugEvent event) { 969 try { 970 listener.onReceived(event); 971 } catch (RemoteException e) { 972 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 973 } 974 } 975 976 private static boolean hasSameTopPort(int path1, int path2) { 977 return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) 978 == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); 979 } 980 981 private HdmiCecLocalDeviceTv tv() { 982 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 983 } 984 985 private HdmiCecLocalDevicePlayback playback() { 986 return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 987 } 988 989 AudioManager getAudioManager() { 990 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 991 } 992 993 boolean isControlEnabled() { 994 synchronized (mLock) { 995 return mHdmiControlEnabled; 996 } 997 } 998} 999