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