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