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