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