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