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