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