HdmiControlService.java revision bdf27fbf746bee11430c4db2ea6dfd026bae77fe
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 for (int type : mLocalDevices) { 420 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); 421 if (localDevice == null) { 422 localDevice = HdmiCecLocalDevice.create(this, type); 423 } 424 localDevice.init(); 425 localDevices.add(localDevice); 426 } 427 // It's now safe to flush existing local devices from mCecController since they were 428 // already moved to 'localDevices'. 429 clearLocalDevices(); 430 allocateLogicalAddress(localDevices, initiatedBy); 431 } 432 433 @ServiceThreadOnly 434 private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, 435 final int initiatedBy) { 436 assertRunOnServiceThread(); 437 mCecController.clearLogicalAddress(); 438 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>(); 439 final int[] finished = new int[1]; 440 for (final HdmiCecLocalDevice localDevice : allocatingDevices) { 441 mCecController.allocateLogicalAddress(localDevice.getType(), 442 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 443 @Override 444 public void onAllocated(int deviceType, int logicalAddress) { 445 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 446 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]"); 447 } else { 448 // Set POWER_STATUS_ON to all local devices because they share lifetime 449 // with system. 450 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType, 451 HdmiControlManager.POWER_STATUS_ON); 452 localDevice.setDeviceInfo(deviceInfo); 453 mCecController.addLocalDevice(deviceType, localDevice); 454 mCecController.addLogicalAddress(logicalAddress); 455 allocatedDevices.add(localDevice); 456 } 457 458 // Address allocation completed for all devices. Notify each device. 459 if (allocatingDevices.size() == ++finished[0]) { 460 if (initiatedBy != INITIATED_BY_HOTPLUG) { 461 // In case of the hotplug we don't call onInitializeCecComplete() 462 // since we reallocate the logical address only. 463 onInitializeCecComplete(); 464 } 465 notifyAddressAllocated(allocatedDevices, initiatedBy); 466 } 467 } 468 }); 469 } 470 } 471 472 @ServiceThreadOnly 473 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) { 474 assertRunOnServiceThread(); 475 for (HdmiCecLocalDevice device : devices) { 476 int address = device.getDeviceInfo().getLogicalAddress(); 477 device.handleAddressAllocated(address, initiatedBy); 478 } 479 } 480 481 // Initialize HDMI port information. Combine the information from CEC and MHL HAL and 482 // keep them in one place. 483 @ServiceThreadOnly 484 private void initPortInfo() { 485 assertRunOnServiceThread(); 486 HdmiPortInfo[] cecPortInfo = null; 487 488 // CEC HAL provides majority of the info while MHL does only MHL support flag for 489 // each port. Return empty array if CEC HAL didn't provide the info. 490 if (mCecController != null) { 491 cecPortInfo = mCecController.getPortInfos(); 492 } 493 if (cecPortInfo == null) { 494 return; 495 } 496 497 SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>(); 498 SparseIntArray portIdMap = new SparseIntArray(); 499 SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>(); 500 for (HdmiPortInfo info : cecPortInfo) { 501 portIdMap.put(info.getAddress(), info.getId()); 502 portInfoMap.put(info.getId(), info); 503 portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId())); 504 } 505 mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); 506 mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); 507 mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); 508 509 HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos(); 510 ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length); 511 for (HdmiPortInfo info : mhlPortInfo) { 512 if (info.isMhlSupported()) { 513 mhlSupportedPorts.add(info.getId()); 514 } 515 } 516 517 // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use 518 // cec port info if we do not have have port that supports MHL. 519 if (mhlSupportedPorts.isEmpty()) { 520 mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo)); 521 return; 522 } 523 ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length); 524 for (HdmiPortInfo info : cecPortInfo) { 525 if (mhlSupportedPorts.contains(info.getId())) { 526 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), 527 info.isCecSupported(), true, info.isArcSupported())); 528 } else { 529 result.add(info); 530 } 531 } 532 mPortInfo = Collections.unmodifiableList(result); 533 } 534 535 List<HdmiPortInfo> getPortInfo() { 536 return mPortInfo; 537 } 538 539 /** 540 * Returns HDMI port information for the given port id. 541 * 542 * @param portId HDMI port id 543 * @return {@link HdmiPortInfo} for the given port 544 */ 545 HdmiPortInfo getPortInfo(int portId) { 546 return mPortInfoMap.get(portId, null); 547 } 548 549 /** 550 * Returns the routing path (physical address) of the HDMI port for the given 551 * port id. 552 */ 553 int portIdToPath(int portId) { 554 HdmiPortInfo portInfo = getPortInfo(portId); 555 if (portInfo == null) { 556 Slog.e(TAG, "Cannot find the port info: " + portId); 557 return Constants.INVALID_PHYSICAL_ADDRESS; 558 } 559 return portInfo.getAddress(); 560 } 561 562 /** 563 * Returns the id of HDMI port located at the top of the hierarchy of 564 * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance, 565 * the port id to be returned is the ID associated with the port address 566 * 0x1000 (1.0.0.0) which is the topmost path of the given routing path. 567 */ 568 int pathToPortId(int path) { 569 int portAddress = path & Constants.ROUTING_PATH_TOP_MASK; 570 return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); 571 } 572 573 boolean isValidPortId(int portId) { 574 return getPortInfo(portId) != null; 575 } 576 577 /** 578 * Returns {@link Looper} for IO operation. 579 * 580 * <p>Declared as package-private. 581 */ 582 Looper getIoLooper() { 583 return mIoThread.getLooper(); 584 } 585 586 /** 587 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 588 * for tasks that are running on main service thread. 589 * 590 * <p>Declared as package-private. 591 */ 592 Looper getServiceLooper() { 593 return mHandler.getLooper(); 594 } 595 596 /** 597 * Returns physical address of the device. 598 */ 599 int getPhysicalAddress() { 600 return mCecController.getPhysicalAddress(); 601 } 602 603 /** 604 * Returns vendor id of CEC service. 605 */ 606 int getVendorId() { 607 return mCecController.getVendorId(); 608 } 609 610 @ServiceThreadOnly 611 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 612 assertRunOnServiceThread(); 613 HdmiCecLocalDeviceTv tv = tv(); 614 if (tv == null) { 615 return null; 616 } 617 return tv.getCecDeviceInfo(logicalAddress); 618 } 619 620 /** 621 * Returns version of CEC. 622 */ 623 int getCecVersion() { 624 return mCecController.getVersion(); 625 } 626 627 /** 628 * Whether a device of the specified physical address is connected to ARC enabled port. 629 */ 630 boolean isConnectedToArcPort(int physicalAddress) { 631 int portId = pathToPortId(physicalAddress); 632 if (portId != Constants.INVALID_PORT_ID) { 633 return mPortInfoMap.get(portId).isArcSupported(); 634 } 635 return false; 636 } 637 638 void runOnServiceThread(Runnable runnable) { 639 mHandler.post(runnable); 640 } 641 642 void runOnServiceThreadAtFrontOfQueue(Runnable runnable) { 643 mHandler.postAtFrontOfQueue(runnable); 644 } 645 646 private void assertRunOnServiceThread() { 647 if (Looper.myLooper() != mHandler.getLooper()) { 648 throw new IllegalStateException("Should run on service thread."); 649 } 650 } 651 652 /** 653 * Transmit a CEC command to CEC bus. 654 * 655 * @param command CEC command to send out 656 * @param callback interface used to the result of send command 657 */ 658 @ServiceThreadOnly 659 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 660 assertRunOnServiceThread(); 661 if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) { 662 mCecController.sendCommand(command, callback); 663 } else { 664 HdmiLogger.error("Invalid message type:" + command); 665 if (callback != null) { 666 callback.onSendCompleted(Constants.SEND_RESULT_FAILURE); 667 } 668 } 669 } 670 671 @ServiceThreadOnly 672 void sendCecCommand(HdmiCecMessage command) { 673 assertRunOnServiceThread(); 674 sendCecCommand(command, null); 675 } 676 677 /** 678 * Send <Feature Abort> command on the given CEC message if possible. 679 * If the aborted message is invalid, then it wont send the message. 680 * @param command original command to be aborted 681 * @param reason reason of feature abort 682 */ 683 @ServiceThreadOnly 684 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 685 assertRunOnServiceThread(); 686 mCecController.maySendFeatureAbortCommand(command, reason); 687 } 688 689 @ServiceThreadOnly 690 boolean handleCecCommand(HdmiCecMessage message) { 691 assertRunOnServiceThread(); 692 int errorCode = mMessageValidator.isValid(message); 693 if (errorCode != HdmiCecMessageValidator.OK) { 694 // We'll not response on the messages with the invalid source or destination. 695 if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) { 696 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND); 697 } 698 return true; 699 } 700 return dispatchMessageToLocalDevice(message); 701 } 702 703 void setAudioReturnChannel(boolean enabled) { 704 mCecController.setAudioReturnChannel(enabled); 705 } 706 707 @ServiceThreadOnly 708 private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) { 709 assertRunOnServiceThread(); 710 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 711 if (device.dispatchMessage(message) 712 && message.getDestination() != Constants.ADDR_BROADCAST) { 713 return true; 714 } 715 } 716 717 if (message.getDestination() != Constants.ADDR_BROADCAST) { 718 HdmiLogger.warning("Unhandled cec command:" + message); 719 } 720 return false; 721 } 722 723 /** 724 * Called when a new hotplug event is issued. 725 * 726 * @param portId hdmi port number where hot plug event issued. 727 * @param connected whether to be plugged in or not 728 */ 729 @ServiceThreadOnly 730 void onHotplug(int portId, boolean connected) { 731 assertRunOnServiceThread(); 732 733 if (connected && !isTvDevice()) { 734 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 735 for (int type : mLocalDevices) { 736 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type); 737 if (localDevice == null) { 738 localDevice = HdmiCecLocalDevice.create(this, type); 739 localDevice.init(); 740 } 741 localDevices.add(localDevice); 742 } 743 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); 744 } 745 746 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 747 device.onHotplug(portId, connected); 748 } 749 announceHotplugEvent(portId, connected); 750 } 751 752 /** 753 * Poll all remote devices. It sends <Polling Message> to all remote 754 * devices. 755 * 756 * @param callback an interface used to get a list of all remote devices' address 757 * @param sourceAddress a logical address of source device where sends polling message 758 * @param pickStrategy strategy how to pick polling candidates 759 * @param retryCount the number of retry used to send polling message to remote devices 760 * @throw IllegalArgumentException if {@code pickStrategy} is invalid value 761 */ 762 @ServiceThreadOnly 763 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 764 int retryCount) { 765 assertRunOnServiceThread(); 766 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 767 retryCount); 768 } 769 770 private int checkPollStrategy(int pickStrategy) { 771 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 772 if (strategy == 0) { 773 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 774 } 775 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 776 if (iterationStrategy == 0) { 777 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 778 } 779 return strategy | iterationStrategy; 780 } 781 782 List<HdmiCecLocalDevice> getAllLocalDevices() { 783 assertRunOnServiceThread(); 784 return mCecController.getLocalDeviceList(); 785 } 786 787 Object getServiceLock() { 788 return mLock; 789 } 790 791 void setAudioStatus(boolean mute, int volume) { 792 AudioManager audioManager = getAudioManager(); 793 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 794 if (mute) { 795 if (!muted) { 796 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 797 } 798 } else { 799 if (muted) { 800 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 801 } 802 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 803 // volume change notification back to hdmi control service. 804 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 805 AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME); 806 } 807 } 808 809 void announceSystemAudioModeChange(boolean enabled) { 810 synchronized (mLock) { 811 for (SystemAudioModeChangeListenerRecord record : 812 mSystemAudioModeChangeListenerRecords) { 813 invokeSystemAudioModeChangeLocked(record.mListener, enabled); 814 } 815 } 816 } 817 818 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) { 819 // TODO: find better name instead of model name. 820 String displayName = Build.MODEL; 821 return new HdmiDeviceInfo(logicalAddress, 822 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType, 823 getVendorId(), displayName); 824 } 825 826 @ServiceThreadOnly 827 void handleMhlHotplugEvent(int portId, boolean connected) { 828 assertRunOnServiceThread(); 829 // Hotplug event is used to add/remove MHL devices as TV input. 830 if (connected) { 831 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId); 832 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice); 833 if (oldDevice != null) { 834 oldDevice.onDeviceRemoved(); 835 Slog.i(TAG, "Old device of port " + portId + " is removed"); 836 } 837 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE); 838 updateSafeMhlInput(); 839 } else { 840 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId); 841 if (device != null) { 842 device.onDeviceRemoved(); 843 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 844 updateSafeMhlInput(); 845 } else { 846 Slog.w(TAG, "No device to remove:[portId=" + portId); 847 } 848 } 849 announceHotplugEvent(portId, connected); 850 } 851 852 @ServiceThreadOnly 853 void handleMhlBusModeChanged(int portId, int busmode) { 854 assertRunOnServiceThread(); 855 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 856 if (device != null) { 857 device.setBusMode(busmode); 858 } else { 859 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId + 860 ", busmode:" + busmode + "]"); 861 } 862 } 863 864 @ServiceThreadOnly 865 void handleMhlBusOvercurrent(int portId, boolean on) { 866 assertRunOnServiceThread(); 867 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 868 if (device != null) { 869 device.onBusOvercurrentDetected(on); 870 } else { 871 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]"); 872 } 873 } 874 875 @ServiceThreadOnly 876 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) { 877 assertRunOnServiceThread(); 878 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 879 880 if (device != null) { 881 device.setDeviceStatusChange(adopterId, deviceId); 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 // Returns all the CEC devices on the bus including system audio, switch, 1236 // even those of reserved type. 1237 @Override 1238 public List<HdmiDeviceInfo> getDeviceList() { 1239 enforceAccessPermission(); 1240 HdmiCecLocalDeviceTv tv = tv(); 1241 synchronized (mLock) { 1242 return (tv == null) 1243 ? Collections.<HdmiDeviceInfo>emptyList() 1244 : tv.getSafeCecDevicesLocked(); 1245 } 1246 } 1247 1248 @Override 1249 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 1250 final int maxIndex) { 1251 enforceAccessPermission(); 1252 runOnServiceThread(new Runnable() { 1253 @Override 1254 public void run() { 1255 HdmiCecLocalDeviceTv tv = tv(); 1256 if (tv == null) { 1257 Slog.w(TAG, "Local tv device not available"); 1258 return; 1259 } 1260 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 1261 } 1262 }); 1263 } 1264 1265 @Override 1266 public void setSystemAudioMute(final boolean mute) { 1267 enforceAccessPermission(); 1268 runOnServiceThread(new Runnable() { 1269 @Override 1270 public void run() { 1271 HdmiCecLocalDeviceTv tv = tv(); 1272 if (tv == null) { 1273 Slog.w(TAG, "Local tv device not available"); 1274 return; 1275 } 1276 tv.changeMute(mute); 1277 } 1278 }); 1279 } 1280 1281 @Override 1282 public void setArcMode(final boolean enabled) { 1283 enforceAccessPermission(); 1284 runOnServiceThread(new Runnable() { 1285 @Override 1286 public void run() { 1287 HdmiCecLocalDeviceTv tv = tv(); 1288 if (tv == null) { 1289 Slog.w(TAG, "Local tv device not available to change arc mode."); 1290 return; 1291 } 1292 } 1293 }); 1294 } 1295 1296 @Override 1297 public void setProhibitMode(final boolean enabled) { 1298 enforceAccessPermission(); 1299 if (!isTvDevice()) { 1300 return; 1301 } 1302 HdmiControlService.this.setProhibitMode(enabled); 1303 } 1304 1305 @Override 1306 public void addVendorCommandListener(final IHdmiVendorCommandListener listener, 1307 final int deviceType) { 1308 enforceAccessPermission(); 1309 HdmiControlService.this.addVendorCommandListener(listener, deviceType); 1310 } 1311 1312 @Override 1313 public void sendVendorCommand(final int deviceType, final int targetAddress, 1314 final byte[] params, final boolean hasVendorId) { 1315 enforceAccessPermission(); 1316 runOnServiceThread(new Runnable() { 1317 @Override 1318 public void run() { 1319 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1320 if (device == null) { 1321 Slog.w(TAG, "Local device not available"); 1322 return; 1323 } 1324 if (hasVendorId) { 1325 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 1326 device.getDeviceInfo().getLogicalAddress(), targetAddress, 1327 getVendorId(), params)); 1328 } else { 1329 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 1330 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 1331 } 1332 } 1333 }); 1334 } 1335 1336 @Override 1337 public void sendStandby(final int deviceType, final int deviceId) { 1338 enforceAccessPermission(); 1339 runOnServiceThread(new Runnable() { 1340 @Override 1341 public void run() { 1342 HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType); 1343 if (device == null) { 1344 Slog.w(TAG, "Local device not available"); 1345 return; 1346 } 1347 device.sendStandby(deviceId); 1348 } 1349 }); 1350 } 1351 1352 @Override 1353 public void setHdmiRecordListener(IHdmiRecordListener listener) { 1354 HdmiControlService.this.setHdmiRecordListener(listener); 1355 } 1356 1357 @Override 1358 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 1359 runOnServiceThread(new Runnable() { 1360 @Override 1361 public void run() { 1362 if (!isTvDevice()) { 1363 Slog.w(TAG, "No TV is available."); 1364 return; 1365 } 1366 tv().startOneTouchRecord(recorderAddress, recordSource); 1367 } 1368 }); 1369 } 1370 1371 @Override 1372 public void stopOneTouchRecord(final int recorderAddress) { 1373 runOnServiceThread(new Runnable() { 1374 @Override 1375 public void run() { 1376 if (!isTvDevice()) { 1377 Slog.w(TAG, "No TV is available."); 1378 return; 1379 } 1380 tv().stopOneTouchRecord(recorderAddress); 1381 } 1382 }); 1383 } 1384 1385 @Override 1386 public void startTimerRecording(final int recorderAddress, final int sourceType, 1387 final byte[] recordSource) { 1388 runOnServiceThread(new Runnable() { 1389 @Override 1390 public void run() { 1391 if (!isTvDevice()) { 1392 Slog.w(TAG, "No TV is available."); 1393 return; 1394 } 1395 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 1396 } 1397 }); 1398 } 1399 1400 @Override 1401 public void clearTimerRecording(final int recorderAddress, final int sourceType, 1402 final byte[] recordSource) { 1403 runOnServiceThread(new Runnable() { 1404 @Override 1405 public void run() { 1406 if (!isTvDevice()) { 1407 Slog.w(TAG, "No TV is available."); 1408 return; 1409 } 1410 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 1411 } 1412 }); 1413 } 1414 1415 @Override 1416 public void sendMhlVendorCommand(final int portId, final int offset, final int length, 1417 final byte[] data) { 1418 enforceAccessPermission(); 1419 runOnServiceThread(new Runnable() { 1420 @Override 1421 public void run() { 1422 if (!isControlEnabled()) { 1423 Slog.w(TAG, "Hdmi control is disabled."); 1424 return ; 1425 } 1426 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1427 if (device == null) { 1428 Slog.w(TAG, "Invalid port id:" + portId); 1429 return; 1430 } 1431 mMhlController.sendVendorCommand(portId, offset, length, data); 1432 } 1433 }); 1434 } 1435 1436 @Override 1437 public void addHdmiMhlVendorCommandListener( 1438 IHdmiMhlVendorCommandListener listener) { 1439 enforceAccessPermission(); 1440 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener); 1441 } 1442 1443 @Override 1444 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 1445 getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); 1446 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 1447 1448 pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled); 1449 pw.println("mProhibitMode: " + mProhibitMode); 1450 if (mCecController != null) { 1451 pw.println("mCecController: "); 1452 pw.increaseIndent(); 1453 mCecController.dump(pw); 1454 pw.decreaseIndent(); 1455 } 1456 pw.println("mPortInfo: "); 1457 pw.increaseIndent(); 1458 for (HdmiPortInfo hdmiPortInfo : mPortInfo) { 1459 pw.println("- " + hdmiPortInfo); 1460 } 1461 pw.decreaseIndent(); 1462 pw.println("mPowerStatus: " + mPowerStatus); 1463 } 1464 } 1465 1466 @ServiceThreadOnly 1467 private void oneTouchPlay(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.oneTouchPlay(callback); 1476 } 1477 1478 @ServiceThreadOnly 1479 private void queryDisplayStatus(final IHdmiControlCallback callback) { 1480 assertRunOnServiceThread(); 1481 HdmiCecLocalDevicePlayback source = playback(); 1482 if (source == null) { 1483 Slog.w(TAG, "Local playback device not available"); 1484 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 1485 return; 1486 } 1487 source.queryDisplayStatus(callback); 1488 } 1489 1490 private void addHotplugEventListener(IHdmiHotplugEventListener listener) { 1491 HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 1492 try { 1493 listener.asBinder().linkToDeath(record, 0); 1494 } catch (RemoteException e) { 1495 Slog.w(TAG, "Listener already died"); 1496 return; 1497 } 1498 synchronized (mLock) { 1499 mHotplugEventListenerRecords.add(record); 1500 } 1501 } 1502 1503 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 1504 synchronized (mLock) { 1505 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1506 if (record.mListener.asBinder() == listener.asBinder()) { 1507 listener.asBinder().unlinkToDeath(record, 0); 1508 mHotplugEventListenerRecords.remove(record); 1509 break; 1510 } 1511 } 1512 } 1513 } 1514 1515 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 1516 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 1517 try { 1518 listener.asBinder().linkToDeath(record, 0); 1519 } catch (RemoteException e) { 1520 Slog.w(TAG, "Listener already died"); 1521 return; 1522 } 1523 synchronized (mLock) { 1524 mDeviceEventListenerRecords.add(record); 1525 } 1526 } 1527 1528 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 1529 synchronized (mLock) { 1530 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 1531 try { 1532 record.mListener.onStatusChanged(device, status); 1533 } catch (RemoteException e) { 1534 Slog.e(TAG, "Failed to report device event:" + e); 1535 } 1536 } 1537 } 1538 } 1539 1540 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 1541 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 1542 listener); 1543 try { 1544 listener.asBinder().linkToDeath(record, 0); 1545 } catch (RemoteException e) { 1546 Slog.w(TAG, "Listener already died"); 1547 return; 1548 } 1549 synchronized (mLock) { 1550 mSystemAudioModeChangeListenerRecords.add(record); 1551 } 1552 } 1553 1554 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 1555 synchronized (mLock) { 1556 for (SystemAudioModeChangeListenerRecord record : 1557 mSystemAudioModeChangeListenerRecords) { 1558 if (record.mListener.asBinder() == listener) { 1559 listener.asBinder().unlinkToDeath(record, 0); 1560 mSystemAudioModeChangeListenerRecords.remove(record); 1561 break; 1562 } 1563 } 1564 } 1565 } 1566 1567 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 1568 private final IHdmiInputChangeListener mListener; 1569 1570 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 1571 mListener = listener; 1572 } 1573 1574 @Override 1575 public void binderDied() { 1576 synchronized (mLock) { 1577 mInputChangeListenerRecord = null; 1578 } 1579 } 1580 } 1581 1582 private void setInputChangeListener(IHdmiInputChangeListener listener) { 1583 synchronized (mLock) { 1584 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 1585 try { 1586 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 1587 } catch (RemoteException e) { 1588 Slog.w(TAG, "Listener already died"); 1589 return; 1590 } 1591 } 1592 } 1593 1594 void invokeInputChangeListener(HdmiDeviceInfo info) { 1595 synchronized (mLock) { 1596 if (mInputChangeListenerRecord != null) { 1597 try { 1598 mInputChangeListenerRecord.mListener.onChanged(info); 1599 } catch (RemoteException e) { 1600 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 1601 } 1602 } 1603 } 1604 } 1605 1606 private void setHdmiRecordListener(IHdmiRecordListener listener) { 1607 synchronized (mLock) { 1608 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 1609 try { 1610 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 1611 } catch (RemoteException e) { 1612 Slog.w(TAG, "Listener already died.", e); 1613 } 1614 } 1615 } 1616 1617 byte[] invokeRecordRequestListener(int recorderAddress) { 1618 synchronized (mLock) { 1619 if (mRecordListenerRecord != null) { 1620 try { 1621 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 1622 } catch (RemoteException e) { 1623 Slog.w(TAG, "Failed to start record.", e); 1624 } 1625 } 1626 return EmptyArray.BYTE; 1627 } 1628 } 1629 1630 void invokeOneTouchRecordResult(int result) { 1631 synchronized (mLock) { 1632 if (mRecordListenerRecord != null) { 1633 try { 1634 mRecordListenerRecord.mListener.onOneTouchRecordResult(result); 1635 } catch (RemoteException e) { 1636 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 1637 } 1638 } 1639 } 1640 } 1641 1642 void invokeTimerRecordingResult(int result) { 1643 synchronized (mLock) { 1644 if (mRecordListenerRecord != null) { 1645 try { 1646 mRecordListenerRecord.mListener.onTimerRecordingResult(result); 1647 } catch (RemoteException e) { 1648 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 1649 } 1650 } 1651 } 1652 } 1653 1654 void invokeClearTimerRecordingResult(int result) { 1655 synchronized (mLock) { 1656 if (mRecordListenerRecord != null) { 1657 try { 1658 mRecordListenerRecord.mListener.onClearTimerRecordingResult(result); 1659 } catch (RemoteException e) { 1660 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 1661 } 1662 } 1663 } 1664 } 1665 1666 private void invokeCallback(IHdmiControlCallback callback, int result) { 1667 try { 1668 callback.onComplete(result); 1669 } catch (RemoteException e) { 1670 Slog.e(TAG, "Invoking callback failed:" + e); 1671 } 1672 } 1673 1674 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 1675 boolean enabled) { 1676 try { 1677 listener.onStatusChanged(enabled); 1678 } catch (RemoteException e) { 1679 Slog.e(TAG, "Invoking callback failed:" + e); 1680 } 1681 } 1682 1683 private void announceHotplugEvent(int portId, boolean connected) { 1684 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 1685 synchronized (mLock) { 1686 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 1687 invokeHotplugEventListenerLocked(record.mListener, event); 1688 } 1689 } 1690 } 1691 1692 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 1693 HdmiHotplugEvent event) { 1694 try { 1695 listener.onReceived(event); 1696 } catch (RemoteException e) { 1697 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 1698 } 1699 } 1700 1701 private HdmiCecLocalDeviceTv tv() { 1702 return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 1703 } 1704 1705 boolean isTvDevice() { 1706 return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); 1707 } 1708 1709 private HdmiCecLocalDevicePlayback playback() { 1710 return (HdmiCecLocalDevicePlayback) 1711 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 1712 } 1713 1714 AudioManager getAudioManager() { 1715 return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); 1716 } 1717 1718 boolean isControlEnabled() { 1719 synchronized (mLock) { 1720 return mHdmiControlEnabled; 1721 } 1722 } 1723 1724 @ServiceThreadOnly 1725 int getPowerStatus() { 1726 assertRunOnServiceThread(); 1727 return mPowerStatus; 1728 } 1729 1730 @ServiceThreadOnly 1731 boolean isPowerOnOrTransient() { 1732 assertRunOnServiceThread(); 1733 return mPowerStatus == HdmiControlManager.POWER_STATUS_ON 1734 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1735 } 1736 1737 @ServiceThreadOnly 1738 boolean isPowerStandbyOrTransient() { 1739 assertRunOnServiceThread(); 1740 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY 1741 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1742 } 1743 1744 @ServiceThreadOnly 1745 boolean isPowerStandby() { 1746 assertRunOnServiceThread(); 1747 return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY; 1748 } 1749 1750 @ServiceThreadOnly 1751 void wakeUp() { 1752 assertRunOnServiceThread(); 1753 mWakeUpMessageReceived = true; 1754 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1755 pm.wakeUp(SystemClock.uptimeMillis()); 1756 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 1757 // the intent, the sequence will continue at onWakeUp(). 1758 } 1759 1760 @ServiceThreadOnly 1761 void standby() { 1762 assertRunOnServiceThread(); 1763 mStandbyMessageReceived = true; 1764 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); 1765 pm.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 1766 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 1767 // the intent, the sequence will continue at onStandby(). 1768 } 1769 1770 @ServiceThreadOnly 1771 private void onWakeUp() { 1772 assertRunOnServiceThread(); 1773 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON; 1774 if (mCecController != null) { 1775 if (mHdmiControlEnabled) { 1776 int startReason = INITIATED_BY_SCREEN_ON; 1777 if (mWakeUpMessageReceived) { 1778 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 1779 } 1780 initializeCec(startReason); 1781 } 1782 } else { 1783 Slog.i(TAG, "Device does not support HDMI-CEC."); 1784 } 1785 // TODO: Initialize MHL local devices. 1786 } 1787 1788 @ServiceThreadOnly 1789 private void onStandby() { 1790 assertRunOnServiceThread(); 1791 mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 1792 1793 final List<HdmiCecLocalDevice> devices = getAllLocalDevices(); 1794 disableDevices(new PendingActionClearedCallback() { 1795 @Override 1796 public void onCleared(HdmiCecLocalDevice device) { 1797 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 1798 devices.remove(device); 1799 if (devices.isEmpty()) { 1800 onStandbyCompleted(); 1801 // We will not clear local devices here, since some OEM/SOC will keep passing 1802 // the received packets until the application processor enters to the sleep 1803 // actually. 1804 } 1805 } 1806 }); 1807 } 1808 1809 @ServiceThreadOnly 1810 private void onLanguageChanged(String language) { 1811 assertRunOnServiceThread(); 1812 mLanguage = language; 1813 1814 if (isTvDevice()) { 1815 tv().broadcastMenuLanguage(language); 1816 } 1817 } 1818 1819 @ServiceThreadOnly 1820 String getLanguage() { 1821 assertRunOnServiceThread(); 1822 return mLanguage; 1823 } 1824 1825 private void disableDevices(PendingActionClearedCallback callback) { 1826 if (mCecController != null) { 1827 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1828 device.disableDevice(mStandbyMessageReceived, callback); 1829 } 1830 if (isTvDevice()) { 1831 unregisterSettingsObserver(); 1832 } 1833 } 1834 1835 mMhlController.clearAllLocalDevices(); 1836 } 1837 1838 @ServiceThreadOnly 1839 private void clearLocalDevices() { 1840 assertRunOnServiceThread(); 1841 if (mCecController == null) { 1842 return; 1843 } 1844 mCecController.clearLogicalAddress(); 1845 mCecController.clearLocalDevices(); 1846 } 1847 1848 @ServiceThreadOnly 1849 private void onStandbyCompleted() { 1850 assertRunOnServiceThread(); 1851 Slog.v(TAG, "onStandbyCompleted"); 1852 1853 if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) { 1854 return; 1855 } 1856 mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY; 1857 for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) { 1858 device.onStandby(mStandbyMessageReceived); 1859 } 1860 mStandbyMessageReceived = false; 1861 mCecController.setOption(OPTION_CEC_SERVICE_CONTROL, DISABLED); 1862 } 1863 1864 private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { 1865 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType); 1866 try { 1867 listener.asBinder().linkToDeath(record, 0); 1868 } catch (RemoteException e) { 1869 Slog.w(TAG, "Listener already died"); 1870 return; 1871 } 1872 synchronized (mLock) { 1873 mVendorCommandListenerRecords.add(record); 1874 } 1875 } 1876 1877 boolean invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params, 1878 boolean hasVendorId) { 1879 synchronized (mLock) { 1880 if (mVendorCommandListenerRecords.isEmpty()) { 1881 return false; 1882 } 1883 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 1884 if (record.mDeviceType != deviceType) { 1885 continue; 1886 } 1887 try { 1888 record.mListener.onReceived(srcAddress, params, hasVendorId); 1889 } catch (RemoteException e) { 1890 Slog.e(TAG, "Failed to notify vendor command reception", e); 1891 } 1892 } 1893 return true; 1894 } 1895 } 1896 1897 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { 1898 HdmiMhlVendorCommandListenerRecord record = 1899 new HdmiMhlVendorCommandListenerRecord(listener); 1900 try { 1901 listener.asBinder().linkToDeath(record, 0); 1902 } catch (RemoteException e) { 1903 Slog.w(TAG, "Listener already died."); 1904 return; 1905 } 1906 1907 synchronized (mLock) { 1908 mMhlVendorCommandListenerRecords.add(record); 1909 } 1910 } 1911 1912 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) { 1913 synchronized (mLock) { 1914 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) { 1915 try { 1916 record.mListener.onReceived(portId, offest, length, data); 1917 } catch (RemoteException e) { 1918 Slog.e(TAG, "Failed to notify MHL vendor command", e); 1919 } 1920 } 1921 } 1922 } 1923 1924 boolean isProhibitMode() { 1925 synchronized (mLock) { 1926 return mProhibitMode; 1927 } 1928 } 1929 1930 void setProhibitMode(boolean enabled) { 1931 synchronized (mLock) { 1932 mProhibitMode = enabled; 1933 } 1934 } 1935 1936 @ServiceThreadOnly 1937 void setCecOption(int key, int value) { 1938 assertRunOnServiceThread(); 1939 mCecController.setOption(key, value); 1940 } 1941 1942 @ServiceThreadOnly 1943 void setControlEnabled(boolean enabled) { 1944 assertRunOnServiceThread(); 1945 1946 int value = toInt(enabled); 1947 mCecController.setOption(OPTION_CEC_ENABLE, value); 1948 mMhlController.setOption(OPTION_MHL_ENABLE, value); 1949 1950 synchronized (mLock) { 1951 mHdmiControlEnabled = enabled; 1952 } 1953 1954 if (enabled) { 1955 initializeCec(INITIATED_BY_ENABLE_CEC); 1956 } else { 1957 disableDevices(new PendingActionClearedCallback() { 1958 @Override 1959 public void onCleared(HdmiCecLocalDevice device) { 1960 assertRunOnServiceThread(); 1961 clearLocalDevices(); 1962 } 1963 }); 1964 } 1965 } 1966 1967 @ServiceThreadOnly 1968 void setActivePortId(int portId) { 1969 assertRunOnServiceThread(); 1970 mActivePortId = portId; 1971 1972 // Resets last input for MHL, which stays valid only after the MHL device was selected, 1973 // and no further switching is done. 1974 setLastInputForMhl(Constants.INVALID_PORT_ID); 1975 } 1976 1977 @ServiceThreadOnly 1978 void setLastInputForMhl(int portId) { 1979 assertRunOnServiceThread(); 1980 mLastInputMhl = portId; 1981 } 1982 1983 @ServiceThreadOnly 1984 int getLastInputForMhl() { 1985 assertRunOnServiceThread(); 1986 return mLastInputMhl; 1987 } 1988 1989 /** 1990 * Performs input change, routing control for MHL device. 1991 * 1992 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 1993 * @param contentOn {@code true} if RAP data is content on; otherwise false 1994 */ 1995 @ServiceThreadOnly 1996 void changeInputForMhl(int portId, boolean contentOn) { 1997 assertRunOnServiceThread(); 1998 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 1999 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 2000 @Override 2001 public void onComplete(int result) throws RemoteException { 2002 // Keep the last input to switch back later when RAP[ContentOff] is received. 2003 // This effectively sets the port to invalid one if the switching is for 2004 // RAP[ContentOff]. 2005 setLastInputForMhl(lastInput); 2006 } 2007 }); 2008 2009 // MHL device is always directly connected to the port. Update the active port ID to avoid 2010 // unnecessary post-routing control task. 2011 tv().setActivePortId(portId); 2012 2013 // The port is either the MHL-enabled port where the mobile device is connected, or 2014 // the last port to go back to when turnoff command is received. Note that the last port 2015 // may not be the MHL-enabled one. In this case the device info to be passed to 2016 // input change listener should be the one describing the corresponding HDMI port. 2017 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 2018 HdmiDeviceInfo info = (device != null) ? device.getInfo() : mPortDeviceMap.get(portId); 2019 invokeInputChangeListener(info); 2020 } 2021 2022 void setMhlInputChangeEnabled(boolean enabled) { 2023 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 2024 2025 synchronized (mLock) { 2026 mMhlInputChangeEnabled = enabled; 2027 } 2028 } 2029 2030 boolean isMhlInputChangeEnabled() { 2031 synchronized (mLock) { 2032 return mMhlInputChangeEnabled; 2033 } 2034 } 2035 2036 @ServiceThreadOnly 2037 void displayOsd(int messageId) { 2038 assertRunOnServiceThread(); 2039 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2040 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2041 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2042 HdmiControlService.PERMISSION); 2043 } 2044 2045 @ServiceThreadOnly 2046 void displayOsd(int messageId, int extra) { 2047 assertRunOnServiceThread(); 2048 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 2049 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 2050 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRAM_PARAM1, extra); 2051 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 2052 HdmiControlService.PERMISSION); 2053 } 2054} 2055