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