HdmiControlService.java revision 7c5d31ea93d6f6770c34f7a2a364522d8cc4b5d8
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 HdmiDeviceInfo deviceInfo = device.getInfo(); 829 if (deviceInfo != null) { 830 invokeDeviceEventListeners(deviceInfo, DEVICE_EVENT_REMOVE_DEVICE); 831 updateSafeMhlInput(); 832 } 833 } else { 834 Slog.w(TAG, "No device to remove:[portId=" + portId); 835 } 836 } 837 announceHotplugEvent(portId, connected); 838 } 839 840 @ServiceThreadOnly 841 void handleMhlCbusModeChanged(int portId, int cbusmode) { 842 assertRunOnServiceThread(); 843 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 844 if (device != null) { 845 device.setCbusMode(cbusmode); 846 } else { 847 Slog.w(TAG, "No mhl device exists for cbus mode change[portId:" + portId + 848 ", cbusmode:" + cbusmode + "]"); 849 } 850 } 851 852 @ServiceThreadOnly 853 void handleMhlVbusOvercurrent(int portId, boolean on) { 854 assertRunOnServiceThread(); 855 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 856 if (device != null) { 857 device.onVbusOvercurrentDetected(on); 858 } else { 859 Slog.w(TAG, "No mhl device exists for vbus overcurrent event[portId:" + portId + "]"); 860 } 861 } 862 863 @ServiceThreadOnly 864 void handleMhlCapabilityRegisterChanged(int portId, int adopterId, int deviceId) { 865 assertRunOnServiceThread(); 866 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 867 868 // Hotplug event should already have been called before capability register change event. 869 if (device != null) { 870 device.setCapabilityRegister(adopterId, deviceId); 871 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_ADD_DEVICE); 872 updateSafeMhlInput(); 873 } else { 874 Slog.w(TAG, "No mhl device exists for capability register change event[portId:" 875 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 876 } 877 } 878 879 @ServiceThreadOnly 880 private void updateSafeMhlInput() { 881 assertRunOnServiceThread(); 882 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 883 SparseArray<HdmiMhlLocalDevice> devices = mMhlController.getAllLocalDevices(); 884 for (int i = 0; i < devices.size(); ++i) { 885 HdmiMhlLocalDevice device = devices.valueAt(i); 886 HdmiDeviceInfo info = device.getInfo(); 887 if (info != null) { 888 if (inputs.isEmpty()) { 889 inputs = new ArrayList<>(); 890 } 891 inputs.add(device.getInfo()); 892 } 893 } 894 synchronized (mLock) { 895 mMhlDevices = inputs; 896 } 897 } 898 899 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 900 return mMhlDevices; 901 } 902 903 private class HdmiMhlScratchpadCommandListenerRecord implements IBinder.DeathRecipient { 904 private final IHdmiMhlScratchpadCommandListener mListener; 905 906 public HdmiMhlScratchpadCommandListenerRecord(IHdmiMhlScratchpadCommandListener listener) { 907 mListener = listener; 908 } 909 910 @Override 911 public void binderDied() { 912 mScratchpadCommandListenerRecords.remove(this); 913 } 914 } 915 916 // Record class that monitors the event of the caller of being killed. Used to clean up 917 // the listener list and record list accordingly. 918 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 919 private final IHdmiHotplugEventListener mListener; 920 921 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 922 mListener = listener; 923 } 924 925 @Override 926 public void binderDied() { 927 synchronized (mLock) { 928 mHotplugEventListenerRecords.remove(this); 929 } 930 } 931 } 932 933 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 934 private final IHdmiDeviceEventListener mListener; 935 936 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 937 mListener = listener; 938 } 939 940 @Override 941 public void binderDied() { 942 synchronized (mLock) { 943 mDeviceEventListenerRecords.remove(this); 944 } 945 } 946 } 947 948 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 949 private final IHdmiSystemAudioModeChangeListener mListener; 950 951 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 952 mListener = listener; 953 } 954 955 @Override 956 public void binderDied() { 957 synchronized (mLock) { 958 mSystemAudioModeChangeListenerRecords.remove(this); 959 } 960 } 961 } 962 963 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 964 private final IHdmiVendorCommandListener mListener; 965 private final int mDeviceType; 966 967 public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) { 968 mListener = listener; 969 mDeviceType = deviceType; 970 } 971 972 @Override 973 public void binderDied() { 974 synchronized (mLock) { 975 mVendorCommandListenerRecords.remove(this); 976 } 977 } 978 } 979 980 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 981 private final IHdmiRecordListener mListener; 982 983 public HdmiRecordListenerRecord(IHdmiRecordListener listener) { 984 mListener = listener; 985 } 986 987 @Override 988 public void binderDied() { 989 synchronized (mLock) { 990 mRecordListenerRecord = null; 991 } 992 } 993 } 994 995 private void enforceAccessPermission() { 996 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 997 } 998 999 private final class BinderService extends IHdmiControlService.Stub { 1000 @Override 1001 public int[] getSupportedTypes() { 1002 enforceAccessPermission(); 1003 // mLocalDevices is an unmodifiable list - no lock necesary. 1004 int[] localDevices = new int[mLocalDevices.size()]; 1005 for (int i = 0; i < localDevices.length; ++i) { 1006 localDevices[i] = mLocalDevices.get(i); 1007 } 1008 return localDevices; 1009 } 1010 1011 @Override 1012 public HdmiDeviceInfo getActiveSource() { 1013 HdmiCecLocalDeviceTv tv = tv(); 1014 if (tv == null) { 1015 Slog.w(TAG, "Local tv device not available"); 1016 return null; 1017 } 1018 ActiveSource activeSource = tv.getActiveSource(); 1019 if (activeSource.isValid()) { 1020 return new HdmiDeviceInfo(activeSource.logicalAddress, 1021 activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID, 1022 HdmiDeviceInfo.DEVICE_INACTIVE, 0, ""); 1023 } 1024 int activePath = tv.getActivePath(); 1025 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 1026 return new HdmiDeviceInfo(activePath, tv.getActivePortId()); 1027 } 1028 return null; 1029 } 1030 1031 @Override 1032 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 1033 enforceAccessPermission(); 1034 runOnServiceThread(new Runnable() { 1035 @Override 1036 public void run() { 1037 if (callback == null) { 1038 Slog.e(TAG, "Callback cannot be null"); 1039 return; 1040 } 1041 HdmiCecLocalDeviceTv tv = tv(); 1042 if (tv == null) { 1043 Slog.w(TAG, "Local tv device not available"); 1044 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1045 return; 1046 } 1047 HdmiMhlLocalDevice device = mMhlController.getLocalDeviceById(deviceId); 1048 if (device != null) { 1049 if (device.getPortId() == tv.getActivePortId()) { 1050 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 1051 return; 1052 } 1053 // Upon selecting MHL device, we send RAP[Content On] to wake up 1054 // the connected mobile device, start routing control to switch ports. 1055 // callback is handled by MHL action. 1056 device.turnOn(callback); 1057 tv.doManualPortSwitching(device.getPortId(), null); 1058 return; 1059 } 1060 tv.deviceSelect(deviceId, callback); 1061 } 1062 }); 1063 } 1064 1065 @Override 1066 public void portSelect(final int portId, final IHdmiControlCallback callback) { 1067 enforceAccessPermission(); 1068 runOnServiceThread(new Runnable() { 1069 @Override 1070 public void run() { 1071 if (callback == null) { 1072 Slog.e(TAG, "Callback cannot be null"); 1073 return; 1074 } 1075 HdmiCecLocalDeviceTv tv = tv(); 1076 if (tv == null) { 1077 Slog.w(TAG, "Local tv device not available"); 1078 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1079 return; 1080 } 1081 tv.doManualPortSwitching(portId, callback); 1082 } 1083 }); 1084 } 1085 1086 @Override 1087 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 1088 enforceAccessPermission(); 1089 runOnServiceThread(new Runnable() { 1090 @Override 1091 public void run() { 1092 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(mActivePortId); 1093 if (device != null) { 1094 device.sendKeyEvent(keyCode, isPressed); 1095 return; 1096 } 1097 if (mCecController != null) { 1098 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType); 1099 if (localDevice == null) { 1100 Slog.w(TAG, "Local device not available"); 1101 return; 1102 } 1103 localDevice.sendKeyEvent(keyCode, isPressed); 1104 } 1105 } 1106 }); 1107 } 1108 1109 @Override 1110 public void oneTouchPlay(final IHdmiControlCallback callback) { 1111 enforceAccessPermission(); 1112 runOnServiceThread(new Runnable() { 1113 @Override 1114 public void run() { 1115 HdmiControlService.this.oneTouchPlay(callback); 1116 } 1117 }); 1118 } 1119 1120 @Override 1121 public void queryDisplayStatus(final IHdmiControlCallback callback) { 1122 enforceAccessPermission(); 1123 runOnServiceThread(new Runnable() { 1124 @Override 1125 public void run() { 1126 HdmiControlService.this.queryDisplayStatus(callback); 1127 } 1128 }); 1129 } 1130 1131 @Override 1132 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 1133 enforceAccessPermission(); 1134 HdmiControlService.this.addHotplugEventListener(listener); 1135 } 1136 1137 @Override 1138 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 1139 enforceAccessPermission(); 1140 HdmiControlService.this.removeHotplugEventListener(listener); 1141 } 1142 1143 @Override 1144 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 1145 enforceAccessPermission(); 1146 HdmiControlService.this.addDeviceEventListener(listener); 1147 } 1148 1149 @Override 1150 public List<HdmiPortInfo> getPortInfo() { 1151 enforceAccessPermission(); 1152 return HdmiControlService.this.getPortInfo(); 1153 } 1154 1155 @Override 1156 public boolean canChangeSystemAudioMode() { 1157 enforceAccessPermission(); 1158 HdmiCecLocalDeviceTv tv = tv(); 1159 if (tv == null) { 1160 return false; 1161 } 1162 return tv.hasSystemAudioDevice(); 1163 } 1164 1165 @Override 1166 public boolean getSystemAudioMode() { 1167 enforceAccessPermission(); 1168 HdmiCecLocalDeviceTv tv = tv(); 1169 if (tv == null) { 1170 return false; 1171 } 1172 return tv.isSystemAudioActivated(); 1173 } 1174 1175 @Override 1176 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 1177 enforceAccessPermission(); 1178 runOnServiceThread(new Runnable() { 1179 @Override 1180 public void run() { 1181 HdmiCecLocalDeviceTv tv = tv(); 1182 if (tv == null) { 1183 Slog.w(TAG, "Local tv device not available"); 1184 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1185 return; 1186 } 1187 tv.changeSystemAudioMode(enabled, callback); 1188 } 1189 }); 1190 } 1191 1192 @Override 1193 public void addSystemAudioModeChangeListener( 1194 final IHdmiSystemAudioModeChangeListener listener) { 1195 enforceAccessPermission(); 1196 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 1197 } 1198 1199 @Override 1200 public void removeSystemAudioModeChangeListener( 1201 final IHdmiSystemAudioModeChangeListener listener) { 1202 enforceAccessPermission(); 1203 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 1204 } 1205 1206 @Override 1207 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 1208 enforceAccessPermission(); 1209 HdmiControlService.this.setInputChangeListener(listener); 1210 } 1211 1212 @Override 1213 public List<HdmiDeviceInfo> getInputDevices() { 1214 enforceAccessPermission(); 1215 // No need to hold the lock for obtaining TV device as the local device instance 1216 // is preserved while the HDMI control is enabled. 1217 HdmiCecLocalDeviceTv tv = tv(); 1218 synchronized (mLock) { 1219 List<HdmiDeviceInfo> cecDevices = (tv == null) 1220 ? Collections.<HdmiDeviceInfo>emptyList() 1221 : tv.getSafeExternalInputsLocked(); 1222 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked()); 1223 } 1224 } 1225 1226 @Override 1227 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1228 final int maxIndex) { 1229 enforceAccessPermission(); 1230 runOnServiceThread(new Runnable() { 1231 @Override 1232 public void run() { 1233 HdmiCecLocalDeviceTv tv = tv(); 1234 if (tv == null) { 1235 Slog.w(TAG, "Local tv device not available"); 1236 return; 1237 } 1238 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1239 } 1240 }); 1241 } 1242 1243 @Override 1244 public void setSystemAudioMute(final boolean mute) { 1245 enforceAccessPermission(); 1246 runOnServiceThread(new Runnable() { 1247 @Override 1248 public void run() { 1249 HdmiCecLocalDeviceTv tv = tv(); 1250 if (tv == null) { 1251 Slog.w(TAG, "Local tv device not available"); 1252 return; 1253 } 1254 tv.changeMute(mute); 1255 } 1256 }); 1257 } 1258 1259 @Override 1260 public void setArcMode(final boolean enabled) { 1261 enforceAccessPermission(); 1262 runOnServiceThread(new Runnable() { 1263 @Override 1264 public void run() { 1265 HdmiCecLocalDeviceTv tv = tv(); 1266 if (tv == null) { 1267 Slog.w(TAG, "Local tv device not available to change arc mode."); 1268 return; 1269 } 1270 } 1271 }); 1272 } 1273 1274 @Override 1275 public void setProhibitMode(final boolean enabled) { 1276 enforceAccessPermission(); 1277 if (!isTvDevice()) { 1278 return; 1279 } 1280 HdmiControlService.this.setProhibitMode(enabled); 1281 } 1282 1283 @Override 1284 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1285 final int deviceType) { 1286 enforceAccessPermission(); 1287 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1288 } 1289 1290 @Override 1291 public void sendVendorCommand(final int deviceType, final int targetAddress, 1292 final byte[] params, final boolean hasVendorId) { 1293 enforceAccessPermission(); 1294 runOnServiceThread(new Runnable() { 1295 @Override 1296 public void run() { 1297 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1298 if (device == null) { 1299 Slog.w(TAG, "Local device not available"); 1300 return; 1301 } 1302 if (hasVendorId) { 1303 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1304 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1305 getVendorId(), params)); 1306 } else { 1307 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1308 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1309 } 1310 } 1311 }); 1312 } 1313 1314 @Override 1315 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1316 HdmiControlService.this.setHdmiRecordListener(listener); 1317 } 1318 1319 @Override 1320 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1321 runOnServiceThread(new Runnable() { 1322 @Override 1323 public void run() { 1324 if (!isTvDevice()) { 1325 Slog.w(TAG, "No TV is available."); 1326 return; 1327 } 1328 tv().startOneTouchRecord(recorderAddress, recordSource); 1329 } 1330 }); 1331 } 1332 1333 @Override 1334 public void stopOneTouchRecord(final int recorderAddress) { 1335 runOnServiceThread(new Runnable() { 1336 @Override 1337 public void run() { 1338 if (!isTvDevice()) { 1339 Slog.w(TAG, "No TV is available."); 1340 return; 1341 } 1342 tv().stopOneTouchRecord(recorderAddress); 1343 } 1344 }); 1345 } 1346 1347 @Override 1348 public void startTimerRecording(final int recorderAddress, final int sourceType, 1349 final byte[] recordSource) { 1350 runOnServiceThread(new Runnable() { 1351 @Override 1352 public void run() { 1353 if (!isTvDevice()) { 1354 Slog.w(TAG, "No TV is available."); 1355 return; 1356 } 1357 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1358 } 1359 }); 1360 } 1361 1362 @Override 1363 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1364 final byte[] recordSource) { 1365 runOnServiceThread(new Runnable() { 1366 @Override 1367 public void run() { 1368 if (!isTvDevice()) { 1369 Slog.w(TAG, "No TV is available."); 1370 return; 1371 } 1372 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1373 } 1374 }); 1375 } 1376 1377 @Override 1378 public void sendScratchpadCommand(final int portId, final int offset, final int length, 1379 final byte[] data) { 1380 enforceAccessPermission(); 1381 runOnServiceThread(new Runnable() { 1382 @Override 1383 public void run() { 1384 if (!isControlEnabled()) { 1385 Slog.w(TAG, "Hdmi control is disabled."); 1386 return ; 1387 } 1388 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 1389 if (device == null) { 1390 Slog.w(TAG, "Invalid port id:" + portId); 1391 return; 1392 } 1393 mMhlController.sendScratchpadCommand(portId, offset, length, data); 1394 } 1395 }); 1396 } 1397 1398 @Override 1399 public void addHdmiMhlScratchpadCommandListener( 1400 IHdmiMhlScratchpadCommandListener listener) { 1401 enforceAccessPermission(); 1402 HdmiControlService.this.addHdmiMhlScratchpadCommandListener(listener); 1403 } 1404 1405 @Override 1406 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 1407 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1408 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 1409 1410 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); 1411 pw.println("mProhibitMode: " + mProhibitMode); 1412 if (mCecController != null) { 1413 pw.println("mCecController: "); 1414 pw.increaseIndent(); 1415 mCecController.dump(pw); 1416 pw.decreaseIndent(); 1417 } 1418 pw.println("mPortInfo: "); 1419 pw.increaseIndent(); 1420 for (HdmiPortInfo hdmiPortInfo : mPortInfo) { 1421 pw.println("- " + hdmiPortInfo); 1422 } 1423 pw.decreaseIndent(); 1424 pw.println("mPowerStatus: " + mPowerStatus); 1425 } 1426 } 1427 1428 @ServiceThreadOnly 1429 private void oneTouchPlay(final IHdmiControlCallback callback) { 1430 assertRunOnServiceThread(); 1431 HdmiCecLocalDevicePlayback source = playback(); 1432 if (source == null) { 1433 Slog.w(TAG, "Local playback device not available"); 1434 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1435 return; 1436 } 1437 source.oneTouchPlay(callback); 1438 } 1439 1440 @ServiceThreadOnly 1441 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1442 assertRunOnServiceThread(); 1443 HdmiCecLocalDevicePlayback source = playback(); 1444 if (source == null) { 1445 Slog.w(TAG, "Local playback device not available"); 1446 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1447 return; 1448 } 1449 source.queryDisplayStatus(callback); 1450 } 1451 1452 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1453 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1454 try { 1455 listener.asBinder().linkToDeath(record, 0); 1456 } catch (RemoteException e) { 1457 Slog.w(TAG, "Listener already died"); 1458 return; 1459 } 1460 synchronized (mLock) { 1461 mHotplugEventListenerRecords.add(record); 1462 } 1463 } 1464 1465 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1466 synchronized (mLock) { 1467 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1468 if (record.mListener.asBinder() == listener.asBinder()) { 1469 listener.asBinder().unlinkToDeath(record, 0); 1470 mHotplugEventListenerRecords.remove(record); 1471 break; 1472 } 1473 } 1474 } 1475 } 1476 1477 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1478 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1479 try { 1480 listener.asBinder().linkToDeath(record, 0); 1481 } catch (RemoteException e) { 1482 Slog.w(TAG, "Listener already died"); 1483 return; 1484 } 1485 synchronized (mLock) { 1486 mDeviceEventListenerRecords.add(record); 1487 } 1488 } 1489 1490 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1491 synchronized (mLock) { 1492 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 1493 try { 1494 record.mListener.onStatusChanged(device, status); 1495 } catch (RemoteException e) { 1496 Slog.e(TAG, "Failed to report device event:" + e); 1497 } 1498 } 1499 } 1500 } 1501 1502 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1503 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1504 listener); 1505 try { 1506 listener.asBinder().linkToDeath(record, 0); 1507 } catch (RemoteException e) { 1508 Slog.w(TAG, "Listener already died"); 1509 return; 1510 } 1511 synchronized (mLock) { 1512 mSystemAudioModeChangeListenerRecords.add(record); 1513 } 1514 } 1515 1516 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1517 synchronized (mLock) { 1518 for (SystemAudioModeChangeListenerRecord record : 1519 mSystemAudioModeChangeListenerRecords) { 1520 if (record.mListener.asBinder() == listener) { 1521 listener.asBinder().unlinkToDeath(record, 0); 1522 mSystemAudioModeChangeListenerRecords.remove(record); 1523 break; 1524 } 1525 } 1526 } 1527 } 1528 1529 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1530 private final IHdmiInputChangeListener mListener; 1531 1532 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 1533 mListener = listener; 1534 } 1535 1536 @Override 1537 public void binderDied() { 1538 synchronized (mLock) { 1539 mInputChangeListenerRecord = null; 1540 } 1541 } 1542 } 1543 1544 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1545 synchronized (mLock) { 1546 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 1547 try { 1548 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1549 } catch (RemoteException e) { 1550 Slog.w(TAG, "Listener already died"); 1551 return; 1552 } 1553 } 1554 } 1555 1556 void invokeInputChangeListener(HdmiDeviceInfo info) { 1557 synchronized (mLock) { 1558 if (mInputChangeListenerRecord != null) { 1559 try { 1560 mInputChangeListenerRecord.mListener.onChanged(info); 1561 } catch (RemoteException e) { 1562 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1563 } 1564 } 1565 } 1566 } 1567 1568 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1569 synchronized (mLock) { 1570 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 1571 try { 1572 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1573 } catch (RemoteException e) { 1574 Slog.w(TAG, "Listener already died.", e); 1575 } 1576 } 1577 } 1578 1579 byte[] invokeRecordRequestListener(int recorderAddress) { 1580 synchronized (mLock) { 1581 if (mRecordListenerRecord != null) { 1582 try { 1583 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 1584 } catch (RemoteException e) { 1585 Slog.w(TAG, "Failed to start record.", e); 1586 } 1587 } 1588 return EmptyArray.BYTE; 1589 } 1590 } 1591 1592 void invokeOneTouchRecordResult(int result) { 1593 synchronized (mLock) { 1594 if (mRecordListenerRecord != null) { 1595 try { 1596 mRecordListenerRecord.mListener.onOneTouchRecordResult(result); 1597 } catch (RemoteException e) { 1598 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1599 } 1600 } 1601 } 1602 } 1603 1604 void invokeTimerRecordingResult(int result) { 1605 synchronized (mLock) { 1606 if (mRecordListenerRecord != null) { 1607 try { 1608 mRecordListenerRecord.mListener.onTimerRecordingResult(result); 1609 } catch (RemoteException e) { 1610 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1611 } 1612 } 1613 } 1614 } 1615 1616 void invokeClearTimerRecordingResult(int result) { 1617 synchronized (mLock) { 1618 if (mRecordListenerRecord != null) { 1619 try { 1620 mRecordListenerRecord.mListener.onClearTimerRecordingResult(result); 1621 } catch (RemoteException e) { 1622 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1623 } 1624 } 1625 } 1626 } 1627 1628 private void invokeCallback(IHdmiControlCallback callback, int result) { 1629 try { 1630 callback.onComplete(result); 1631 } catch (RemoteException e) { 1632 Slog.e(TAG, "Invoking callback failed:" + e); 1633 } 1634 } 1635 1636 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 1637 boolean enabled) { 1638 try { 1639 listener.onStatusChanged(enabled); 1640 } catch (RemoteException e) { 1641 Slog.e(TAG, "Invoking callback failed:" + e); 1642 } 1643 } 1644 1645 private void announceHotplugEvent(int portId, boolean connected) { 1646 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1647 synchronized (mLock) { 1648 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1649 invokeHotplugEventListenerLocked(record.mListener, event); 1650 } 1651 } 1652 } 1653 1654 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1655 HdmiHotplugEvent event) { 1656 try { 1657 listener.onReceived(event); 1658 } catch (RemoteException e) { 1659 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1660 } 1661 } 1662 1663 private HdmiCecLocalDeviceTv tv() { 1664 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1665 } 1666 1667 boolean isTvDevice() { 1668 return tv() != null; 1669 } 1670 1671 private HdmiCecLocalDevicePlayback playback() { 1672 return (HdmiCecLocalDevicePlayback) 1673 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1674 } 1675 1676 AudioManager getAudioManager() { 1677 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1678 } 1679 1680 boolean isControlEnabled() { 1681 synchronized (mLock) { 1682 return mHdmiControlEnabled; 1683 } 1684 } 1685 1686 @ServiceThreadOnly 1687 int getPowerStatus() { 1688 assertRunOnServiceThread(); 1689 return mPowerStatus; 1690 } 1691 1692 @ServiceThreadOnly 1693 boolean isPowerOnOrTransient() { 1694 assertRunOnServiceThread(); 1695 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1696 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1697 } 1698 1699 @ServiceThreadOnly 1700 boolean isPowerStandbyOrTransient() { 1701 assertRunOnServiceThread(); 1702 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1703 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1704 } 1705 1706 @ServiceThreadOnly 1707 boolean isPowerStandby() { 1708 assertRunOnServiceThread(); 1709 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1710 } 1711 1712 @ServiceThreadOnly 1713 void wakeUp() { 1714 assertRunOnServiceThread(); 1715 mWakeUpMessageReceived = true; 1716 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1717 pm.wakeUp(SystemClock.uptimeMillis()); 1718 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1719 // the intent, the sequence will continue at onWakeUp(). 1720 } 1721 1722 @ServiceThreadOnly 1723 void standby() { 1724 assertRunOnServiceThread(); 1725 mStandbyMessageReceived = true; 1726 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1727 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 1728 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1729 // the intent, the sequence will continue at onStandby(). 1730 } 1731 1732 @ServiceThreadOnly 1733 private void onWakeUp() { 1734 assertRunOnServiceThread(); 1735 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1736 if (mCecController != null) { 1737 if (mHdmiControlEnabled) { 1738 int startReason = INITIATED_BY_SCREEN_ON; 1739 if (mWakeUpMessageReceived) { 1740 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1741 } 1742 initializeCec(startReason); 1743 } 1744 } else { 1745 Slog.i(TAG, "Device does not support HDMI-CEC."); 1746 } 1747 // TODO: Initialize MHL local devices. 1748 } 1749 1750 @ServiceThreadOnly 1751 private void onStandby() { 1752 assertRunOnServiceThread(); 1753 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1754 1755 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1756 disableDevices(new PendingActionClearedCallback() { 1757 @Override 1758 public void onCleared(HdmiCecLocalDevice device) { 1759 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1760 devices.remove(device); 1761 if (devices.isEmpty()) { 1762 onStandbyCompleted(); 1763 // We will not clear local devices here, since some OEM/SOC will keep passing 1764 // the received packets until the application processor enters to the sleep 1765 // actually. 1766 } 1767 } 1768 }); 1769 } 1770 1771 @ServiceThreadOnly 1772 private void onLanguageChanged(String language) { 1773 assertRunOnServiceThread(); 1774 mLanguage = language; 1775 1776 if (isTvDevice()) { 1777 tv().broadcastMenuLanguage(language); 1778 } 1779 } 1780 1781 @ServiceThreadOnly 1782 String getLanguage() { 1783 assertRunOnServiceThread(); 1784 return mLanguage; 1785 } 1786 1787 private void disableDevices(PendingActionClearedCallback callback) { 1788 if (mCecController != null) { 1789 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1790 device.disableDevice(mStandbyMessageReceived, callback); 1791 } 1792 if (isTvDevice()) { 1793 unregisterSettingsObserver(); 1794 } 1795 } 1796 1797 mMhlController.clearAllLocalDevices(); 1798 } 1799 1800 @ServiceThreadOnly 1801 private void clearLocalDevices() { 1802 assertRunOnServiceThread(); 1803 if (mCecController == null) { 1804 return; 1805 } 1806 mCecController.clearLogicalAddress(); 1807 mCecController.clearLocalDevices(); 1808 } 1809 1810 @ServiceThreadOnly 1811 private void onStandbyCompleted() { 1812 assertRunOnServiceThread(); 1813 Slog.v(TAG, "onStandbyCompleted"); 1814 1815 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1816 return; 1817 } 1818 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1819 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1820 device.onStandby(mStandbyMessageReceived); 1821 } 1822 mStandbyMessageReceived = false; 1823 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED); 1824 } 1825 1826 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1827 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1828 try { 1829 listener.asBinder().linkToDeath(record, 0); 1830 } catch (RemoteException e) { 1831 Slog.w(TAG, "Listener already died"); 1832 return; 1833 } 1834 synchronized (mLock) { 1835 mVendorCommandListenerRecords.add(record); 1836 } 1837 } 1838 1839 void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1840 boolean hasVendorId) { 1841 synchronized (mLock) { 1842 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1843 if (record.mDeviceType != deviceType) { 1844 continue; 1845 } 1846 try { 1847 record.mListener.onReceived(srcAddress, params, hasVendorId); 1848 } catch (RemoteException e) { 1849 Slog.e(TAG, "Failed to notify vendor command reception", e); 1850 } 1851 } 1852 } 1853 } 1854 1855 private void addHdmiMhlScratchpadCommandListener(IHdmiMhlScratchpadCommandListener listener) { 1856 HdmiMhlScratchpadCommandListenerRecord record = 1857 new HdmiMhlScratchpadCommandListenerRecord(listener); 1858 try { 1859 listener.asBinder().linkToDeath(record, 0); 1860 } catch (RemoteException e) { 1861 Slog.w(TAG, "Listener already died."); 1862 return; 1863 } 1864 1865 synchronized (mLock) { 1866 mScratchpadCommandListenerRecords.add(record); 1867 } 1868 } 1869 1870 void invokeScratchpadCommandListeners(int portId, int offest, int length, byte[] data) { 1871 synchronized (mLock) { 1872 for (HdmiMhlScratchpadCommandListenerRecord record : 1873 mScratchpadCommandListenerRecords) { 1874 try { 1875 record.mListener.onReceived(portId, offest, length, data); 1876 } catch (RemoteException e) { 1877 Slog.e(TAG, "Failed to notify scratchpad command", e); 1878 } 1879 } 1880 } 1881 } 1882 1883 boolean isProhibitMode() { 1884 synchronized (mLock) { 1885 return mProhibitMode; 1886 } 1887 } 1888 1889 void setProhibitMode(boolean enabled) { 1890 synchronized (mLock) { 1891 mProhibitMode = enabled; 1892 } 1893 } 1894 1895 @ServiceThreadOnly 1896 void setCecOption(int key, int value) { 1897 assertRunOnServiceThread(); 1898 mCecController.setOption(key, value); 1899 } 1900 1901 @ServiceThreadOnly 1902 void setControlEnabled(boolean enabled) { 1903 assertRunOnServiceThread(); 1904 1905 int value = toInt(enabled); 1906 mCecController.setOption(OPTION_CEC_ENABLE, value); 1907 mMhlController.setOption(OPTION_MHL_ENABLE, value); 1908 1909 synchronized (mLock) { 1910 mHdmiControlEnabled = enabled; 1911 } 1912 1913 if (enabled) { 1914 initializeCec(INITIATED_BY_ENABLE_CEC); 1915 } else { 1916 disableDevices(new PendingActionClearedCallback() { 1917 @Override 1918 public void onCleared(HdmiCecLocalDevice device) { 1919 assertRunOnServiceThread(); 1920 clearLocalDevices(); 1921 } 1922 }); 1923 } 1924 } 1925 1926 @ServiceThreadOnly 1927 void setActivePortId(int portId) { 1928 assertRunOnServiceThread(); 1929 mActivePortId = portId; 1930 1931 // Resets last input for MHL, which stays valid only after the MHL device was selected, 1932 // and no further switching is done. 1933 setLastInputForMhl(Constants.INVALID_PORT_ID); 1934 } 1935 1936 @ServiceThreadOnly 1937 void setLastInputForMhl(int portId) { 1938 assertRunOnServiceThread(); 1939 mLastInputMhl = portId; 1940 } 1941 1942 @ServiceThreadOnly 1943 int getLastInputForMhl() { 1944 assertRunOnServiceThread(); 1945 return mLastInputMhl; 1946 } 1947 1948 /** 1949 * Performs input change, routing control for MHL device. 1950 * 1951 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 1952 * @param contentOn {@code true} if RAP data is content on; otherwise false 1953 */ 1954 @ServiceThreadOnly 1955 void changeInputForMhl(int portId, boolean contentOn) { 1956 assertRunOnServiceThread(); 1957 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 1958 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 1959 @Override 1960 public void onComplete(int result) throws RemoteException { 1961 // Keep the last input to switch back later when RAP[ContentOff] is received. 1962 // This effectively sets the port to invalid one if the switching is for 1963 // RAP[ContentOff]. 1964 setLastInputForMhl(lastInput); 1965 } 1966 }); 1967 1968 // MHL device is always directly connected to the port. Update the active port ID to avoid 1969 // unnecessary post-routing control task. 1970 tv().setActivePortId(portId); 1971 1972 // The port is either the MHL-enabled port where the mobile device is connected, or 1973 // the last port to go back to when turnoff command is received. Note that the last port 1974 // may not be the MHL-enabled one. In this case the device info to be passed to 1975 // input change listener should be the one describing the corresponding HDMI port. 1976 HdmiMhlLocalDevice device = mMhlController.getLocalDevice(portId); 1977 HdmiDeviceInfo info = (device != null && device.getInfo() != null) 1978 ? device.getInfo() 1979 : mPortDeviceMap.get(portId); 1980 invokeInputChangeListener(info); 1981 } 1982 1983 void setMhlInputChangeEnabled(boolean enabled) { 1984 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 1985 1986 synchronized (mLock) { 1987 mMhlInputChangeEnabled = enabled; 1988 } 1989 } 1990 1991 boolean isMhlInputChangeEnabled() { 1992 synchronized (mLock) { 1993 return mMhlInputChangeEnabled; 1994 } 1995 } 1996 1997 @ServiceThreadOnly 1998 void displayOsd(int messageId) { 1999 assertRunOnServiceThread(); 2000 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2001 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2002 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2003 HdmiControlService.PERMISSION); 2004 } 2005} 2006