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