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