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