HdmiControlService.java revision 9c37e1f53ea4734bfe5ae156dc5399ce5f2c7ccc
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.BroadcastReceiver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.hardware.hdmi.HdmiCec; 25import android.hardware.hdmi.HdmiCecDeviceInfo; 26import android.hardware.hdmi.HdmiCecMessage; 27import android.hardware.hdmi.HdmiHotplugEvent; 28import android.hardware.hdmi.HdmiPortInfo; 29import android.hardware.hdmi.IHdmiControlCallback; 30import android.hardware.hdmi.IHdmiControlService; 31import android.hardware.hdmi.IHdmiDeviceEventListener; 32import android.hardware.hdmi.IHdmiHotplugEventListener; 33import android.hardware.hdmi.IHdmiInputChangeListener; 34import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 35import android.media.AudioManager; 36import android.os.Build; 37import android.os.Handler; 38import android.os.HandlerThread; 39import android.os.IBinder; 40import android.os.Looper; 41import android.os.PowerManager; 42import android.os.RemoteException; 43import android.os.SystemClock; 44import android.util.Slog; 45import android.util.SparseArray; 46import android.util.SparseIntArray; 47 48import com.android.internal.annotations.GuardedBy; 49import com.android.server.SystemService; 50import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 51import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 52 53import java.util.ArrayList; 54import java.util.Collections; 55import java.util.List; 56 57/** 58 * Provides a service for sending and processing HDMI control messages, 59 * HDMI-CEC and MHL control command, and providing the information on both standard. 60 */ 61public final class HdmiControlService extends SystemService { 62 private static final String TAG = "HdmiControlService"; 63 64 // TODO: Rename the permission to HDMI_CONTROL. 65 private static final String PERMISSION = "android.permission.HDMI_CEC"; 66 67 /** 68 * Interface to report send result. 69 */ 70 interface SendMessageCallback { 71 /** 72 * Called when {@link HdmiControlService#sendCecCommand} is completed. 73 * 74 * @param error result of send request. 75 * @see {@link #SEND_RESULT_SUCCESS} 76 * @see {@link #SEND_RESULT_NAK} 77 * @see {@link #SEND_RESULT_FAILURE} 78 */ 79 void onSendCompleted(int error); 80 } 81 82 /** 83 * Interface to get a list of available logical devices. 84 */ 85 interface DevicePollingCallback { 86 /** 87 * Called when device polling is finished. 88 * 89 * @param ackedAddress a list of logical addresses of available devices 90 */ 91 void onPollingFinished(List<Integer> ackedAddress); 92 } 93 94 private class PowerStateReceiver extends BroadcastReceiver { 95 @Override 96 public void onReceive(Context context, Intent intent) { 97 switch (intent.getAction()) { 98 case Intent.ACTION_SCREEN_OFF: 99 if (isPowerOnOrTransient()) { 100 onStandby(); 101 } 102 break; 103 case Intent.ACTION_SCREEN_ON: 104 if (isPowerStandbyOrTransient()) { 105 onWakeUp(); 106 } 107 break; 108 } 109 } 110 } 111 112 // A thread to handle synchronous IO of CEC and MHL control service. 113 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 114 // and sparse call it shares a thread to handle IO operations. 115 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 116 117 // Used to synchronize the access to the service. 118 private final Object mLock = new Object(); 119 120 // Type of logical devices hosted in the system. Stored in the unmodifiable list. 121 private final List<Integer> mLocalDevices; 122 123 // List of listeners registered by callers that want to get notified of 124 // hotplug events. 125 @GuardedBy("mLock") 126 private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>(); 127 128 // List of records for hotplug event listener to handle the the caller killed in action. 129 @GuardedBy("mLock") 130 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 131 new ArrayList<>(); 132 133 // List of listeners registered by callers that want to get notified of 134 // device status events. 135 @GuardedBy("mLock") 136 private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>(); 137 138 // List of records for device event listener to handle the the caller killed in action. 139 @GuardedBy("mLock") 140 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 141 new ArrayList<>(); 142 143 @GuardedBy("mLock") 144 private IHdmiInputChangeListener mInputChangeListener; 145 146 @GuardedBy("mLock") 147 private InputChangeListenerRecord mInputChangeListenerRecord; 148 149 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 150 // handling will be disabled and no request will be handled. 151 @GuardedBy("mLock") 152 private boolean mHdmiControlEnabled; 153 154 // List of listeners registered by callers that want to get notified of 155 // system audio mode changes. 156 private final ArrayList<IHdmiSystemAudioModeChangeListener> 157 mSystemAudioModeChangeListeners = new ArrayList<>(); 158 // List of records for system audio mode change to handle the the caller killed in action. 159 private final ArrayList<SystemAudioModeChangeListenerRecord> 160 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 161 162 // Handler used to run a task in service thread. 163 private final Handler mHandler = new Handler(); 164 165 @Nullable 166 private HdmiCecController mCecController; 167 168 @Nullable 169 private HdmiMhlController mMhlController; 170 171 // HDMI port information. Stored in the unmodifiable list to keep the static information 172 // from being modified. 173 private List<HdmiPortInfo> mPortInfo; 174 175 private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver(); 176 177 @ServiceThreadOnly 178 private int mPowerStatus = HdmiCec.POWER_STATUS_STANDBY; 179 180 @ServiceThreadOnly 181 private boolean mStandbyMessageReceived = false; 182 183 public HdmiControlService(Context context) { 184 super(context); 185 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 186 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 187 } 188 189 @Override 190 public void onStart() { 191 mIoThread.start(); 192 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 193 mCecController = HdmiCecController.create(this); 194 195 if (mCecController != null) { 196 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED); 197 initializeLocalDevices(mLocalDevices); 198 } else { 199 Slog.i(TAG, "Device does not support HDMI-CEC."); 200 } 201 202 mMhlController = HdmiMhlController.create(this); 203 if (mMhlController == null) { 204 Slog.i(TAG, "Device does not support MHL-control."); 205 } 206 mPortInfo = initPortInfo(); 207 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 208 209 // Register broadcast receiver for power state change. 210 if (mCecController != null || mMhlController != null) { 211 IntentFilter filter = new IntentFilter(); 212 filter.addAction(Intent.ACTION_SCREEN_OFF); 213 filter.addAction(Intent.ACTION_SCREEN_ON); 214 getContext().registerReceiver(mPowerStateReceiver, filter); 215 } 216 217 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 218 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 219 mHdmiControlEnabled = true; 220 } 221 222 @ServiceThreadOnly 223 private void initializeLocalDevices(final List<Integer> deviceTypes) { 224 assertRunOnServiceThread(); 225 // A container for [Logical Address, Local device info]. 226 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 227 final SparseIntArray finished = new SparseIntArray(); 228 mCecController.clearLogicalAddress(); 229 for (int type : deviceTypes) { 230 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 231 localDevice.init(); 232 mCecController.allocateLogicalAddress(type, 233 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 234 @Override 235 public void onAllocated(int deviceType, int logicalAddress) { 236 if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) { 237 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 238 } else { 239 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 240 localDevice.setDeviceInfo(deviceInfo); 241 mCecController.addLocalDevice(deviceType, localDevice); 242 mCecController.addLogicalAddress(logicalAddress); 243 devices.append(logicalAddress, localDevice); 244 } 245 finished.append(deviceType, logicalAddress); 246 247 // Address allocation completed for all devices. Notify each device. 248 if (deviceTypes.size() == finished.size()) { 249 if (mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON) { 250 mPowerStatus = HdmiCec.POWER_STATUS_ON; 251 } 252 notifyAddressAllocated(devices); 253 } 254 } 255 }); 256 } 257 } 258 259 @ServiceThreadOnly 260 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 261 assertRunOnServiceThread(); 262 for (int i = 0; i < devices.size(); ++i) { 263 int address = devices.keyAt(i); 264 HdmiCecLocalDevice device = devices.valueAt(i); 265 device.handleAddressAllocated(address); 266 } 267 } 268 269 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 270 // keep them in one place. 271 @ServiceThreadOnly 272 private List<HdmiPortInfo> initPortInfo() { 273 assertRunOnServiceThread(); 274 HdmiPortInfo[] cecPortInfo = null; 275 276 // CEC HAL provides majority of the info while MHL does only MHL support flag for 277 // each port. Return empty array if CEC HAL didn't provide the info. 278 if (mCecController != null) { 279 cecPortInfo = mCecController.getPortInfos(); 280 } 281 if (cecPortInfo == null) { 282 return Collections.emptyList(); 283 } 284 285 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 286 if (mMhlController != null) { 287 // TODO: Implement plumbing logic to get MHL port information. 288 // mhlPortInfo = mMhlController.getPortInfos(); 289 } 290 291 // Use the id (port number) to find the matched info between CEC and MHL to combine them 292 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 293 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 294 for (int i = 0; i < cecPortInfo.length; ++i) { 295 HdmiPortInfo cec = cecPortInfo[i]; 296 int id = cec.getId(); 297 boolean mhlInfoFound = false; 298 for (HdmiPortInfo mhl : mhlPortInfo) { 299 if (id == mhl.getId()) { 300 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 301 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 302 mhlInfoFound = true; 303 break; 304 } 305 } 306 if (!mhlInfoFound) { 307 result.add(cec); 308 } 309 } 310 311 return Collections.unmodifiableList(result); 312 } 313 314 /** 315 * Returns HDMI port information for the given port id. 316 * 317 * @param portId HDMI port id 318 * @return {@link HdmiPortInfo} for the given port 319 */ 320 HdmiPortInfo getPortInfo(int portId) { 321 // mPortInfo is an unmodifiable list and the only reference to its inner list. 322 // No lock is necessary. 323 for (HdmiPortInfo info : mPortInfo) { 324 if (portId == info.getId()) { 325 return info; 326 } 327 } 328 return null; 329 } 330 331 /** 332 * Returns the routing path (physical address) of the HDMI port for the given 333 * port id. 334 */ 335 int portIdToPath(int portId) { 336 HdmiPortInfo portInfo = getPortInfo(portId); 337 if (portInfo == null) { 338 Slog.e(TAG, "Cannot find the port info: " + portId); 339 return HdmiConstants.INVALID_PHYSICAL_ADDRESS; 340 } 341 return portInfo.getAddress(); 342 } 343 344 /** 345 * Returns the id of HDMI port located at the top of the hierarchy of 346 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 347 * the port id to be returned is the ID associated with the port address 348 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 349 */ 350 int pathToPortId(int path) { 351 int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK; 352 for (HdmiPortInfo info : mPortInfo) { 353 if (portAddress == info.getAddress()) { 354 return info.getId(); 355 } 356 } 357 return HdmiConstants.INVALID_PORT_ID; 358 } 359 360 /** 361 * Returns {@link Looper} for IO operation. 362 * 363 * <p>Declared as package-private. 364 */ 365 Looper getIoLooper() { 366 return mIoThread.getLooper(); 367 } 368 369 /** 370 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 371 * for tasks that are running on main service thread. 372 * 373 * <p>Declared as package-private. 374 */ 375 Looper getServiceLooper() { 376 return mHandler.getLooper(); 377 } 378 379 /** 380 * Returns physical address of the device. 381 */ 382 int getPhysicalAddress() { 383 return mCecController.getPhysicalAddress(); 384 } 385 386 /** 387 * Returns vendor id of CEC service. 388 */ 389 int getVendorId() { 390 return mCecController.getVendorId(); 391 } 392 393 @ServiceThreadOnly 394 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 395 assertRunOnServiceThread(); 396 HdmiCecLocalDeviceTv tv = tv(); 397 if (tv == null) { 398 return null; 399 } 400 return tv.getDeviceInfo(logicalAddress); 401 } 402 403 /** 404 * Returns version of CEC. 405 */ 406 int getCecVersion() { 407 return mCecController.getVersion(); 408 } 409 410 /** 411 * Whether a device of the specified physical address is connected to ARC enabled port. 412 */ 413 boolean isConnectedToArcPort(int physicalAddress) { 414 for (HdmiPortInfo portInfo : mPortInfo) { 415 if (hasSameTopPort(portInfo.getAddress(), physicalAddress) 416 && portInfo.isArcSupported()) { 417 return true; 418 } 419 } 420 return false; 421 } 422 423 void runOnServiceThread(Runnable runnable) { 424 mHandler.post(runnable); 425 } 426 427 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 428 mHandler.postAtFrontOfQueue(runnable); 429 } 430 431 private void assertRunOnServiceThread() { 432 if (Looper.myLooper() != mHandler.getLooper()) { 433 throw new IllegalStateException("Should run on service thread."); 434 } 435 } 436 437 /** 438 * Transmit a CEC command to CEC bus. 439 * 440 * @param command CEC command to send out 441 * @param callback interface used to the result of send command 442 */ 443 @ServiceThreadOnly 444 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 445 assertRunOnServiceThread(); 446 mCecController.sendCommand(command, callback); 447 } 448 449 @ServiceThreadOnly 450 void sendCecCommand(HdmiCecMessage command) { 451 assertRunOnServiceThread(); 452 mCecController.sendCommand(command, null); 453 } 454 455 @ServiceThreadOnly 456 boolean handleCecCommand(HdmiCecMessage message) { 457 assertRunOnServiceThread(); 458 return dispatchMessageToLocalDevice(message); 459 } 460 461 void setAudioReturnChannel(boolean enabled) { 462 mCecController.setAudioReturnChannel(enabled); 463 } 464 465 @ServiceThreadOnly 466 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 467 assertRunOnServiceThread(); 468 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 469 if (device.dispatchMessage(message) 470 && message.getDestination() != HdmiCec.ADDR_BROADCAST) { 471 return true; 472 } 473 } 474 475 if (message.getDestination() != HdmiCec.ADDR_BROADCAST) { 476 Slog.w(TAG, "Unhandled cec command:" + message); 477 } 478 return false; 479 } 480 481 /** 482 * Called when a new hotplug event is issued. 483 * 484 * @param portNo hdmi port number where hot plug event issued. 485 * @param connected whether to be plugged in or not 486 */ 487 @ServiceThreadOnly 488 void onHotplug(int portNo, boolean connected) { 489 assertRunOnServiceThread(); 490 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 491 device.onHotplug(portNo, connected); 492 } 493 announceHotplugEvent(portNo, connected); 494 } 495 496 /** 497 * Poll all remote devices. It sends <Polling Message> to all remote 498 * devices. 499 * 500 * @param callback an interface used to get a list of all remote devices' address 501 * @param sourceAddress a logical address of source device where sends polling message 502 * @param pickStrategy strategy how to pick polling candidates 503 * @param retryCount the number of retry used to send polling message to remote devices 504 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 505 */ 506 @ServiceThreadOnly 507 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 508 int retryCount) { 509 assertRunOnServiceThread(); 510 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 511 retryCount); 512 } 513 514 private int checkPollStrategy(int pickStrategy) { 515 int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK; 516 if (strategy == 0) { 517 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 518 } 519 int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK; 520 if (iterationStrategy == 0) { 521 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 522 } 523 return strategy | iterationStrategy; 524 } 525 526 List<HdmiCecLocalDevice> getAllLocalDevices() { 527 assertRunOnServiceThread(); 528 return mCecController.getLocalDeviceList(); 529 } 530 531 Object getServiceLock() { 532 return mLock; 533 } 534 535 void setAudioStatus(boolean mute, int volume) { 536 // TODO: Hook up with AudioManager. 537 } 538 539 void announceSystemAudioModeChange(boolean enabled) { 540 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 541 invokeSystemAudioModeChange(listener, enabled); 542 } 543 } 544 545 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 546 // TODO: find better name instead of model name. 547 String displayName = Build.MODEL; 548 return new HdmiCecDeviceInfo(logicalAddress, 549 getPhysicalAddress(), deviceType, getVendorId(), displayName); 550 } 551 552 // Record class that monitors the event of the caller of being killed. Used to clean up 553 // the listener list and record list accordingly. 554 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 555 private final IHdmiHotplugEventListener mListener; 556 557 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 558 mListener = listener; 559 } 560 561 @Override 562 public void binderDied() { 563 synchronized (mLock) { 564 mHotplugEventListenerRecords.remove(this); 565 mHotplugEventListeners.remove(mListener); 566 } 567 } 568 } 569 570 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 571 private final IHdmiDeviceEventListener mListener; 572 573 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 574 mListener = listener; 575 } 576 577 @Override 578 public void binderDied() { 579 synchronized (mLock) { 580 mDeviceEventListenerRecords.remove(this); 581 mDeviceEventListeners.remove(mListener); 582 } 583 } 584 } 585 586 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 587 private final IHdmiSystemAudioModeChangeListener mListener; 588 589 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 590 mListener = listener; 591 } 592 593 @Override 594 public void binderDied() { 595 synchronized (mLock) { 596 mSystemAudioModeChangeListenerRecords.remove(this); 597 mSystemAudioModeChangeListeners.remove(mListener); 598 } 599 } 600 } 601 602 private void enforceAccessPermission() { 603 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 604 } 605 606 private final class BinderService extends IHdmiControlService.Stub { 607 @Override 608 public int[] getSupportedTypes() { 609 enforceAccessPermission(); 610 // mLocalDevices is an unmodifiable list - no lock necesary. 611 int[] localDevices = new int[mLocalDevices.size()]; 612 for (int i = 0; i < localDevices.length; ++i) { 613 localDevices[i] = mLocalDevices.get(i); 614 } 615 return localDevices; 616 } 617 618 @Override 619 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 620 enforceAccessPermission(); 621 runOnServiceThread(new Runnable() { 622 @Override 623 public void run() { 624 HdmiCecLocalDeviceTv tv = tv(); 625 if (tv == null) { 626 Slog.w(TAG, "Local tv device not available"); 627 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 628 return; 629 } 630 tv.deviceSelect(logicalAddress, callback); 631 } 632 }); 633 } 634 635 @Override 636 public void portSelect(final int portId, final IHdmiControlCallback callback) { 637 enforceAccessPermission(); 638 runOnServiceThread(new Runnable() { 639 @Override 640 public void run() { 641 HdmiCecLocalDeviceTv tv = tv(); 642 if (tv == null) { 643 Slog.w(TAG, "Local tv device not available"); 644 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 645 return; 646 } 647 tv.doManualPortSwitching(portId, callback); 648 } 649 }); 650 } 651 652 @Override 653 public void sendKeyEvent(final int keyCode, final boolean isPressed) { 654 enforceAccessPermission(); 655 runOnServiceThread(new Runnable() { 656 @Override 657 public void run() { 658 // TODO: sendKeyEvent is for TV device only for now. Allow other 659 // local devices of different types to use this as well. 660 HdmiCecLocalDeviceTv tv = tv(); 661 if (tv == null) { 662 Slog.w(TAG, "Local tv device not available"); 663 return; 664 } 665 tv.sendKeyEvent(keyCode, isPressed); 666 } 667 }); 668 } 669 670 @Override 671 public void oneTouchPlay(final IHdmiControlCallback callback) { 672 enforceAccessPermission(); 673 runOnServiceThread(new Runnable() { 674 @Override 675 public void run() { 676 HdmiControlService.this.oneTouchPlay(callback); 677 } 678 }); 679 } 680 681 @Override 682 public void queryDisplayStatus(final IHdmiControlCallback callback) { 683 enforceAccessPermission(); 684 runOnServiceThread(new Runnable() { 685 @Override 686 public void run() { 687 HdmiControlService.this.queryDisplayStatus(callback); 688 } 689 }); 690 } 691 692 @Override 693 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 694 enforceAccessPermission(); 695 runOnServiceThread(new Runnable() { 696 @Override 697 public void run() { 698 HdmiControlService.this.addHotplugEventListener(listener); 699 } 700 }); 701 } 702 703 @Override 704 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 705 enforceAccessPermission(); 706 runOnServiceThread(new Runnable() { 707 @Override 708 public void run() { 709 HdmiControlService.this.removeHotplugEventListener(listener); 710 } 711 }); 712 } 713 714 @Override 715 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 716 enforceAccessPermission(); 717 runOnServiceThread(new Runnable() { 718 @Override 719 public void run() { 720 HdmiControlService.this.addDeviceEventListener(listener); 721 } 722 }); 723 } 724 725 @Override 726 public List<HdmiPortInfo> getPortInfo() { 727 enforceAccessPermission(); 728 return mPortInfo; 729 } 730 731 @Override 732 public boolean canChangeSystemAudioMode() { 733 enforceAccessPermission(); 734 HdmiCecLocalDeviceTv tv = tv(); 735 if (tv == null) { 736 return false; 737 } 738 return tv.hasSystemAudioDevice(); 739 } 740 741 @Override 742 public boolean getSystemAudioMode() { 743 enforceAccessPermission(); 744 HdmiCecLocalDeviceTv tv = tv(); 745 if (tv == null) { 746 return false; 747 } 748 return tv.getSystemAudioMode(); 749 } 750 751 @Override 752 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 753 enforceAccessPermission(); 754 runOnServiceThread(new Runnable() { 755 @Override 756 public void run() { 757 HdmiCecLocalDeviceTv tv = tv(); 758 if (tv == null) { 759 Slog.w(TAG, "Local tv device not available"); 760 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 761 return; 762 } 763 tv.changeSystemAudioMode(enabled, callback); 764 } 765 }); 766 } 767 768 @Override 769 public void addSystemAudioModeChangeListener( 770 final IHdmiSystemAudioModeChangeListener listener) { 771 enforceAccessPermission(); 772 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 773 } 774 775 @Override 776 public void removeSystemAudioModeChangeListener( 777 final IHdmiSystemAudioModeChangeListener listener) { 778 enforceAccessPermission(); 779 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 780 } 781 782 @Override 783 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 784 enforceAccessPermission(); 785 HdmiControlService.this.setInputChangeListener(listener); 786 } 787 788 @Override 789 public List<HdmiCecDeviceInfo> getInputDevices() { 790 enforceAccessPermission(); 791 // No need to hold the lock for obtaining TV device as the local device instance 792 // is preserved while the HDMI control is enabled. 793 HdmiCecLocalDeviceTv tv = tv(); 794 if (tv == null) { 795 return Collections.emptyList(); 796 } 797 return tv.getSafeExternalInputs(); 798 } 799 800 @Override 801 public void setControlEnabled(final boolean enabled) { 802 enforceAccessPermission(); 803 synchronized (mLock) { 804 mHdmiControlEnabled = enabled; 805 } 806 // TODO: Stop the running actions when disabled, and start 807 // address allocation/device discovery when enabled. 808 if (!enabled) { 809 return; 810 } 811 runOnServiceThread(new Runnable() { 812 @Override 813 public void run() { 814 HdmiCecLocalDeviceTv tv = tv(); 815 if (tv == null) { 816 return; 817 } 818 int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED; 819 mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value); 820 if (mMhlController != null) { 821 mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value); 822 } 823 tv.routingAtEnableTime(); 824 } 825 }); 826 } 827 828 @Override 829 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 830 final int maxIndex) { 831 enforceAccessPermission(); 832 runOnServiceThread(new Runnable() { 833 @Override 834 public void run() { 835 HdmiCecLocalDeviceTv tv = tv(); 836 if (tv == null) { 837 Slog.w(TAG, "Local tv device not available"); 838 return; 839 } 840 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 841 } 842 }); 843 } 844 845 @Override 846 public void setSystemAudioMute(final boolean mute) { 847 enforceAccessPermission(); 848 runOnServiceThread(new Runnable() { 849 @Override 850 public void run() { 851 HdmiCecLocalDeviceTv tv = tv(); 852 if (tv == null) { 853 Slog.w(TAG, "Local tv device not available"); 854 return; 855 } 856 tv.changeMute(mute); 857 } 858 }); 859 } 860 861 @Override 862 public void setArcMode(final boolean enabled) { 863 enforceAccessPermission(); 864 runOnServiceThread(new Runnable() { 865 @Override 866 public void run() { 867 HdmiCecLocalDeviceTv tv = tv(); 868 if (tv == null) { 869 Slog.w(TAG, "Local tv device not available to change arc mode."); 870 return; 871 } 872 } 873 }); 874 } 875 876 @Override 877 public void setOption(final int key, final int value) { 878 if (!isTvDevice()) { 879 return; 880 } 881 switch (key) { 882 case HdmiCec.OPTION_CEC_AUTO_WAKEUP: 883 mCecController.setOption(key, value); 884 break; 885 case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF: 886 // No need to pass this option to HAL. 887 tv().setAutoDeviceOff(value == HdmiCec.ENABLED); 888 break; 889 case HdmiCec.OPTION_MHL_INPUT_SWITCHING: // Fall through 890 case HdmiCec.OPTION_MHL_POWER_CHARGE: 891 if (mMhlController != null) { 892 mMhlController.setOption(key, value); 893 } 894 break; 895 } 896 } 897 898 private boolean isTvDevice() { 899 return tv() != null; 900 } 901 } 902 903 @ServiceThreadOnly 904 private void oneTouchPlay(final IHdmiControlCallback callback) { 905 assertRunOnServiceThread(); 906 HdmiCecLocalDevicePlayback source = playback(); 907 if (source == null) { 908 Slog.w(TAG, "Local playback device not available"); 909 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 910 return; 911 } 912 source.oneTouchPlay(callback); 913 } 914 915 @ServiceThreadOnly 916 private void queryDisplayStatus(final IHdmiControlCallback callback) { 917 assertRunOnServiceThread(); 918 HdmiCecLocalDevicePlayback source = playback(); 919 if (source == null) { 920 Slog.w(TAG, "Local playback device not available"); 921 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 922 return; 923 } 924 source.queryDisplayStatus(callback); 925 } 926 927 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 928 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 929 try { 930 listener.asBinder().linkToDeath(record, 0); 931 } catch (RemoteException e) { 932 Slog.w(TAG, "Listener already died"); 933 return; 934 } 935 synchronized (mLock) { 936 mHotplugEventListenerRecords.add(record); 937 mHotplugEventListeners.add(listener); 938 } 939 } 940 941 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 942 synchronized (mLock) { 943 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 944 if (record.mListener.asBinder() == listener.asBinder()) { 945 listener.asBinder().unlinkToDeath(record, 0); 946 mHotplugEventListenerRecords.remove(record); 947 break; 948 } 949 } 950 mHotplugEventListeners.remove(listener); 951 } 952 } 953 954 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 955 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 956 try { 957 listener.asBinder().linkToDeath(record, 0); 958 } catch (RemoteException e) { 959 Slog.w(TAG, "Listener already died"); 960 return; 961 } 962 synchronized (mLock) { 963 mDeviceEventListeners.add(listener); 964 mDeviceEventListenerRecords.add(record); 965 } 966 } 967 968 void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) { 969 synchronized (mLock) { 970 for (IHdmiDeviceEventListener listener : mDeviceEventListeners) { 971 try { 972 listener.onStatusChanged(device, activated); 973 } catch (RemoteException e) { 974 Slog.e(TAG, "Failed to report device event:" + e); 975 } 976 } 977 } 978 } 979 980 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 981 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 982 listener); 983 try { 984 listener.asBinder().linkToDeath(record, 0); 985 } catch (RemoteException e) { 986 Slog.w(TAG, "Listener already died"); 987 return; 988 } 989 synchronized (mLock) { 990 mSystemAudioModeChangeListeners.add(listener); 991 mSystemAudioModeChangeListenerRecords.add(record); 992 } 993 } 994 995 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 996 synchronized (mLock) { 997 for (SystemAudioModeChangeListenerRecord record : 998 mSystemAudioModeChangeListenerRecords) { 999 if (record.mListener.asBinder() == listener) { 1000 listener.asBinder().unlinkToDeath(record, 0); 1001 mSystemAudioModeChangeListenerRecords.remove(record); 1002 break; 1003 } 1004 } 1005 mSystemAudioModeChangeListeners.remove(listener); 1006 } 1007 } 1008 1009 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1010 @Override 1011 public void binderDied() { 1012 synchronized (mLock) { 1013 mInputChangeListener = null; 1014 } 1015 } 1016 } 1017 1018 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1019 synchronized (mLock) { 1020 mInputChangeListenerRecord = new InputChangeListenerRecord(); 1021 try { 1022 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1023 } catch (RemoteException e) { 1024 Slog.w(TAG, "Listener already died"); 1025 return; 1026 } 1027 mInputChangeListener = listener; 1028 } 1029 } 1030 1031 void invokeInputChangeListener(int activeAddress) { 1032 synchronized (mLock) { 1033 if (mInputChangeListener != null) { 1034 HdmiCecDeviceInfo activeSource = getDeviceInfo(activeAddress); 1035 try { 1036 mInputChangeListener.onChanged(activeSource); 1037 } catch (RemoteException e) { 1038 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1039 } 1040 } 1041 } 1042 } 1043 1044 private void invokeCallback(IHdmiControlCallback callback, int result) { 1045 try { 1046 callback.onComplete(result); 1047 } catch (RemoteException e) { 1048 Slog.e(TAG, "Invoking callback failed:" + e); 1049 } 1050 } 1051 1052 private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener, 1053 boolean enabled) { 1054 try { 1055 listener.onStatusChanged(enabled); 1056 } catch (RemoteException e) { 1057 Slog.e(TAG, "Invoking callback failed:" + e); 1058 } 1059 } 1060 1061 private void announceHotplugEvent(int portId, boolean connected) { 1062 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1063 synchronized (mLock) { 1064 for (IHdmiHotplugEventListener listener : mHotplugEventListeners) { 1065 invokeHotplugEventListenerLocked(listener, event); 1066 } 1067 } 1068 } 1069 1070 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1071 HdmiHotplugEvent event) { 1072 try { 1073 listener.onReceived(event); 1074 } catch (RemoteException e) { 1075 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1076 } 1077 } 1078 1079 private static boolean hasSameTopPort(int path1, int path2) { 1080 return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK) 1081 == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK); 1082 } 1083 1084 private HdmiCecLocalDeviceTv tv() { 1085 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV); 1086 } 1087 1088 private HdmiCecLocalDevicePlayback playback() { 1089 return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK); 1090 } 1091 1092 AudioManager getAudioManager() { 1093 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1094 } 1095 1096 boolean isControlEnabled() { 1097 synchronized (mLock) { 1098 return mHdmiControlEnabled; 1099 } 1100 } 1101 1102 int getPowerStatus() { 1103 return mPowerStatus; 1104 } 1105 1106 boolean isPowerOnOrTransient() { 1107 return mPowerStatus == HdmiCec.POWER_STATUS_ON 1108 || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 1109 } 1110 1111 boolean isPowerStandbyOrTransient() { 1112 return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY 1113 || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY; 1114 } 1115 1116 boolean isPowerStandby() { 1117 return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY; 1118 } 1119 1120 @ServiceThreadOnly 1121 void wakeUp() { 1122 assertRunOnServiceThread(); 1123 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1124 pm.wakeUp(SystemClock.uptimeMillis()); 1125 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1126 // the intent, the sequence will continue at onWakeUp(). 1127 } 1128 1129 @ServiceThreadOnly 1130 void standby() { 1131 assertRunOnServiceThread(); 1132 mStandbyMessageReceived = true; 1133 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1134 pm.goToSleep(SystemClock.uptimeMillis()); 1135 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1136 // the intent, the sequence will continue at onStandby(). 1137 } 1138 1139 @ServiceThreadOnly 1140 private void onWakeUp() { 1141 assertRunOnServiceThread(); 1142 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON; 1143 if (mCecController != null) { 1144 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.ENABLED); 1145 initializeLocalDevices(mLocalDevices); 1146 } else { 1147 Slog.i(TAG, "Device does not support HDMI-CEC."); 1148 } 1149 // TODO: Initialize MHL local devices. 1150 } 1151 1152 @ServiceThreadOnly 1153 private void onStandby() { 1154 assertRunOnServiceThread(); 1155 mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY; 1156 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1157 device.onTransitionToStandby(mStandbyMessageReceived); 1158 } 1159 } 1160 1161 /** 1162 * Called when there are the outstanding actions in the local devices. 1163 * This callback is used to wait for when the action queue is empty 1164 * during the power state transition to standby. 1165 */ 1166 @ServiceThreadOnly 1167 void onPendingActionsCleared() { 1168 assertRunOnServiceThread(); 1169 if (mPowerStatus != HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1170 return; 1171 } 1172 mPowerStatus = HdmiCec.POWER_STATUS_STANDBY; 1173 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1174 device.onStandBy(mStandbyMessageReceived); 1175 } 1176 mStandbyMessageReceived = false; 1177 mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED); 1178 } 1179} 1180