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