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