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