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