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