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