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