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