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