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