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