HdmiControlService.java revision c7eba0f1db8928ca779933a564a06989e22a8532
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 final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver(); 187 188 @ServiceThreadOnly 189 private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 190 191 @ServiceThreadOnly 192 private boolean mStandbyMessageReceived = false; 193 194 public HdmiControlService(Context context) { 195 super(context); 196 mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray( 197 com.android.internal.R.array.config_hdmiCecLogicalDeviceType)); 198 } 199 200 @Override 201 public void onStart() { 202 mIoThread.start(); 203 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 204 mCecController = HdmiCecController.create(this); 205 206 if (mCecController != null) { 207 // TODO: Remove this as soon as OEM's HAL implementation is corrected. 208 mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, 209 HdmiTvClient.ENABLED); 210 211 mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, 212 HdmiTvClient.ENABLED); 213 initializeLocalDevices(mLocalDevices); 214 } else { 215 Slog.i(TAG, "Device does not support HDMI-CEC."); 216 } 217 218 mMhlController = HdmiMhlController.create(this); 219 if (mMhlController == null) { 220 Slog.i(TAG, "Device does not support MHL-control."); 221 } 222 mPortInfo = initPortInfo(); 223 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 224 225 // Register broadcast receiver for power state change. 226 if (mCecController != null || mMhlController != null) { 227 IntentFilter filter = new IntentFilter(); 228 filter.addAction(Intent.ACTION_SCREEN_OFF); 229 filter.addAction(Intent.ACTION_SCREEN_ON); 230 getContext().registerReceiver(mPowerStateReceiver, filter); 231 } 232 233 // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and 234 // start to monitor the preference value and invoke SystemAudioActionFromTv if needed. 235 mHdmiControlEnabled = true; 236 // TODO: Get control flag from persistent storage 237 mProhibitMode = false; 238 } 239 240 @ServiceThreadOnly 241 private void initializeLocalDevices(final List<Integer> deviceTypes) { 242 assertRunOnServiceThread(); 243 // A container for [Logical Address, Local device info]. 244 final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>(); 245 final SparseIntArray finished = new SparseIntArray(); 246 mCecController.clearLogicalAddress(); 247 for (int type : deviceTypes) { 248 final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type); 249 localDevice.init(); 250 mCecController.allocateLogicalAddress(type, 251 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 252 @Override 253 public void onAllocated(int deviceType, int logicalAddress) { 254 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 255 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 256 } else { 257 HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType); 258 localDevice.setDeviceInfo(deviceInfo); 259 mCecController.addLocalDevice(deviceType, localDevice); 260 mCecController.addLogicalAddress(logicalAddress); 261 devices.append(logicalAddress, localDevice); 262 } 263 finished.append(deviceType, logicalAddress); 264 265 // Address allocation completed for all devices. Notify each device. 266 if (deviceTypes.size() == finished.size()) { 267 if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) { 268 mPowerStatus = HdmiControlManager.POWER_STATUS_ON; 269 } 270 notifyAddressAllocated(devices); 271 } 272 } 273 }); 274 } 275 } 276 277 @ServiceThreadOnly 278 private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) { 279 assertRunOnServiceThread(); 280 for (int i = 0; i < devices.size(); ++i) { 281 int address = devices.keyAt(i); 282 HdmiCecLocalDevice device = devices.valueAt(i); 283 device.handleAddressAllocated(address); 284 } 285 } 286 287 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 288 // keep them in one place. 289 @ServiceThreadOnly 290 private List<HdmiPortInfo> initPortInfo() { 291 assertRunOnServiceThread(); 292 HdmiPortInfo[] cecPortInfo = null; 293 294 // CEC HAL provides majority of the info while MHL does only MHL support flag for 295 // each port. Return empty array if CEC HAL didn't provide the info. 296 if (mCecController != null) { 297 cecPortInfo = mCecController.getPortInfos(); 298 } 299 if (cecPortInfo == null) { 300 return Collections.emptyList(); 301 } 302 303 HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0]; 304 if (mMhlController != null) { 305 // TODO: Implement plumbing logic to get MHL port information. 306 // mhlPortInfo = mMhlController.getPortInfos(); 307 } 308 309 // Use the id (port number) to find the matched info between CEC and MHL to combine them 310 // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found. 311 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 312 for (int i = 0; i < cecPortInfo.length; ++i) { 313 HdmiPortInfo cec = cecPortInfo[i]; 314 int id = cec.getId(); 315 boolean mhlInfoFound = false; 316 for (HdmiPortInfo mhl : mhlPortInfo) { 317 if (id == mhl.getId()) { 318 result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(), 319 cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported())); 320 mhlInfoFound = true; 321 break; 322 } 323 } 324 if (!mhlInfoFound) { 325 result.add(cec); 326 } 327 } 328 329 return Collections.unmodifiableList(result); 330 } 331 332 /** 333 * Returns HDMI port information for the given port id. 334 * 335 * @param portId HDMI port id 336 * @return {@link HdmiPortInfo} for the given port 337 */ 338 HdmiPortInfo getPortInfo(int portId) { 339 // mPortInfo is an unmodifiable list and the only reference to its inner list. 340 // No lock is necessary. 341 for (HdmiPortInfo info : mPortInfo) { 342 if (portId == info.getId()) { 343 return info; 344 } 345 } 346 return null; 347 } 348 349 /** 350 * Returns the routing path (physical address) of the HDMI port for the given 351 * port id. 352 */ 353 int portIdToPath(int portId) { 354 HdmiPortInfo portInfo = getPortInfo(portId); 355 if (portInfo == null) { 356 Slog.e(TAG, "Cannot find the port info: " + portId); 357 return Constants.INVALID_PHYSICAL_ADDRESS; 358 } 359 return portInfo.getAddress(); 360 } 361 362 /** 363 * Returns the id of HDMI port located at the top of the hierarchy of 364 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 365 * the port id to be returned is the ID associated with the port address 366 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 367 */ 368 int pathToPortId(int path) { 369 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; 370 for (HdmiPortInfo info : mPortInfo) { 371 if (portAddress == info.getAddress()) { 372 return info.getId(); 373 } 374 } 375 return Constants.INVALID_PORT_ID; 376 } 377 378 /** 379 * Returns {@link Looper} for IO operation. 380 * 381 * <p>Declared as package-private. 382 */ 383 Looper getIoLooper() { 384 return mIoThread.getLooper(); 385 } 386 387 /** 388 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 389 * for tasks that are running on main service thread. 390 * 391 * <p>Declared as package-private. 392 */ 393 Looper getServiceLooper() { 394 return mHandler.getLooper(); 395 } 396 397 /** 398 * Returns physical address of the device. 399 */ 400 int getPhysicalAddress() { 401 return mCecController.getPhysicalAddress(); 402 } 403 404 /** 405 * Returns vendor id of CEC service. 406 */ 407 int getVendorId() { 408 return mCecController.getVendorId(); 409 } 410 411 @ServiceThreadOnly 412 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 413 assertRunOnServiceThread(); 414 HdmiCecLocalDeviceTv tv = tv(); 415 if (tv == null) { 416 return null; 417 } 418 return tv.getDeviceInfo(logicalAddress); 419 } 420 421 /** 422 * Returns version of CEC. 423 */ 424 int getCecVersion() { 425 return mCecController.getVersion(); 426 } 427 428 /** 429 * Whether a device of the specified physical address is connected to ARC enabled port. 430 */ 431 boolean isConnectedToArcPort(int physicalAddress) { 432 for (HdmiPortInfo portInfo : mPortInfo) { 433 if (hasSameTopPort(portInfo.getAddress(), physicalAddress) 434 && portInfo.isArcSupported()) { 435 return true; 436 } 437 } 438 return false; 439 } 440 441 void runOnServiceThread(Runnable runnable) { 442 mHandler.post(runnable); 443 } 444 445 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 446 mHandler.postAtFrontOfQueue(runnable); 447 } 448 449 private void assertRunOnServiceThread() { 450 if (Looper.myLooper() != mHandler.getLooper()) { 451 throw new IllegalStateException("Should run on service thread."); 452 } 453 } 454 455 /** 456 * Transmit a CEC command to CEC bus. 457 * 458 * @param command CEC command to send out 459 * @param callback interface used to the result of send command 460 */ 461 @ServiceThreadOnly 462 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 463 assertRunOnServiceThread(); 464 mCecController.sendCommand(command, callback); 465 } 466 467 @ServiceThreadOnly 468 void sendCecCommand(HdmiCecMessage command) { 469 assertRunOnServiceThread(); 470 mCecController.sendCommand(command, null); 471 } 472 473 @ServiceThreadOnly 474 boolean handleCecCommand(HdmiCecMessage message) { 475 assertRunOnServiceThread(); 476 return dispatchMessageToLocalDevice(message); 477 } 478 479 void setAudioReturnChannel(boolean enabled) { 480 mCecController.setAudioReturnChannel(enabled); 481 } 482 483 @ServiceThreadOnly 484 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 485 assertRunOnServiceThread(); 486 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 487 if (device.dispatchMessage(message) 488 && message.getDestination() != Constants.ADDR_BROADCAST) { 489 return true; 490 } 491 } 492 493 if (message.getDestination() != Constants.ADDR_BROADCAST) { 494 Slog.w(TAG, "Unhandled cec command:" + message); 495 } 496 return false; 497 } 498 499 /** 500 * Called when a new hotplug event is issued. 501 * 502 * @param portNo hdmi port number where hot plug event issued. 503 * @param connected whether to be plugged in or not 504 */ 505 @ServiceThreadOnly 506 void onHotplug(int portNo, boolean connected) { 507 assertRunOnServiceThread(); 508 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 509 device.onHotplug(portNo, connected); 510 } 511 announceHotplugEvent(portNo, connected); 512 } 513 514 /** 515 * Poll all remote devices. It sends <Polling Message> to all remote 516 * devices. 517 * 518 * @param callback an interface used to get a list of all remote devices' address 519 * @param sourceAddress a logical address of source device where sends polling message 520 * @param pickStrategy strategy how to pick polling candidates 521 * @param retryCount the number of retry used to send polling message to remote devices 522 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 523 */ 524 @ServiceThreadOnly 525 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 526 int retryCount) { 527 assertRunOnServiceThread(); 528 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 529 retryCount); 530 } 531 532 private int checkPollStrategy(int pickStrategy) { 533 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 534 if (strategy == 0) { 535 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 536 } 537 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 538 if (iterationStrategy == 0) { 539 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 540 } 541 return strategy | iterationStrategy; 542 } 543 544 List<HdmiCecLocalDevice> getAllLocalDevices() { 545 assertRunOnServiceThread(); 546 return mCecController.getLocalDeviceList(); 547 } 548 549 Object getServiceLock() { 550 return mLock; 551 } 552 553 void setAudioStatus(boolean mute, int volume) { 554 // TODO: Hook up with AudioManager. 555 } 556 557 void announceSystemAudioModeChange(boolean enabled) { 558 for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) { 559 invokeSystemAudioModeChange(listener, enabled); 560 } 561 } 562 563 private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) { 564 // TODO: find better name instead of model name. 565 String displayName = Build.MODEL; 566 return new HdmiCecDeviceInfo(logicalAddress, 567 getPhysicalAddress(), deviceType, getVendorId(), displayName); 568 } 569 570 // Record class that monitors the event of the caller of being killed. Used to clean up 571 // the listener list and record list accordingly. 572 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 573 private final IHdmiHotplugEventListener mListener; 574 575 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 576 mListener = listener; 577 } 578 579 @Override 580 public void binderDied() { 581 synchronized (mLock) { 582 mHotplugEventListenerRecords.remove(this); 583 mHotplugEventListeners.remove(mListener); 584 } 585 } 586 } 587 588 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 589 private final IHdmiDeviceEventListener mListener; 590 591 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 592 mListener = listener; 593 } 594 595 @Override 596 public void binderDied() { 597 synchronized (mLock) { 598 mDeviceEventListenerRecords.remove(this); 599 mDeviceEventListeners.remove(mListener); 600 } 601 } 602 } 603 604 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 605 private final IHdmiSystemAudioModeChangeListener mListener; 606 607 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 608 mListener = listener; 609 } 610 611 @Override 612 public void binderDied() { 613 synchronized (mLock) { 614 mSystemAudioModeChangeListenerRecords.remove(this); 615 mSystemAudioModeChangeListeners.remove(mListener); 616 } 617 } 618 } 619 620 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 621 private final IHdmiVendorCommandListener mListener; 622 private final int mDeviceType; 623 624 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 625 mListener = listener; 626 mDeviceType = deviceType; 627 } 628 629 @Override 630 public void binderDied() { 631 synchronized (mLock) { 632 mVendorCommandListenerRecords.remove(this); 633 } 634 } 635 } 636 637 private void enforceAccessPermission() { 638 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 639 } 640 641 private final class BinderService extends IHdmiControlService.Stub { 642 @Override 643 public int[] getSupportedTypes() { 644 enforceAccessPermission(); 645 // mLocalDevices is an unmodifiable list - no lock necesary. 646 int[] localDevices = new int[mLocalDevices.size()]; 647 for (int i = 0; i < localDevices.length; ++i) { 648 localDevices[i] = mLocalDevices.get(i); 649 } 650 return localDevices; 651 } 652 653 @Override 654 public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) { 655 enforceAccessPermission(); 656 runOnServiceThread(new Runnable() { 657 @Override 658 public void run() { 659 HdmiCecLocalDeviceTv tv = tv(); 660 if (tv == null) { 661 Slog.w(TAG, "Local tv device not available"); 662 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 663 return; 664 } 665 tv.deviceSelect(logicalAddress, callback); 666 } 667 }); 668 } 669 670 @Override 671 public void portSelect(final int portId, final IHdmiControlCallback callback) { 672 enforceAccessPermission(); 673 runOnServiceThread(new Runnable() { 674 @Override 675 public void run() { 676 HdmiCecLocalDeviceTv tv = tv(); 677 if (tv == null) { 678 Slog.w(TAG, "Local tv device not available"); 679 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 680 return; 681 } 682 tv.doManualPortSwitching(portId, callback); 683 } 684 }); 685 } 686 687 @Override 688 public void sendKeyEvent(final int keyCode, final boolean isPressed) { 689 enforceAccessPermission(); 690 runOnServiceThread(new Runnable() { 691 @Override 692 public void run() { 693 // TODO: sendKeyEvent is for TV device only for now. Allow other 694 // local devices of different types to use this as well. 695 HdmiCecLocalDeviceTv tv = tv(); 696 if (tv == null) { 697 Slog.w(TAG, "Local tv device not available"); 698 return; 699 } 700 tv.sendKeyEvent(keyCode, isPressed); 701 } 702 }); 703 } 704 705 @Override 706 public void oneTouchPlay(final IHdmiControlCallback callback) { 707 enforceAccessPermission(); 708 runOnServiceThread(new Runnable() { 709 @Override 710 public void run() { 711 HdmiControlService.this.oneTouchPlay(callback); 712 } 713 }); 714 } 715 716 @Override 717 public void queryDisplayStatus(final IHdmiControlCallback callback) { 718 enforceAccessPermission(); 719 runOnServiceThread(new Runnable() { 720 @Override 721 public void run() { 722 HdmiControlService.this.queryDisplayStatus(callback); 723 } 724 }); 725 } 726 727 @Override 728 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 729 enforceAccessPermission(); 730 runOnServiceThread(new Runnable() { 731 @Override 732 public void run() { 733 HdmiControlService.this.addHotplugEventListener(listener); 734 } 735 }); 736 } 737 738 @Override 739 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 740 enforceAccessPermission(); 741 runOnServiceThread(new Runnable() { 742 @Override 743 public void run() { 744 HdmiControlService.this.removeHotplugEventListener(listener); 745 } 746 }); 747 } 748 749 @Override 750 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 751 enforceAccessPermission(); 752 runOnServiceThread(new Runnable() { 753 @Override 754 public void run() { 755 HdmiControlService.this.addDeviceEventListener(listener); 756 } 757 }); 758 } 759 760 @Override 761 public List<HdmiPortInfo> getPortInfo() { 762 enforceAccessPermission(); 763 return mPortInfo; 764 } 765 766 @Override 767 public boolean canChangeSystemAudioMode() { 768 enforceAccessPermission(); 769 HdmiCecLocalDeviceTv tv = tv(); 770 if (tv == null) { 771 return false; 772 } 773 return tv.hasSystemAudioDevice(); 774 } 775 776 @Override 777 public boolean getSystemAudioMode() { 778 enforceAccessPermission(); 779 HdmiCecLocalDeviceTv tv = tv(); 780 if (tv == null) { 781 return false; 782 } 783 return tv.getSystemAudioMode(); 784 } 785 786 @Override 787 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 788 enforceAccessPermission(); 789 runOnServiceThread(new Runnable() { 790 @Override 791 public void run() { 792 HdmiCecLocalDeviceTv tv = tv(); 793 if (tv == null) { 794 Slog.w(TAG, "Local tv device not available"); 795 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 796 return; 797 } 798 tv.changeSystemAudioMode(enabled, callback); 799 } 800 }); 801 } 802 803 @Override 804 public void addSystemAudioModeChangeListener( 805 final IHdmiSystemAudioModeChangeListener listener) { 806 enforceAccessPermission(); 807 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 808 } 809 810 @Override 811 public void removeSystemAudioModeChangeListener( 812 final IHdmiSystemAudioModeChangeListener listener) { 813 enforceAccessPermission(); 814 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 815 } 816 817 @Override 818 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 819 enforceAccessPermission(); 820 HdmiControlService.this.setInputChangeListener(listener); 821 } 822 823 @Override 824 public List<HdmiCecDeviceInfo> getInputDevices() { 825 enforceAccessPermission(); 826 // No need to hold the lock for obtaining TV device as the local device instance 827 // is preserved while the HDMI control is enabled. 828 HdmiCecLocalDeviceTv tv = tv(); 829 if (tv == null) { 830 return Collections.emptyList(); 831 } 832 return tv.getSafeExternalInputs(); 833 } 834 835 @Override 836 public void setControlEnabled(final boolean enabled) { 837 enforceAccessPermission(); 838 synchronized (mLock) { 839 mHdmiControlEnabled = enabled; 840 } 841 // TODO: Stop the running actions when disabled, and start 842 // address allocation/device discovery when enabled. 843 if (!enabled) { 844 return; 845 } 846 runOnServiceThread(new Runnable() { 847 @Override 848 public void run() { 849 HdmiCecLocalDeviceTv tv = tv(); 850 if (tv == null) { 851 return; 852 } 853 int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED; 854 mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value); 855 if (mMhlController != null) { 856 mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value); 857 } 858 tv.launchRoutingControl(false); 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 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1239 device.onTransitionToStandby(mStandbyMessageReceived); 1240 } 1241 } 1242 1243 /** 1244 * Called when there are the outstanding actions in the local devices. 1245 * This callback is used to wait for when the action queue is empty 1246 * during the power state transition to standby. 1247 */ 1248 @ServiceThreadOnly 1249 void onPendingActionsCleared() { 1250 assertRunOnServiceThread(); 1251 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1252 return; 1253 } 1254 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1255 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1256 device.onStandBy(mStandbyMessageReceived); 1257 } 1258 mStandbyMessageReceived = false; 1259 mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED); 1260 } 1261 1262 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1263 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1264 try { 1265 listener.asBinder().linkToDeath(record, 0); 1266 } catch (RemoteException e) { 1267 Slog.w(TAG, "Listener already died"); 1268 return; 1269 } 1270 synchronized (mLock) { 1271 mVendorCommandListenerRecords.add(record); 1272 } 1273 } 1274 1275 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1276 boolean hasVendorId) { 1277 synchronized (mLock) { 1278 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1279 if (record.mDeviceType != deviceType) { 1280 continue; 1281 } 1282 try { 1283 record.mListener.onReceived(srcAddress, params, hasVendorId); 1284 } catch (RemoteException e) { 1285 Slog.e(TAG, "Failed to notify vendor command reception", e); 1286 } 1287 } 1288 } 1289 } 1290 1291 boolean isProhibitMode() { 1292 synchronized (mLock) { 1293 return mProhibitMode; 1294 } 1295 } 1296 1297 void setProhibitMode(boolean enabled) { 1298 synchronized (mLock) { 1299 mProhibitMode = enabled; 1300 } 1301 } 1302} 1303