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