HdmiControlService.java revision 41d974631c5f525da49c88d34cecedd5a4cfeda8
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 pickStrategy strategy how to pick polling candidates 452 * @param retryCount the number of retry used to send polling message to remote devices 453 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 454 */ 455 @ServiceThreadOnly 456 void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) { 457 assertRunOnServiceThread(); 458 mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount); 459 } 460 461 private int checkPollStrategy(int pickStrategy) { 462 int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK; 463 if (strategy == 0) { 464 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 465 } 466 int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK; 467 if (iterationStrategy == 0) { 468 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 469 } 470 return strategy | iterationStrategy; 471 } 472 473 List<HdmiCecLocalDevice> getAllLocalDevices() { 474 assertRunOnServiceThread(); 475 return mCecController.getLocalDeviceList(); 476 } 477 478 Object getServiceLock() { 479 return mLock; 480 } 481 482 void setAudioStatus(boolean mute, int volume) { 483 // TODO: Hook up with AudioManager. 484 } 485 486 void announceSystemAudioModeChange(boolean enabled) { 487 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 488 invokeSystemAudioModeChange(listener, enabled); 489 } 490 } 491 492 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 493 // TODO: find better name instead of model name. 494 String displayName = Build.MODEL; 495 return new HdmiCecDeviceInfo(logicalAddress, 496 getPhysicalAddress(), deviceType, getVendorId(), displayName); 497 } 498 499 // Record class that monitors the event of the caller of being killed. Used to clean up 500 // the listener list and record list accordingly. 501 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 502 private final IHdmiHotplugEventListener mListener; 503 504 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 505 mListener = listener; 506 } 507 508 @Override 509 public void binderDied() { 510 synchronized (mLock) { 511 mHotplugEventListenerRecords.remove(this); 512 mHotplugEventListeners.remove(mListener); 513 } 514 } 515 } 516 517 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 518 private final IHdmiDeviceEventListener mListener; 519 520 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 521 mListener = listener; 522 } 523 524 @Override 525 public void binderDied() { 526 synchronized (mLock) { 527 mDeviceEventListenerRecords.remove(this); 528 mDeviceEventListeners.remove(mListener); 529 } 530 } 531 } 532 533 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 534 private IHdmiSystemAudioModeChangeListener mListener; 535 536 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 537 mListener = listener; 538 } 539 540 @Override 541 public void binderDied() { 542 synchronized (mLock) { 543 mSystemAudioModeChangeListenerRecords.remove(this); 544 mSystemAudioModeChangeListeners.remove(mListener); 545 } 546 } 547 } 548 549 private void enforceAccessPermission() { 550 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 551 } 552 553 private final class BinderService extends IHdmiControlService.Stub { 554 @Override 555 public int[] getSupportedTypes() { 556 enforceAccessPermission(); 557 // mLocalDevices is an unmodifiable list - no lock necesary. 558 int[] localDevices = new int[mLocalDevices.size()]; 559 for (int i = 0; i < localDevices.length; ++i) { 560 localDevices[i] = mLocalDevices.get(i); 561 } 562 return localDevices; 563 } 564 565 @Override 566 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 567 enforceAccessPermission(); 568 runOnServiceThread(new Runnable() { 569 @Override 570 public void run() { 571 HdmiCecLocalDeviceTv tv = tv(); 572 if (tv == null) { 573 Slog.w(TAG, "Local tv device not available"); 574 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 575 return; 576 } 577 tv.deviceSelect(logicalAddress, callback); 578 } 579 }); 580 } 581 582 @Override 583 public void portSelect(final int portId, final IHdmiControlCallback callback) { 584 enforceAccessPermission(); 585 runOnServiceThread(new Runnable() { 586 @Override 587 public void run() { 588 HdmiCecLocalDeviceTv tv = tv(); 589 if (tv == null) { 590 Slog.w(TAG, "Local tv device not available"); 591 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 592 return; 593 } 594 tv.doManualPortSwitching(portId, callback); 595 } 596 }); 597 } 598 599 @Override 600 public void sendKeyEvent(final int keyCode, final boolean isPressed) { 601 enforceAccessPermission(); 602 runOnServiceThread(new Runnable() { 603 @Override 604 public void run() { 605 // TODO: sendKeyEvent is for TV device only for now. Allow other 606 // local devices of different types to use this as well. 607 HdmiCecLocalDeviceTv tv = tv(); 608 if (tv == null) { 609 Slog.w(TAG, "Local tv device not available"); 610 return; 611 } 612 tv.sendKeyEvent(keyCode, isPressed); 613 } 614 }); 615 } 616 617 @Override 618 public void oneTouchPlay(final IHdmiControlCallback callback) { 619 enforceAccessPermission(); 620 runOnServiceThread(new Runnable() { 621 @Override 622 public void run() { 623 HdmiControlService.this.oneTouchPlay(callback); 624 } 625 }); 626 } 627 628 @Override 629 public void queryDisplayStatus(final IHdmiControlCallback callback) { 630 enforceAccessPermission(); 631 runOnServiceThread(new Runnable() { 632 @Override 633 public void run() { 634 HdmiControlService.this.queryDisplayStatus(callback); 635 } 636 }); 637 } 638 639 @Override 640 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 641 enforceAccessPermission(); 642 runOnServiceThread(new Runnable() { 643 @Override 644 public void run() { 645 HdmiControlService.this.addHotplugEventListener(listener); 646 } 647 }); 648 } 649 650 @Override 651 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 652 enforceAccessPermission(); 653 runOnServiceThread(new Runnable() { 654 @Override 655 public void run() { 656 HdmiControlService.this.removeHotplugEventListener(listener); 657 } 658 }); 659 } 660 661 @Override 662 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 663 enforceAccessPermission(); 664 runOnServiceThread(new Runnable() { 665 @Override 666 public void run() { 667 HdmiControlService.this.addDeviceEventListener(listener); 668 } 669 }); 670 } 671 672 @Override 673 public List<HdmiPortInfo> getPortInfo() { 674 enforceAccessPermission(); 675 return mPortInfo; 676 } 677 678 @Override 679 public boolean canChangeSystemAudioMode() { 680 enforceAccessPermission(); 681 HdmiCecLocalDeviceTv tv = tv(); 682 if (tv == null) { 683 return false; 684 } 685 return tv.hasSystemAudioDevice(); 686 } 687 688 @Override 689 public boolean getSystemAudioMode() { 690 enforceAccessPermission(); 691 HdmiCecLocalDeviceTv tv = tv(); 692 if (tv == null) { 693 return false; 694 } 695 return tv.getSystemAudioMode(); 696 } 697 698 @Override 699 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 700 enforceAccessPermission(); 701 runOnServiceThread(new Runnable() { 702 @Override 703 public void run() { 704 HdmiCecLocalDeviceTv tv = tv(); 705 if (tv == null) { 706 Slog.w(TAG, "Local tv device not available"); 707 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 708 return; 709 } 710 tv.changeSystemAudioMode(enabled, callback); 711 } 712 }); 713 } 714 715 @Override 716 public void addSystemAudioModeChangeListener( 717 final IHdmiSystemAudioModeChangeListener listener) { 718 enforceAccessPermission(); 719 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 720 } 721 722 @Override 723 public void removeSystemAudioModeChangeListener( 724 final IHdmiSystemAudioModeChangeListener listener) { 725 enforceAccessPermission(); 726 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 727 } 728 729 @Override 730 public void setControlEnabled(final boolean enabled) { 731 enforceAccessPermission(); 732 synchronized (mLock) { 733 mHdmiControlEnabled = enabled; 734 } 735 // TODO: Stop the running actions when disabled, and start 736 // address allocation/device discovery when enabled. 737 if (!enabled) { 738 return; 739 } 740 runOnServiceThread(new Runnable() { 741 @Override 742 public void run() { 743 HdmiCecLocalDeviceTv tv = tv(); 744 if (tv == null) { 745 return; 746 } 747 int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED; 748 mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value); 749 if (mMhlController != null) { 750 mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value); 751 } 752 tv.routingAtEnableTime(); 753 } 754 }); 755 } 756 757 @Override 758 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 759 final int maxIndex) { 760 enforceAccessPermission(); 761 runOnServiceThread(new Runnable() { 762 @Override 763 public void run() { 764 HdmiCecLocalDeviceTv tv = tv(); 765 if (tv == null) { 766 Slog.w(TAG, "Local tv device not available"); 767 return; 768 } 769 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 770 } 771 }); 772 } 773 774 @Override 775 public void setSystemAudioMute(final boolean mute) { 776 enforceAccessPermission(); 777 runOnServiceThread(new Runnable() { 778 @Override 779 public void run() { 780 HdmiCecLocalDeviceTv tv = tv(); 781 if (tv == null) { 782 Slog.w(TAG, "Local tv device not available"); 783 return; 784 } 785 tv.changeMute(mute); 786 } 787 }); 788 } 789 790 @Override 791 public void setArcMode(final boolean enabled) { 792 enforceAccessPermission(); 793 runOnServiceThread(new Runnable() { 794 @Override 795 public void run() { 796 HdmiCecLocalDeviceTv tv = tv(); 797 if (tv == null) { 798 Log.w(TAG, "Local tv device not available to change arc mode."); 799 return; 800 } 801 } 802 }); 803 } 804 805 @Override 806 public void setOption(final int key, final int value) { 807 if (!isTvDevice()) { 808 return; 809 } 810 switch (key) { 811 case HdmiCec.OPTION_CEC_AUTO_WAKEUP: 812 mCecController.setOption(key, value); 813 break; 814 case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF: 815 // No need to pass this option to HAL. 816 tv().setAutoDeviceOff(value == HdmiCec.ENABLED); 817 break; 818 case HdmiCec.OPTION_MHL_INPUT_SWITCHING: // Fall through 819 case HdmiCec.OPTION_MHL_POWER_CHARGE: 820 if (mMhlController != null) { 821 mMhlController.setOption(key, value); 822 } 823 break; 824 } 825 } 826 827 private boolean isTvDevice() { 828 return tv() != null; 829 } 830 } 831 832 @ServiceThreadOnly 833 private void oneTouchPlay(final IHdmiControlCallback callback) { 834 assertRunOnServiceThread(); 835 HdmiCecLocalDevicePlayback source = playback(); 836 if (source == null) { 837 Slog.w(TAG, "Local playback device not available"); 838 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 839 return; 840 } 841 source.oneTouchPlay(callback); 842 } 843 844 @ServiceThreadOnly 845 private void queryDisplayStatus(final IHdmiControlCallback callback) { 846 assertRunOnServiceThread(); 847 HdmiCecLocalDevicePlayback source = playback(); 848 if (source == null) { 849 Slog.w(TAG, "Local playback device not available"); 850 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 851 return; 852 } 853 source.queryDisplayStatus(callback); 854 } 855 856 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 857 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 858 try { 859 listener.asBinder().linkToDeath(record, 0); 860 } catch (RemoteException e) { 861 Slog.w(TAG, "Listener already died"); 862 return; 863 } 864 synchronized (mLock) { 865 mHotplugEventListenerRecords.add(record); 866 mHotplugEventListeners.add(listener); 867 } 868 } 869 870 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 871 synchronized (mLock) { 872 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 873 if (record.mListener.asBinder() == listener.asBinder()) { 874 listener.asBinder().unlinkToDeath(record, 0); 875 mHotplugEventListenerRecords.remove(record); 876 break; 877 } 878 } 879 mHotplugEventListeners.remove(listener); 880 } 881 } 882 883 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 884 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 885 try { 886 listener.asBinder().linkToDeath(record, 0); 887 } catch (RemoteException e) { 888 Slog.w(TAG, "Listener already died"); 889 return; 890 } 891 synchronized (mLock) { 892 mDeviceEventListeners.add(listener); 893 mDeviceEventListenerRecords.add(record); 894 } 895 } 896 897 void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { 898 synchronized (mLock) { 899 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 900 try { 901 listener.onStatusChanged(device, activated); 902 } catch (RemoteException e) { 903 Slog.e(TAG, "Failed to report device event:" + e); 904 } 905 } 906 } 907 } 908 909 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 910 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 911 listener); 912 try { 913 listener.asBinder().linkToDeath(record, 0); 914 } catch (RemoteException e) { 915 Slog.w(TAG, "Listener already died"); 916 return; 917 } 918 synchronized (mLock) { 919 mSystemAudioModeChangeListeners.add(listener); 920 mSystemAudioModeChangeListenerRecords.add(record); 921 } 922 } 923 924 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 925 synchronized (mLock) { 926 for (SystemAudioModeChangeListenerRecord record : 927 mSystemAudioModeChangeListenerRecords) { 928 if (record.mListener.asBinder() == listener) { 929 listener.asBinder().unlinkToDeath(record, 0); 930 mSystemAudioModeChangeListenerRecords.remove(record); 931 break; 932 } 933 } 934 mSystemAudioModeChangeListeners.remove(listener); 935 } 936 } 937 938 private void invokeCallback(IHdmiControlCallback callback, int result) { 939 try { 940 callback.onComplete(result); 941 } catch (RemoteException e) { 942 Slog.e(TAG, "Invoking callback failed:" + e); 943 } 944 } 945 946 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 947 boolean enabled) { 948 try { 949 listener.onStatusChanged(enabled); 950 } catch (RemoteException e) { 951 Slog.e(TAG, "Invoking callback failed:" + e); 952 } 953 } 954 955 private void announceHotplugEvent(int portId, boolean connected) { 956 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 957 synchronized (mLock) { 958 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 959 invokeHotplugEventListenerLocked(listener, event); 960 } 961 } 962 } 963 964 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 965 HdmiHotplugEvent event) { 966 try { 967 listener.onReceived(event); 968 } catch (RemoteException e) { 969 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 970 } 971 } 972 973 private static boolean hasSameTopPort(int path1, int path2) { 974 return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) 975 == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); 976 } 977 978 private HdmiCecLocalDeviceTv tv() { 979 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 980 } 981 982 private HdmiCecLocalDevicePlayback playback() { 983 return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 984 } 985 986 AudioManager getAudioManager() { 987 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 988 } 989 990 boolean isControlEnabled() { 991 synchronized (mLock) { 992 return mHdmiControlEnabled; 993 } 994 } 995} 996