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