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