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 17 package com.android.server.hdmi; 18 19 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE; 20 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE; 21 import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_DISABLED; 22 import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED; 23 import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED; 24 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED; 25 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED; 26 27 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED; 28 import static com.android.server.hdmi.Constants.DISABLED; 29 import static com.android.server.hdmi.Constants.ENABLED; 30 import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING; 31 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE; 32 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING; 33 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE; 34 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL; 35 import static com.android.server.power.ShutdownThread.SHUTDOWN_ACTION_PROPERTY; 36 37 import android.annotation.IntDef; 38 import android.annotation.NonNull; 39 import android.annotation.Nullable; 40 import android.content.BroadcastReceiver; 41 import android.content.ContentResolver; 42 import android.content.Context; 43 import android.content.Intent; 44 import android.content.IntentFilter; 45 import android.database.ContentObserver; 46 import android.hardware.display.DisplayManager; 47 import android.hardware.hdmi.DeviceFeatures; 48 import android.hardware.hdmi.HdmiControlManager; 49 import android.hardware.hdmi.HdmiDeviceInfo; 50 import android.hardware.hdmi.HdmiHotplugEvent; 51 import android.hardware.hdmi.HdmiPortInfo; 52 import android.hardware.hdmi.IHdmiCecSettingChangeListener; 53 import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener; 54 import android.hardware.hdmi.IHdmiControlCallback; 55 import android.hardware.hdmi.IHdmiControlService; 56 import android.hardware.hdmi.IHdmiControlStatusChangeListener; 57 import android.hardware.hdmi.IHdmiDeviceEventListener; 58 import android.hardware.hdmi.IHdmiHotplugEventListener; 59 import android.hardware.hdmi.IHdmiInputChangeListener; 60 import android.hardware.hdmi.IHdmiMhlVendorCommandListener; 61 import android.hardware.hdmi.IHdmiRecordListener; 62 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 63 import android.hardware.hdmi.IHdmiVendorCommandListener; 64 import android.hardware.tv.cec.V1_0.SendMessageResult; 65 import android.media.AudioAttributes; 66 import android.media.AudioDescriptor; 67 import android.media.AudioDeviceAttributes; 68 import android.media.AudioDeviceInfo; 69 import android.media.AudioDeviceVolumeManager; 70 import android.media.AudioManager; 71 import android.media.AudioProfile; 72 import android.media.VolumeInfo; 73 import android.media.session.MediaController; 74 import android.media.session.MediaSessionManager; 75 import android.media.tv.TvInputManager; 76 import android.media.tv.TvInputManager.TvInputCallback; 77 import android.net.Uri; 78 import android.os.Binder; 79 import android.os.Build; 80 import android.os.Handler; 81 import android.os.HandlerThread; 82 import android.os.IBinder; 83 import android.os.Looper; 84 import android.os.PowerManager; 85 import android.os.RemoteCallbackList; 86 import android.os.RemoteException; 87 import android.os.ResultReceiver; 88 import android.os.ShellCallback; 89 import android.os.SystemClock; 90 import android.os.SystemProperties; 91 import android.os.UserHandle; 92 import android.provider.DeviceConfig; 93 import android.provider.Settings.Global; 94 import android.sysprop.HdmiProperties; 95 import android.text.TextUtils; 96 import android.util.ArrayMap; 97 import android.util.Slog; 98 import android.util.SparseArray; 99 import android.view.Display; 100 import android.view.KeyEvent; 101 102 import com.android.internal.annotations.GuardedBy; 103 import com.android.internal.annotations.VisibleForTesting; 104 import com.android.internal.util.DumpUtils; 105 import com.android.internal.util.IndentingPrintWriter; 106 import com.android.server.SystemService; 107 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 108 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback; 109 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource; 110 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback; 111 112 import libcore.util.EmptyArray; 113 114 import java.io.FileDescriptor; 115 import java.io.PrintWriter; 116 import java.lang.annotation.Retention; 117 import java.lang.annotation.RetentionPolicy; 118 import java.util.ArrayList; 119 import java.util.Arrays; 120 import java.util.Collection; 121 import java.util.Collections; 122 import java.util.HashMap; 123 import java.util.HashSet; 124 import java.util.List; 125 import java.util.Locale; 126 import java.util.Map; 127 import java.util.Objects; 128 import java.util.Set; 129 import java.util.concurrent.Executor; 130 import java.util.stream.Collectors; 131 132 /** 133 * Provides a service for sending and processing HDMI control messages, 134 * HDMI-CEC and MHL control command, and providing the information on both standard. 135 * 136 * Additionally takes care of establishing and managing an eARC connection. 137 */ 138 public class HdmiControlService extends SystemService { 139 private static final String TAG = "HdmiControlService"; 140 private static final Locale HONG_KONG = new Locale("zh", "HK"); 141 private static final Locale MACAU = new Locale("zh", "MO"); 142 143 private static final Map<String, String> sTerminologyToBibliographicMap = 144 createsTerminologyToBibliographicMap(); 145 createsTerminologyToBibliographicMap()146 private static Map<String, String> createsTerminologyToBibliographicMap() { 147 Map<String, String> temp = new HashMap<>(); 148 // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE) 149 temp.put("sqi", "alb"); // Albanian 150 temp.put("hye", "arm"); // Armenian 151 temp.put("eus", "baq"); // Basque 152 temp.put("mya", "bur"); // Burmese 153 temp.put("ces", "cze"); // Czech 154 temp.put("nld", "dut"); // Dutch 155 temp.put("kat", "geo"); // Georgian 156 temp.put("deu", "ger"); // German 157 temp.put("ell", "gre"); // Greek 158 temp.put("fra", "fre"); // French 159 temp.put("isl", "ice"); // Icelandic 160 temp.put("mkd", "mac"); // Macedonian 161 temp.put("mri", "mao"); // Maori 162 temp.put("msa", "may"); // Malay 163 temp.put("fas", "per"); // Persian 164 temp.put("ron", "rum"); // Romanian 165 temp.put("slk", "slo"); // Slovak 166 temp.put("bod", "tib"); // Tibetan 167 temp.put("cym", "wel"); // Welsh 168 return Collections.unmodifiableMap(temp); 169 } 170 localeToMenuLanguage(Locale locale)171 @VisibleForTesting static String localeToMenuLanguage(Locale locale) { 172 if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) { 173 // Android always returns "zho" for all Chinese variants. 174 // Use "bibliographic" code defined in CEC639-2 for traditional 175 // Chinese used in Taiwan/Hong Kong/Macau. 176 return "chi"; 177 } else { 178 String language = locale.getISO3Language(); 179 180 // locale.getISO3Language() returns terminology code and need to 181 // send it as bibliographic code instead since the Bibliographic 182 // codes of ISO/FDIS 639-2 shall be used. 183 // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi" 184 // But, as it depends on the locale, is not handled here. 185 if (sTerminologyToBibliographicMap.containsKey(language)) { 186 language = sTerminologyToBibliographicMap.get(language); 187 } 188 189 return language; 190 } 191 } 192 193 static final String PERMISSION = "android.permission.HDMI_CEC"; 194 195 // The reason code to initiate initializeCec() and initializeEarc(). 196 static final int INITIATED_BY_ENABLE_CEC = 0; 197 static final int INITIATED_BY_BOOT_UP = 1; 198 static final int INITIATED_BY_SCREEN_ON = 2; 199 static final int INITIATED_BY_WAKE_UP_MESSAGE = 3; 200 static final int INITIATED_BY_HOTPLUG = 4; 201 static final int INITIATED_BY_SOUNDBAR_MODE = 5; 202 static final int INITIATED_BY_ENABLE_EARC = 6; 203 204 // The reason code representing the intent action that drives the standby 205 // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or 206 // Intent.ACTION_SHUTDOWN. 207 static final int STANDBY_SCREEN_OFF = 0; 208 static final int STANDBY_SHUTDOWN = 1; 209 210 private HdmiCecNetwork mHdmiCecNetwork; 211 212 static final int WAKE_UP_SCREEN_ON = 0; 213 static final int WAKE_UP_BOOT_UP = 1; 214 215 // The reason code for starting the wake-up procedure. This procedure starts either by 216 // Intent.ACTION_SCREEN_ON or after boot-up. 217 @IntDef({ 218 WAKE_UP_SCREEN_ON, 219 WAKE_UP_BOOT_UP 220 }) 221 @Retention(RetentionPolicy.SOURCE) 222 public @interface WakeReason { 223 } 224 225 @VisibleForTesting 226 static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI = new AudioDeviceAttributes( 227 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI, ""); 228 @VisibleForTesting 229 static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_ARC = 230 new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 231 AudioDeviceInfo.TYPE_HDMI_ARC, ""); 232 static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC = 233 new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, 234 AudioDeviceInfo.TYPE_HDMI_EARC, ""); 235 236 // Audio output devices used for absolute volume behavior 237 private static final List<AudioDeviceAttributes> AVB_AUDIO_OUTPUT_DEVICES = 238 Collections.unmodifiableList(Arrays.asList(AUDIO_OUTPUT_DEVICE_HDMI, 239 AUDIO_OUTPUT_DEVICE_HDMI_ARC, AUDIO_OUTPUT_DEVICE_HDMI_EARC)); 240 241 // AudioAttributes for STREAM_MUSIC 242 @VisibleForTesting 243 static final AudioAttributes STREAM_MUSIC_ATTRIBUTES = 244 new AudioAttributes.Builder().setLegacyStreamType(AudioManager.STREAM_MUSIC).build(); 245 246 private final Executor mServiceThreadExecutor = new Executor() { 247 @Override 248 public void execute(Runnable r) { 249 runOnServiceThread(r); 250 } 251 }; 252 getServiceThreadExecutor()253 Executor getServiceThreadExecutor() { 254 return mServiceThreadExecutor; 255 } 256 257 // Logical address of the active source. 258 @GuardedBy("mLock") 259 protected final ActiveSource mActiveSource = new ActiveSource(); 260 261 // Whether System Audio Mode is activated or not. 262 @GuardedBy("mLock") 263 private boolean mSystemAudioActivated = false; 264 265 // Whether HDMI CEC volume control is enabled or not. 266 @GuardedBy("mLock") 267 @HdmiControlManager.VolumeControl 268 private int mHdmiCecVolumeControl; 269 270 // Caches the volume behaviors of all audio output devices in AVB_AUDIO_OUTPUT_DEVICES. 271 @GuardedBy("mLock") 272 private Map<AudioDeviceAttributes, Integer> mAudioDeviceVolumeBehaviors = new HashMap<>(); 273 274 // Maximum volume of AudioManager.STREAM_MUSIC. Set upon gaining access to system services. 275 private int mStreamMusicMaxVolume; 276 277 // Make sure HdmiCecConfig is instantiated and the XMLs are read. 278 private HdmiCecConfig mHdmiCecConfig; 279 280 /** 281 * Interface to report send result. 282 */ 283 interface SendMessageCallback { 284 /** 285 * Called when {@link HdmiControlService#sendCecCommand} is completed. 286 * 287 * @param error result of send request. 288 * <ul> 289 * <li>{@link SendMessageResult#SUCCESS} 290 * <li>{@link SendMessageResult#NACK} 291 * <li>{@link SendMessageResult#BUSY} 292 * <li>{@link SendMessageResult#FAIL} 293 * </ul> 294 */ onSendCompleted(int error)295 void onSendCompleted(int error); 296 } 297 298 /** 299 * Interface to get a list of available logical devices. 300 */ 301 interface DevicePollingCallback { 302 /** 303 * Called when device polling is finished. 304 * 305 * @param ackedAddress a list of logical addresses of available devices 306 */ onPollingFinished(List<Integer> ackedAddress)307 void onPollingFinished(List<Integer> ackedAddress); 308 } 309 310 private class HdmiControlBroadcastReceiver extends BroadcastReceiver { 311 @ServiceThreadOnly 312 @Override onReceive(Context context, Intent intent)313 public void onReceive(Context context, Intent intent) { 314 assertRunOnServiceThread(); 315 boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1"); 316 switch (intent.getAction()) { 317 case Intent.ACTION_SCREEN_OFF: 318 if (isPowerOnOrTransient() && !isReboot) { 319 onStandby(STANDBY_SCREEN_OFF); 320 } 321 break; 322 case Intent.ACTION_SCREEN_ON: 323 if (isPowerStandbyOrTransient()) { 324 onWakeUp(WAKE_UP_SCREEN_ON); 325 } 326 break; 327 case Intent.ACTION_CONFIGURATION_CHANGED: 328 String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault()); 329 if (!mMenuLanguage.equals(language)) { 330 onLanguageChanged(language); 331 } 332 break; 333 case Intent.ACTION_SHUTDOWN: 334 if (isPowerOnOrTransient() && !isReboot) { 335 onStandby(STANDBY_SHUTDOWN); 336 } 337 break; 338 } 339 } 340 341 } 342 343 // A thread to handle synchronous IO of CEC and MHL control service. 344 // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms) 345 // and sparse call it shares a thread to handle IO operations. 346 private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread"); 347 348 // Used to synchronize the access to the service. 349 private final Object mLock = new Object(); 350 351 // Type of CEC logical devices hosted in the system. Stored in the unmodifiable list. 352 private final List<Integer> mCecLocalDevices; 353 354 // List of records for HDMI control status change listener for death monitoring. 355 @GuardedBy("mLock") 356 private final ArrayList<HdmiControlStatusChangeListenerRecord> 357 mHdmiControlStatusChangeListenerRecords = new ArrayList<>(); 358 359 // List of records for HDMI control volume control status change listener for death monitoring. 360 @GuardedBy("mLock") 361 private final RemoteCallbackList<IHdmiCecVolumeControlFeatureListener> 362 mHdmiCecVolumeControlFeatureListenerRecords = new RemoteCallbackList<>(); 363 364 // List of records for hotplug event listener to handle the the caller killed in action. 365 @GuardedBy("mLock") 366 private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords = 367 new ArrayList<>(); 368 369 // List of records for device event listener to handle the caller killed in action. 370 @GuardedBy("mLock") 371 private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords = 372 new ArrayList<>(); 373 374 // List of records for vendor command listener to handle the caller killed in action. 375 @GuardedBy("mLock") 376 private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords = 377 new ArrayList<>(); 378 379 // List of records for CEC setting change listener to handle the caller killed in action. 380 @GuardedBy("mLock") 381 private final ArrayMap<String, RemoteCallbackList<IHdmiCecSettingChangeListener>> 382 mHdmiCecSettingChangeListenerRecords = new ArrayMap<>(); 383 384 @GuardedBy("mLock") 385 private InputChangeListenerRecord mInputChangeListenerRecord; 386 387 @GuardedBy("mLock") 388 private HdmiRecordListenerRecord mRecordListenerRecord; 389 390 // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol 391 // handling will be disabled and no request will be handled. 392 @GuardedBy("mLock") 393 @HdmiControlManager.HdmiCecControl 394 private int mHdmiControlEnabled; 395 396 // Set to true while the eARC feature is supported by the hardware on at least one port 397 // and the eARC HAL is present. 398 @GuardedBy("mLock") 399 @VisibleForTesting 400 private boolean mEarcSupported; 401 402 // Set to true while the eARC feature is enabled. 403 @GuardedBy("mLock") 404 private boolean mEarcEnabled; 405 406 private int mEarcPortId = -1; 407 408 // Set to true while the service is in normal mode. While set to false, no input change is 409 // allowed. Used for situations where input change can confuse users such as channel auto-scan, 410 // system upgrade, etc., a.k.a. "prohibit mode". 411 @GuardedBy("mLock") 412 private boolean mProhibitMode; 413 414 // List of records for system audio mode change to handle the the caller killed in action. 415 private final ArrayList<SystemAudioModeChangeListenerRecord> 416 mSystemAudioModeChangeListenerRecords = new ArrayList<>(); 417 418 // Handler used to run a task in service thread. 419 private final Handler mHandler = new Handler(); 420 421 private final SettingsObserver mSettingsObserver; 422 423 private final HdmiControlBroadcastReceiver 424 mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver(); 425 426 @Nullable 427 // Save callback when the device is still under logcial address allocation 428 // Invoke once new local device is ready. 429 private IHdmiControlCallback mDisplayStatusCallback = null; 430 431 @Nullable 432 // Save callback when the device is still under logcial address allocation 433 // Invoke once new local device is ready. 434 private IHdmiControlCallback mOtpCallbackPendingAddressAllocation = null; 435 436 @Nullable 437 private HdmiCecController mCecController; 438 439 private HdmiCecPowerStatusController mPowerStatusController; 440 441 @Nullable 442 private HdmiEarcController mEarcController; 443 444 @Nullable 445 private HdmiEarcLocalDevice mEarcLocalDevice; 446 447 @ServiceThreadOnly 448 private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault()); 449 450 @ServiceThreadOnly 451 private boolean mStandbyMessageReceived = false; 452 453 @ServiceThreadOnly 454 private boolean mWakeUpMessageReceived = false; 455 456 @ServiceThreadOnly 457 private boolean mSoundbarModeFeatureFlagEnabled = false; 458 459 @ServiceThreadOnly 460 private boolean mEarcTxFeatureFlagEnabled = false; 461 462 @ServiceThreadOnly 463 private boolean mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = false; 464 465 @ServiceThreadOnly 466 private boolean mTransitionFromArcToEarcTxEnabled = false; 467 468 @ServiceThreadOnly 469 private int mActivePortId = Constants.INVALID_PORT_ID; 470 471 // Set to true while the input change by MHL is allowed. 472 @GuardedBy("mLock") 473 private boolean mMhlInputChangeEnabled; 474 475 // List of records for MHL Vendor command listener to handle the caller killed in action. 476 @GuardedBy("mLock") 477 private final ArrayList<HdmiMhlVendorCommandListenerRecord> 478 mMhlVendorCommandListenerRecords = new ArrayList<>(); 479 480 @GuardedBy("mLock") 481 private List<HdmiDeviceInfo> mMhlDevices; 482 483 @Nullable 484 private HdmiMhlControllerStub mMhlController; 485 486 @Nullable 487 private TvInputManager mTvInputManager; 488 489 @Nullable 490 private DeviceConfigWrapper mDeviceConfig; 491 492 @Nullable 493 private PowerManagerWrapper mPowerManager; 494 495 @Nullable 496 private PowerManagerInternalWrapper mPowerManagerInternal; 497 498 @Nullable 499 private AudioManagerWrapper mAudioManager; 500 501 @Nullable 502 private AudioDeviceVolumeManagerWrapper mAudioDeviceVolumeManager; 503 504 @Nullable 505 private Looper mIoLooper; 506 507 @Nullable 508 private DisplayManager mDisplayManager; 509 510 @HdmiControlManager.HdmiCecVersion 511 private int mCecVersion; 512 513 // Last input port before switching to the MHL port. Should switch back to this port 514 // when the mobile device sends the request one touch play with off. 515 // Gets invalidated if we go to other port/input. 516 @ServiceThreadOnly 517 private int mLastInputMhl = Constants.INVALID_PORT_ID; 518 519 // Set to true if the logical address allocation is completed. 520 private boolean mAddressAllocated = false; 521 522 // Whether a CEC-enabled sink is connected to the playback device 523 private boolean mIsCecAvailable = false; 524 525 // Object that handles logging statsd atoms. 526 // Use getAtomWriter() instead of accessing directly, to allow dependency injection for testing. 527 private HdmiCecAtomWriter mAtomWriter = new HdmiCecAtomWriter(); 528 529 private CecMessageBuffer mCecMessageBuffer; 530 531 private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer(); 532 533 /** 534 * Constructor for testing. 535 * 536 * Takes fakes for AudioManager and AudioDeviceVolumeManager. 537 * 538 * This is especially important for AudioDeviceVolumeManager because a normally instantiated 539 * AudioDeviceVolumeManager can access the "real" AudioService on the DUT. 540 */ HdmiControlService(Context context, List<Integer> deviceTypes, AudioManagerWrapper audioManager, AudioDeviceVolumeManagerWrapper audioDeviceVolumeManager)541 @VisibleForTesting HdmiControlService(Context context, List<Integer> deviceTypes, 542 AudioManagerWrapper audioManager, 543 AudioDeviceVolumeManagerWrapper audioDeviceVolumeManager) { 544 super(context); 545 mCecLocalDevices = deviceTypes; 546 mSettingsObserver = new SettingsObserver(mHandler); 547 mHdmiCecConfig = new HdmiCecConfig(context); 548 mDeviceConfig = new DeviceConfigWrapper(); 549 mAudioManager = audioManager; 550 mAudioDeviceVolumeManager = audioDeviceVolumeManager; 551 } 552 HdmiControlService(Context context)553 public HdmiControlService(Context context) { 554 super(context); 555 mCecLocalDevices = readDeviceTypes(); 556 mSettingsObserver = new SettingsObserver(mHandler); 557 mHdmiCecConfig = new HdmiCecConfig(context); 558 mDeviceConfig = new DeviceConfigWrapper(); 559 } 560 561 @VisibleForTesting getCecDeviceTypes()562 protected List<HdmiProperties.cec_device_types_values> getCecDeviceTypes() { 563 return HdmiProperties.cec_device_types(); 564 } 565 566 @VisibleForTesting getDeviceTypes()567 protected List<Integer> getDeviceTypes() { 568 return HdmiProperties.device_type(); 569 } 570 571 /** 572 * Extracts a list of integer device types from the sysprop ro.hdmi.cec_device_types. 573 * If ro.hdmi.cec_device_types is not set, reads from ro.hdmi.device.type instead. 574 * @return the list of integer device types 575 */ 576 @VisibleForTesting readDeviceTypes()577 protected List<Integer> readDeviceTypes() { 578 List<HdmiProperties.cec_device_types_values> cecDeviceTypes = getCecDeviceTypes(); 579 if (!cecDeviceTypes.isEmpty()) { 580 if (cecDeviceTypes.contains(null)) { 581 Slog.w(TAG, "Error parsing ro.hdmi.cec_device_types: " + SystemProperties.get( 582 "ro.hdmi.cec_device_types")); 583 } 584 return cecDeviceTypes.stream() 585 .map(HdmiControlService::enumToIntDeviceType) 586 .filter(Objects::nonNull) 587 .collect(Collectors.toList()); 588 } else { 589 // If ro.hdmi.cec_device_types isn't set, fall back to reading ro.hdmi.device_type 590 List<Integer> deviceTypes = getDeviceTypes(); 591 if (deviceTypes.contains(null)) { 592 Slog.w(TAG, "Error parsing ro.hdmi.device_type: " + SystemProperties.get( 593 "ro.hdmi.device_type")); 594 } 595 return deviceTypes.stream() 596 .filter(Objects::nonNull) 597 .collect(Collectors.toList()); 598 } 599 } 600 601 /** 602 * Converts an enum representing a value in ro.hdmi.cec_device_types to an integer device type. 603 * Returns null if the input is null or an unrecognized device type. 604 */ 605 @Nullable enumToIntDeviceType( @ullable HdmiProperties.cec_device_types_values cecDeviceType)606 private static Integer enumToIntDeviceType( 607 @Nullable HdmiProperties.cec_device_types_values cecDeviceType) { 608 if (cecDeviceType == null) { 609 return null; 610 } 611 switch (cecDeviceType) { 612 case TV: 613 return HdmiDeviceInfo.DEVICE_TV; 614 case RECORDING_DEVICE: 615 return HdmiDeviceInfo.DEVICE_RECORDER; 616 case RESERVED: 617 return HdmiDeviceInfo.DEVICE_RESERVED; 618 case TUNER: 619 return HdmiDeviceInfo.DEVICE_TUNER; 620 case PLAYBACK_DEVICE: 621 return HdmiDeviceInfo.DEVICE_PLAYBACK; 622 case AUDIO_SYSTEM: 623 return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; 624 case PURE_CEC_SWITCH: 625 return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; 626 case VIDEO_PROCESSOR: 627 return HdmiDeviceInfo.DEVICE_VIDEO_PROCESSOR; 628 default: 629 Slog.w(TAG, "Unrecognized device type in ro.hdmi.cec_device_types: " 630 + cecDeviceType.getPropValue()); 631 return null; 632 } 633 } 634 getIntList(String string)635 protected static List<Integer> getIntList(String string) { 636 ArrayList<Integer> list = new ArrayList<>(); 637 TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 638 splitter.setString(string); 639 for (String item : splitter) { 640 try { 641 list.add(Integer.parseInt(item)); 642 } catch (NumberFormatException e) { 643 Slog.w(TAG, "Can't parseInt: " + item); 644 } 645 } 646 return Collections.unmodifiableList(list); 647 } 648 649 @Override onStart()650 public void onStart() { 651 initService(); 652 publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService()); 653 654 if (mCecController != null) { 655 // Register broadcast receiver for power state change. 656 IntentFilter filter = new IntentFilter(); 657 filter.addAction(Intent.ACTION_SCREEN_OFF); 658 filter.addAction(Intent.ACTION_SCREEN_ON); 659 filter.addAction(Intent.ACTION_SHUTDOWN); 660 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); 661 getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter); 662 663 // Register ContentObserver to monitor the settings change. 664 registerContentObserver(); 665 } 666 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED); 667 } 668 669 @VisibleForTesting initService()670 void initService() { 671 if (mIoLooper == null) { 672 mIoThread.start(); 673 mIoLooper = mIoThread.getLooper(); 674 } 675 676 if (mPowerStatusController == null) { 677 mPowerStatusController = new HdmiCecPowerStatusController(this); 678 } 679 mPowerStatusController.setPowerStatus(getInitialPowerStatus()); 680 setProhibitMode(false); 681 mHdmiControlEnabled = mHdmiCecConfig.getIntValue( 682 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); 683 684 mSoundbarModeFeatureFlagEnabled = mDeviceConfig.getBoolean( 685 Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false); 686 mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean( 687 Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false); 688 mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean( 689 Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false); 690 mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = mDeviceConfig.getBoolean( 691 Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, false); 692 693 synchronized (mLock) { 694 mEarcEnabled = (mHdmiCecConfig.getIntValue( 695 HdmiControlManager.SETTING_NAME_EARC_ENABLED) == EARC_FEATURE_ENABLED); 696 if (isTvDevice()) { 697 mEarcEnabled &= mEarcTxFeatureFlagEnabled; 698 } 699 } 700 setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue( 701 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); 702 mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true); 703 704 if (mCecMessageBuffer == null) { 705 mCecMessageBuffer = new CecMessageBuffer(this); 706 } 707 if (mCecController == null) { 708 mCecController = HdmiCecController.create(this, getAtomWriter()); 709 } 710 if (mCecController == null) { 711 Slog.i(TAG, "Device does not support HDMI-CEC."); 712 return; 713 } 714 if (mMhlController == null) { 715 mMhlController = HdmiMhlControllerStub.create(this); 716 } 717 if (!mMhlController.isReady()) { 718 Slog.i(TAG, "Device does not support MHL-control."); 719 } 720 if (mEarcController == null) { 721 mEarcController = HdmiEarcController.create(this); 722 } 723 if (mEarcController == null) { 724 Slog.i(TAG, "Device does not support eARC."); 725 } 726 mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController); 727 if (isCecControlEnabled()) { 728 initializeCec(INITIATED_BY_BOOT_UP); 729 } else { 730 mCecController.enableCec(false); 731 } 732 733 synchronized (mLock) { 734 mMhlDevices = Collections.emptyList(); 735 } 736 737 mHdmiCecNetwork.initPortInfo(); 738 List<HdmiPortInfo> ports = getPortInfo(); 739 synchronized (mLock) { 740 mEarcSupported = false; 741 for (HdmiPortInfo port : ports) { 742 boolean earcSupportedOnPort = port.isEarcSupported(); 743 if (earcSupportedOnPort && mEarcSupported) { 744 // This means that more than 1 port supports eARC. 745 // The HDMI specification only allows 1 active eARC connection. 746 // Android does not support devices with multiple eARC-enabled ports. 747 // Consider eARC not supported in this case. 748 Slog.e(TAG, "HDMI eARC supported on more than 1 port."); 749 mEarcSupported = false; 750 mEarcPortId = -1; 751 break; 752 } else if (earcSupportedOnPort) { 753 mEarcPortId = port.getId(); 754 mEarcSupported = earcSupportedOnPort; 755 } 756 } 757 mEarcSupported &= (mEarcController != null); 758 } 759 if (isEarcSupported()) { 760 if (isEarcEnabled()) { 761 initializeEarc(INITIATED_BY_BOOT_UP); 762 } else { 763 setEarcEnabledInHal(false, false); 764 } 765 } 766 767 mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED, 768 new HdmiCecConfig.SettingChangeListener() { 769 @Override 770 public void onChange(String setting) { 771 @HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue( 772 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); 773 setCecEnabled(enabled); 774 } 775 }, mServiceThreadExecutor); 776 mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION, 777 new HdmiCecConfig.SettingChangeListener() { 778 @Override 779 public void onChange(String setting) { 780 initializeCec(INITIATED_BY_ENABLE_CEC); 781 } 782 }, mServiceThreadExecutor); 783 mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL, 784 new HdmiCecConfig.SettingChangeListener() { 785 @Override 786 public void onChange(String setting) { 787 boolean enabled = mHdmiCecConfig.getIntValue( 788 HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL) 789 == HdmiControlManager.ROUTING_CONTROL_ENABLED; 790 if (isAudioSystemDevice()) { 791 if (audioSystem() == null) { 792 Slog.w(TAG, "Switch device has not registered yet." 793 + " Can't turn routing on."); 794 } else { 795 audioSystem().setRoutingControlFeatureEnabled(enabled); 796 } 797 } 798 } 799 }, mServiceThreadExecutor); 800 mHdmiCecConfig.registerChangeListener( 801 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL, 802 new HdmiCecConfig.SettingChangeListener() { 803 @Override 804 public void onChange(String setting) { 805 boolean enabled = mHdmiCecConfig.getIntValue( 806 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL) 807 == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED; 808 if (isTvDeviceEnabled()) { 809 tv().setSystemAudioControlFeatureEnabled(enabled); 810 } 811 if (isAudioSystemDevice()) { 812 if (audioSystem() == null) { 813 Slog.e(TAG, "Audio System device has not registered yet." 814 + " Can't turn system audio mode on."); 815 } else { 816 audioSystem().onSystemAudioControlFeatureSupportChanged(enabled); 817 } 818 } 819 } 820 }, mServiceThreadExecutor); 821 mHdmiCecConfig.registerChangeListener( 822 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE, 823 new HdmiCecConfig.SettingChangeListener() { 824 @Override 825 public void onChange(String setting) { 826 setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue( 827 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); 828 } 829 }, mServiceThreadExecutor); 830 mHdmiCecConfig.registerChangeListener( 831 HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, 832 new HdmiCecConfig.SettingChangeListener() { 833 @Override 834 public void onChange(String setting) { 835 if (isTvDeviceEnabled()) { 836 mCecController.enableWakeupByOtp(tv().getAutoWakeup()); 837 } 838 } 839 }, mServiceThreadExecutor); 840 841 if (isTvDevice()) { 842 mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(), 843 new DeviceConfig.OnPropertiesChangedListener() { 844 @Override 845 public void onPropertiesChanged(DeviceConfig.Properties properties) { 846 mEarcTxFeatureFlagEnabled = properties.getBoolean( 847 Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, 848 false); 849 boolean earcEnabledSetting = mHdmiCecConfig.getIntValue( 850 HdmiControlManager.SETTING_NAME_EARC_ENABLED) 851 == EARC_FEATURE_ENABLED; 852 setEarcEnabled(earcEnabledSetting && mEarcTxFeatureFlagEnabled 853 ? EARC_FEATURE_ENABLED : EARC_FEATURE_DISABLED); 854 } 855 }); 856 } 857 858 mHdmiCecConfig.registerChangeListener(HdmiControlManager.SETTING_NAME_EARC_ENABLED, 859 new HdmiCecConfig.SettingChangeListener() { 860 @Override 861 public void onChange(String setting) { 862 if (isTvDevice()) { 863 boolean earcEnabledSetting = mHdmiCecConfig.getIntValue( 864 HdmiControlManager.SETTING_NAME_EARC_ENABLED) 865 == EARC_FEATURE_ENABLED; 866 setEarcEnabled(earcEnabledSetting && mEarcTxFeatureFlagEnabled 867 ? EARC_FEATURE_ENABLED : EARC_FEATURE_DISABLED); 868 } else { 869 setEarcEnabled(mHdmiCecConfig.getIntValue( 870 HdmiControlManager.SETTING_NAME_EARC_ENABLED)); 871 } 872 } 873 }, 874 mServiceThreadExecutor); 875 876 mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(), 877 new DeviceConfig.OnPropertiesChangedListener() { 878 @Override 879 public void onPropertiesChanged(DeviceConfig.Properties properties) { 880 mSoundbarModeFeatureFlagEnabled = properties.getBoolean( 881 Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, 882 false); 883 boolean soundbarModeSetting = mHdmiCecConfig.getIntValue( 884 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE) 885 == SOUNDBAR_MODE_ENABLED; 886 setSoundbarMode(soundbarModeSetting && mSoundbarModeFeatureFlagEnabled 887 ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED); 888 } 889 }); 890 mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE, 891 new HdmiCecConfig.SettingChangeListener() { 892 @Override 893 public void onChange(String setting) { 894 boolean soundbarModeSetting = mHdmiCecConfig.getIntValue( 895 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE) 896 == SOUNDBAR_MODE_ENABLED; 897 setSoundbarMode(soundbarModeSetting && mSoundbarModeFeatureFlagEnabled 898 ? SOUNDBAR_MODE_ENABLED : SOUNDBAR_MODE_DISABLED); 899 } 900 }, mServiceThreadExecutor); 901 902 mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(), 903 new DeviceConfig.OnPropertiesChangedListener() { 904 @Override 905 public void onPropertiesChanged(DeviceConfig.Properties properties) { 906 mTransitionFromArcToEarcTxEnabled = properties.getBoolean( 907 Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, 908 false); 909 } 910 }); 911 912 mDeviceConfig.addOnPropertiesChangedListener(getContext().getMainExecutor(), 913 new DeviceConfig.OnPropertiesChangedListener() { 914 @Override 915 public void onPropertiesChanged(DeviceConfig.Properties properties) { 916 mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = properties.getBoolean( 917 Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, 918 false); 919 checkAndUpdateAbsoluteVolumeBehavior(); 920 } 921 }); 922 } 923 /** Returns true if the device screen is off */ isScreenOff()924 boolean isScreenOff() { 925 return mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_OFF; 926 } 927 bootCompleted()928 private void bootCompleted() { 929 // on boot, if device is interactive, set HDMI CEC state as powered on as well 930 if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) { 931 mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_ON); 932 // Start all actions that were queued because the device was in standby 933 if (mAddressAllocated) { 934 for (HdmiCecLocalDevice localDevice : getAllCecLocalDevices()) { 935 localDevice.startQueuedActions(); 936 } 937 } 938 } 939 } 940 941 /** 942 * Returns the initial power status used when the HdmiControlService starts. 943 */ 944 @VisibleForTesting getInitialPowerStatus()945 int getInitialPowerStatus() { 946 // The initial power status is POWER_STATUS_TRANSIENT_TO_STANDBY. 947 // Once boot completes the service transitions to POWER_STATUS_ON if the device is 948 // interactive. 949 // Quiescent boot is a special boot mode, in which the screen stays off during boot 950 // and the device goes to sleep after boot has finished. 951 // We don't transition to POWER_STATUS_ON initially, as we might be booting in quiescent 952 // mode, during which we don't want to appear powered on to avoid being made active source. 953 return HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY; 954 } 955 956 @VisibleForTesting setCecController(HdmiCecController cecController)957 void setCecController(HdmiCecController cecController) { 958 mCecController = cecController; 959 } 960 961 @VisibleForTesting setEarcController(HdmiEarcController earcController)962 void setEarcController(HdmiEarcController earcController) { 963 mEarcController = earcController; 964 } 965 966 @VisibleForTesting setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork)967 void setHdmiCecNetwork(HdmiCecNetwork hdmiCecNetwork) { 968 mHdmiCecNetwork = hdmiCecNetwork; 969 } 970 971 @VisibleForTesting setHdmiCecConfig(HdmiCecConfig hdmiCecConfig)972 void setHdmiCecConfig(HdmiCecConfig hdmiCecConfig) { 973 mHdmiCecConfig = hdmiCecConfig; 974 } 975 getHdmiCecNetwork()976 public HdmiCecNetwork getHdmiCecNetwork() { 977 return mHdmiCecNetwork; 978 } 979 980 @VisibleForTesting setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController)981 void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) { 982 mMhlController = hdmiMhlController; 983 } 984 985 @Override onBootPhase(int phase)986 public void onBootPhase(int phase) { 987 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 988 mDisplayManager = getContext().getSystemService(DisplayManager.class); 989 mTvInputManager = (TvInputManager) getContext().getSystemService( 990 Context.TV_INPUT_SERVICE); 991 mPowerManager = new PowerManagerWrapper(getContext()); 992 mPowerManagerInternal = new PowerManagerInternalWrapper(); 993 if (mAudioManager == null) { 994 mAudioManager = new DefaultAudioManagerWrapper(getContext()); 995 } 996 mStreamMusicMaxVolume = getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC); 997 if (mAudioDeviceVolumeManager == null) { 998 mAudioDeviceVolumeManager = 999 new DefaultAudioDeviceVolumeManagerWrapper(getContext()); 1000 } 1001 getAudioDeviceVolumeManager().addOnDeviceVolumeBehaviorChangedListener( 1002 mServiceThreadExecutor, this::onDeviceVolumeBehaviorChanged); 1003 } else if (phase == SystemService.PHASE_BOOT_COMPLETED) { 1004 runOnServiceThread(this::bootCompleted); 1005 } 1006 } 1007 getTvInputManager()1008 TvInputManager getTvInputManager() { 1009 return mTvInputManager; 1010 } 1011 registerTvInputCallback(TvInputCallback callback)1012 void registerTvInputCallback(TvInputCallback callback) { 1013 if (mTvInputManager == null) return; 1014 mTvInputManager.registerCallback(callback, mHandler); 1015 } 1016 unregisterTvInputCallback(TvInputCallback callback)1017 void unregisterTvInputCallback(TvInputCallback callback) { 1018 if (mTvInputManager == null) return; 1019 mTvInputManager.unregisterCallback(callback); 1020 } 1021 1022 @VisibleForTesting setDeviceConfig(DeviceConfigWrapper deviceConfig)1023 void setDeviceConfig(DeviceConfigWrapper deviceConfig) { 1024 mDeviceConfig = deviceConfig; 1025 } 1026 1027 @VisibleForTesting setPowerManager(PowerManagerWrapper powerManager)1028 void setPowerManager(PowerManagerWrapper powerManager) { 1029 mPowerManager = powerManager; 1030 } 1031 1032 @VisibleForTesting setPowerManagerInternal(PowerManagerInternalWrapper powerManagerInternal)1033 void setPowerManagerInternal(PowerManagerInternalWrapper powerManagerInternal) { 1034 mPowerManagerInternal = powerManagerInternal; 1035 } 1036 getDeviceConfig()1037 DeviceConfigWrapper getDeviceConfig() { 1038 return mDeviceConfig; 1039 } 1040 getPowerManager()1041 PowerManagerWrapper getPowerManager() { 1042 return mPowerManager; 1043 } 1044 getPowerManagerInternal()1045 PowerManagerInternalWrapper getPowerManagerInternal() { 1046 return mPowerManagerInternal; 1047 } 1048 1049 /** 1050 * Triggers the address allocation that states the presence of a local device audio system in 1051 * the network. 1052 */ 1053 @VisibleForTesting setSoundbarMode(final int settingValue)1054 public void setSoundbarMode(final int settingValue) { 1055 HdmiCecLocalDevicePlayback playback = playback(); 1056 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 1057 if (playback == null) { 1058 Slog.w(TAG, "Device type not compatible to change soundbar mode."); 1059 return; 1060 } 1061 if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { 1062 Slog.w(TAG, "Device type doesn't support ARC."); 1063 return; 1064 } 1065 boolean isArcEnabled = false; 1066 if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) { 1067 isArcEnabled = audioSystem.isArcEnabled(); 1068 if (isSystemAudioActivated()) { 1069 audioSystem.terminateSystemAudioMode(); 1070 } 1071 if (isArcEnabled) { 1072 if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) { 1073 audioSystem.removeAction(ArcTerminationActionFromAvr.class); 1074 } 1075 audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem, 1076 new IHdmiControlCallback.Stub() { 1077 @Override 1078 public void onComplete(int result) { 1079 mAddressAllocated = false; 1080 initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); 1081 } 1082 })); 1083 } 1084 } 1085 if (!isArcEnabled) { 1086 mAddressAllocated = false; 1087 initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE); 1088 } 1089 } 1090 1091 /** 1092 * Checks if the Device Discovery is handled by the local device playback. 1093 * See {@link HdmiCecLocalDeviceAudioSystem#launchDeviceDiscovery}. 1094 */ isDeviceDiscoveryHandledByPlayback()1095 public boolean isDeviceDiscoveryHandledByPlayback() { 1096 HdmiCecLocalDevicePlayback playback = playback(); 1097 if (playback != null && (playback.hasAction(DeviceDiscoveryAction.class) 1098 || playback.hasAction(HotplugDetectionAction.class))) { 1099 return true; 1100 } 1101 return false; 1102 } 1103 1104 /** 1105 * Called when the initialization of local devices is complete. 1106 */ onInitializeCecComplete(int initiatedBy)1107 private void onInitializeCecComplete(int initiatedBy) { 1108 updatePowerStatusOnInitializeCecComplete(); 1109 mWakeUpMessageReceived = false; 1110 1111 if (isTvDeviceEnabled()) { 1112 mCecController.enableWakeupByOtp(tv().getAutoWakeup()); 1113 } 1114 int reason = -1; 1115 switch (initiatedBy) { 1116 case INITIATED_BY_BOOT_UP: 1117 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START; 1118 break; 1119 case INITIATED_BY_ENABLE_CEC: 1120 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING; 1121 break; 1122 case INITIATED_BY_SCREEN_ON: 1123 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP; 1124 final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices(); 1125 for (HdmiCecLocalDevice device : devices) { 1126 device.onInitializeCecComplete(initiatedBy); 1127 } 1128 break; 1129 case INITIATED_BY_WAKE_UP_MESSAGE: 1130 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP; 1131 break; 1132 } 1133 if (reason != -1) { 1134 invokeVendorCommandListenersOnControlStateChanged(true, reason); 1135 announceHdmiControlStatusChange(HDMI_CEC_CONTROL_ENABLED); 1136 } 1137 } 1138 1139 /** 1140 * Updates the power status once the initialization of local devices is complete. 1141 */ updatePowerStatusOnInitializeCecComplete()1142 private void updatePowerStatusOnInitializeCecComplete() { 1143 if (mPowerStatusController.isPowerStatusTransientToOn()) { 1144 mHandler.post(() -> mPowerStatusController.setPowerStatus( 1145 HdmiControlManager.POWER_STATUS_ON)); 1146 } else if (mPowerStatusController.isPowerStatusTransientToStandby()) { 1147 mHandler.post(() -> mPowerStatusController.setPowerStatus( 1148 HdmiControlManager.POWER_STATUS_STANDBY)); 1149 } 1150 } 1151 registerContentObserver()1152 private void registerContentObserver() { 1153 ContentResolver resolver = getContext().getContentResolver(); 1154 String[] settings = new String[] { 1155 Global.MHL_INPUT_SWITCHING_ENABLED, 1156 Global.MHL_POWER_CHARGE_ENABLED, 1157 Global.DEVICE_NAME 1158 }; 1159 for (String s : settings) { 1160 resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver, 1161 UserHandle.USER_ALL); 1162 } 1163 } 1164 1165 private class SettingsObserver extends ContentObserver { SettingsObserver(Handler handler)1166 public SettingsObserver(Handler handler) { 1167 super(handler); 1168 } 1169 1170 // onChange is set up to run in service thread. 1171 @Override onChange(boolean selfChange, Uri uri)1172 public void onChange(boolean selfChange, Uri uri) { 1173 String option = uri.getLastPathSegment(); 1174 boolean enabled = readBooleanSetting(option, true); 1175 switch (option) { 1176 case Global.MHL_INPUT_SWITCHING_ENABLED: 1177 setMhlInputChangeEnabled(enabled); 1178 break; 1179 case Global.MHL_POWER_CHARGE_ENABLED: 1180 mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled)); 1181 break; 1182 case Global.DEVICE_NAME: 1183 String deviceName = readStringSetting(option, Build.MODEL); 1184 setDisplayName(deviceName); 1185 break; 1186 } 1187 } 1188 } 1189 toInt(boolean enabled)1190 private static int toInt(boolean enabled) { 1191 return enabled ? ENABLED : DISABLED; 1192 } 1193 1194 @VisibleForTesting readBooleanSetting(String key, boolean defVal)1195 boolean readBooleanSetting(String key, boolean defVal) { 1196 ContentResolver cr = getContext().getContentResolver(); 1197 return Global.getInt(cr, key, toInt(defVal)) == ENABLED; 1198 } 1199 1200 @VisibleForTesting readIntSetting(String key, int defVal)1201 int readIntSetting(String key, int defVal) { 1202 ContentResolver cr = getContext().getContentResolver(); 1203 return Global.getInt(cr, key, defVal); 1204 } 1205 writeBooleanSetting(String key, boolean value)1206 void writeBooleanSetting(String key, boolean value) { 1207 ContentResolver cr = getContext().getContentResolver(); 1208 Global.putInt(cr, key, toInt(value)); 1209 } 1210 1211 @VisibleForTesting writeStringSystemProperty(String key, String value)1212 protected void writeStringSystemProperty(String key, String value) { 1213 SystemProperties.set(key, value); 1214 } 1215 1216 @VisibleForTesting readBooleanSystemProperty(String key, boolean defVal)1217 boolean readBooleanSystemProperty(String key, boolean defVal) { 1218 return SystemProperties.getBoolean(key, defVal); 1219 } 1220 readStringSetting(String key, String defVal)1221 String readStringSetting(String key, String defVal) { 1222 ContentResolver cr = getContext().getContentResolver(); 1223 String content = Global.getString(cr, key); 1224 if (TextUtils.isEmpty(content)) { 1225 return defVal; 1226 } 1227 return content; 1228 } 1229 writeStringSetting(String key, String value)1230 void writeStringSetting(String key, String value) { 1231 ContentResolver cr = getContext().getContentResolver(); 1232 Global.putString(cr, key, value); 1233 } 1234 initializeCec(int initiatedBy)1235 private void initializeCec(int initiatedBy) { 1236 mAddressAllocated = false; 1237 int settingsCecVersion = getHdmiCecConfig().getIntValue( 1238 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION); 1239 int supportedCecVersion = mCecController.getVersion(); 1240 1241 // Limit the used CEC version to the highest supported version by HAL and selected 1242 // version in settings (but at least v1.4b). 1243 mCecVersion = Math.max(HdmiControlManager.HDMI_CEC_VERSION_1_4_B, 1244 Math.min(settingsCecVersion, supportedCecVersion)); 1245 1246 mCecController.enableSystemCecControl(true); 1247 mCecController.setLanguage(mMenuLanguage); 1248 initializeCecLocalDevices(initiatedBy); 1249 } 1250 1251 /** 1252 * If the Soundbar mode is turned on, adds the local device type audio system in the list of 1253 * local devices types. This method is called when the local devices are initialized such that 1254 * the list of local devices is in sync with the Soundbar mode setting. 1255 * @return the list of integer device types 1256 */ 1257 @ServiceThreadOnly getCecLocalDeviceTypes()1258 private List<Integer> getCecLocalDeviceTypes() { 1259 ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices); 1260 if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE) 1261 == SOUNDBAR_MODE_ENABLED 1262 && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) 1263 && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true) 1264 && mSoundbarModeFeatureFlagEnabled) { 1265 allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 1266 } 1267 return allLocalDeviceTypes; 1268 } 1269 1270 @ServiceThreadOnly 1271 @VisibleForTesting initializeCecLocalDevices(final int initiatedBy)1272 protected void initializeCecLocalDevices(final int initiatedBy) { 1273 assertRunOnServiceThread(); 1274 // A container for [Device type, Local device info]. 1275 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 1276 for (int type : getCecLocalDeviceTypes()) { 1277 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); 1278 if (localDevice == null) { 1279 localDevice = HdmiCecLocalDevice.create(this, type); 1280 } 1281 localDevice.init(); 1282 localDevices.add(localDevice); 1283 } 1284 // It's now safe to flush existing local devices from mCecController since they were 1285 // already moved to 'localDevices'. 1286 clearCecLocalDevices(); 1287 mHdmiCecNetwork.clearDeviceList(); 1288 allocateLogicalAddress(localDevices, initiatedBy); 1289 } 1290 1291 @ServiceThreadOnly 1292 @VisibleForTesting allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, final int initiatedBy)1293 protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, 1294 final int initiatedBy) { 1295 assertRunOnServiceThread(); 1296 mCecController.clearLogicalAddress(); 1297 final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>(); 1298 final int[] finished = new int[1]; 1299 mAddressAllocated = allocatingDevices.isEmpty(); 1300 1301 // For TV device, select request can be invoked while address allocation or device 1302 // discovery is in progress. Initialize the request here at the start of allocation, 1303 // and process the collected requests later when the allocation and device discovery 1304 // is all completed. 1305 mSelectRequestBuffer.clear(); 1306 1307 for (final HdmiCecLocalDevice localDevice : allocatingDevices) { 1308 mCecController.allocateLogicalAddress(localDevice.getType(), 1309 localDevice.getPreferredAddress(), new AllocateAddressCallback() { 1310 @Override 1311 public void onAllocated(int deviceType, int logicalAddress) { 1312 if (logicalAddress == Constants.ADDR_UNREGISTERED) { 1313 Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType 1314 + "]"); 1315 } else { 1316 // Set POWER_STATUS_ON to all local devices because they share 1317 // lifetime 1318 // with system. 1319 HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, 1320 deviceType, 1321 HdmiControlManager.POWER_STATUS_ON, getCecVersion()); 1322 localDevice.setDeviceInfo(deviceInfo); 1323 mHdmiCecNetwork.addLocalDevice(deviceType, localDevice); 1324 mHdmiCecNetwork.addCecDevice(localDevice.getDeviceInfo()); 1325 mCecController.addLogicalAddress(logicalAddress); 1326 allocatedDevices.add(localDevice); 1327 } 1328 1329 // Address allocation completed for all devices. Notify each device. 1330 if (allocatingDevices.size() == ++finished[0]) { 1331 if (initiatedBy != INITIATED_BY_HOTPLUG 1332 && initiatedBy != INITIATED_BY_SOUNDBAR_MODE) { 1333 // In case of the hotplug or soundbar mode setting toggle 1334 // we don't call onInitializeCecComplete() 1335 // since we reallocate the logical address only. 1336 onInitializeCecComplete(initiatedBy); 1337 } 1338 mAddressAllocated = true; 1339 notifyAddressAllocated(allocatedDevices, initiatedBy); 1340 // Reinvoke the saved display status callback once the local 1341 // device is ready. 1342 if (mDisplayStatusCallback != null) { 1343 queryDisplayStatus(mDisplayStatusCallback); 1344 mDisplayStatusCallback = null; 1345 } 1346 if (mOtpCallbackPendingAddressAllocation != null) { 1347 oneTouchPlay(mOtpCallbackPendingAddressAllocation); 1348 mOtpCallbackPendingAddressAllocation = null; 1349 } 1350 mCecMessageBuffer.processMessages(); 1351 } 1352 } 1353 }); 1354 } 1355 } 1356 1357 @ServiceThreadOnly notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy)1358 private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) { 1359 assertRunOnServiceThread(); 1360 List<HdmiCecMessage> bufferedMessages = mCecMessageBuffer.getBuffer(); 1361 for (HdmiCecLocalDevice device : devices) { 1362 int address = device.getDeviceInfo().getLogicalAddress(); 1363 device.handleAddressAllocated(address, bufferedMessages, initiatedBy); 1364 } 1365 if (isTvDeviceEnabled()) { 1366 tv().setSelectRequestBuffer(mSelectRequestBuffer); 1367 } 1368 } 1369 isAddressAllocated()1370 boolean isAddressAllocated() { 1371 return mAddressAllocated; 1372 } 1373 getPortInfo()1374 List<HdmiPortInfo> getPortInfo() { 1375 synchronized (mLock) { 1376 return mHdmiCecNetwork.getPortInfo(); 1377 } 1378 } 1379 getPortInfo(int portId)1380 HdmiPortInfo getPortInfo(int portId) { 1381 return mHdmiCecNetwork.getPortInfo(portId); 1382 } 1383 1384 /** 1385 * Returns the routing path (physical address) of the HDMI port for the given 1386 * port id. 1387 */ portIdToPath(int portId)1388 int portIdToPath(int portId) { 1389 return mHdmiCecNetwork.portIdToPath(portId); 1390 } 1391 1392 /** 1393 * Returns the id of HDMI port located at the current device that runs this method. 1394 * 1395 * For TV with physical address 0x0000, target device 0x1120, we want port physical address 1396 * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address 1397 * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. 1398 * 1399 * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. 1400 * 1401 * @param path the target device's physical address. 1402 * @return the id of the port that the target device eventually connects to 1403 * on the current device. 1404 */ pathToPortId(int path)1405 int pathToPortId(int path) { 1406 return mHdmiCecNetwork.physicalAddressToPortId(path); 1407 } 1408 isValidPortId(int portId)1409 boolean isValidPortId(int portId) { 1410 return mHdmiCecNetwork.getPortInfo(portId) != null; 1411 } 1412 1413 /** 1414 * Returns {@link Looper} for IO operation. 1415 */ 1416 @Nullable 1417 @VisibleForTesting getIoLooper()1418 protected Looper getIoLooper() { 1419 return mIoLooper; 1420 } 1421 1422 @VisibleForTesting setIoLooper(Looper ioLooper)1423 void setIoLooper(Looper ioLooper) { 1424 mIoLooper = ioLooper; 1425 } 1426 1427 @VisibleForTesting setCecMessageBuffer(CecMessageBuffer cecMessageBuffer)1428 void setCecMessageBuffer(CecMessageBuffer cecMessageBuffer) { 1429 this.mCecMessageBuffer = cecMessageBuffer; 1430 } 1431 1432 /** 1433 * Returns {@link Looper} of main thread. Use this {@link Looper} instance 1434 * for tasks that are running on main service thread. 1435 */ getServiceLooper()1436 protected Looper getServiceLooper() { 1437 return mHandler.getLooper(); 1438 } 1439 1440 /** 1441 * Returns physical address of the device. 1442 */ getPhysicalAddress()1443 int getPhysicalAddress() { 1444 return mCecController.getPhysicalAddress(); 1445 } 1446 1447 /** 1448 * Returns vendor id of CEC service. 1449 */ getVendorId()1450 int getVendorId() { 1451 return mCecController.getVendorId(); 1452 } 1453 1454 @Nullable 1455 @ServiceThreadOnly getDeviceInfo(int logicalAddress)1456 HdmiDeviceInfo getDeviceInfo(int logicalAddress) { 1457 assertRunOnServiceThread(); 1458 return mHdmiCecNetwork.getCecDeviceInfo(logicalAddress); 1459 } 1460 1461 @ServiceThreadOnly getDeviceInfoByPort(int port)1462 HdmiDeviceInfo getDeviceInfoByPort(int port) { 1463 assertRunOnServiceThread(); 1464 HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port); 1465 if (info != null) { 1466 return info.getInfo(); 1467 } 1468 return null; 1469 } 1470 1471 /** 1472 * Returns version of CEC. 1473 */ 1474 @VisibleForTesting 1475 @HdmiControlManager.HdmiCecVersion getCecVersion()1476 protected int getCecVersion() { 1477 return mCecVersion; 1478 } 1479 1480 /** 1481 * Whether a device of the specified physical address is connected to ARC enabled port. 1482 */ isConnectedToArcPort(int physicalAddress)1483 boolean isConnectedToArcPort(int physicalAddress) { 1484 return mHdmiCecNetwork.isConnectedToArcPort(physicalAddress); 1485 } 1486 1487 @ServiceThreadOnly isConnected(int portId)1488 boolean isConnected(int portId) { 1489 assertRunOnServiceThread(); 1490 return mCecController.isConnected(portId); 1491 } 1492 1493 /** 1494 * Executes a Runnable on the service thread. 1495 * During execution, sets the work source UID to the parent's work source UID. 1496 * 1497 * @param runnable The runnable to execute on the service thread 1498 */ runOnServiceThread(Runnable runnable)1499 void runOnServiceThread(Runnable runnable) { 1500 mHandler.post(new WorkSourceUidPreservingRunnable(runnable)); 1501 } 1502 assertRunOnServiceThread()1503 private void assertRunOnServiceThread() { 1504 if (Looper.myLooper() != mHandler.getLooper()) { 1505 throw new IllegalStateException("Should run on service thread."); 1506 } 1507 } 1508 1509 /** 1510 * Transmit a CEC command to CEC bus. 1511 * 1512 * @param command CEC command to send out 1513 * @param callback interface used to the result of send command 1514 */ 1515 @ServiceThreadOnly sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback)1516 void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) { 1517 assertRunOnServiceThread(); 1518 if (command.getValidationResult() == HdmiCecMessageValidator.OK 1519 && verifyPhysicalAddresses(command)) { 1520 mCecController.sendCommand(command, callback); 1521 } else { 1522 HdmiLogger.error("Invalid message type:" + command); 1523 if (callback != null) { 1524 callback.onSendCompleted(SendMessageResult.FAIL); 1525 } 1526 } 1527 } 1528 1529 @ServiceThreadOnly sendCecCommand(HdmiCecMessage command)1530 void sendCecCommand(HdmiCecMessage command) { 1531 assertRunOnServiceThread(); 1532 sendCecCommand(command, null); 1533 } 1534 1535 /** 1536 * Send <Feature Abort> command on the given CEC message if possible. 1537 * If the aborted message is invalid, then it wont send the message. 1538 * @param command original command to be aborted 1539 * @param reason reason of feature abort 1540 */ 1541 @ServiceThreadOnly maySendFeatureAbortCommand(HdmiCecMessage command, int reason)1542 void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) { 1543 assertRunOnServiceThread(); 1544 mCecController.maySendFeatureAbortCommand(command, reason); 1545 } 1546 1547 /** 1548 * Returns whether all of the physical addresses in a message could exist in this CEC network. 1549 */ verifyPhysicalAddresses(HdmiCecMessage message)1550 boolean verifyPhysicalAddresses(HdmiCecMessage message) { 1551 byte[] params = message.getParams(); 1552 switch (message.getOpcode()) { 1553 case Constants.MESSAGE_ROUTING_CHANGE: 1554 return verifyPhysicalAddress(params, 0) 1555 && verifyPhysicalAddress(params, 2); 1556 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST: 1557 return params.length == 0 || verifyPhysicalAddress(params, 0); 1558 case Constants.MESSAGE_ACTIVE_SOURCE: 1559 case Constants.MESSAGE_INACTIVE_SOURCE: 1560 case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: 1561 case Constants.MESSAGE_ROUTING_INFORMATION: 1562 case Constants.MESSAGE_SET_STREAM_PATH: 1563 return verifyPhysicalAddress(params, 0); 1564 case Constants.MESSAGE_CLEAR_EXTERNAL_TIMER: 1565 case Constants.MESSAGE_SET_EXTERNAL_TIMER: 1566 return verifyExternalSourcePhysicalAddress(params, 7); 1567 default: 1568 return true; 1569 } 1570 } 1571 1572 /** 1573 * Returns whether a given physical address could exist in this CEC network. 1574 * For a TV, the physical address must either be the address of the TV itself, 1575 * or the address of a device connected to one of its ports (possibly indirectly). 1576 */ verifyPhysicalAddress(byte[] params, int offset)1577 private boolean verifyPhysicalAddress(byte[] params, int offset) { 1578 if (!isTvDevice()) { 1579 // If the device is not TV, we can't convert path to port-id, so stop here. 1580 return true; 1581 } 1582 // Invalidate the physical address if parameters length is too short. 1583 if (params.length < offset + 2) { 1584 return false; 1585 } 1586 int path = HdmiUtils.twoBytesToInt(params, offset); 1587 if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) { 1588 return true; 1589 } 1590 int portId = pathToPortId(path); 1591 if (portId == Constants.INVALID_PORT_ID) { 1592 return false; 1593 } 1594 return true; 1595 } 1596 1597 /** 1598 * Returns whether the physical address of an external source could exist in this network. 1599 */ verifyExternalSourcePhysicalAddress(byte[] params, int offset)1600 private boolean verifyExternalSourcePhysicalAddress(byte[] params, int offset) { 1601 int externalSourceSpecifier = params[offset]; 1602 offset = offset + 1; 1603 if (externalSourceSpecifier == 0x05) { 1604 if (params.length - offset >= 2) { 1605 return verifyPhysicalAddress(params, offset); 1606 } 1607 } 1608 return true; 1609 } 1610 1611 /** 1612 * Returns whether the source address of a message is a local logical address. 1613 */ sourceAddressIsLocal(HdmiCecMessage message)1614 private boolean sourceAddressIsLocal(HdmiCecMessage message) { 1615 for (HdmiCecLocalDevice device : getAllCecLocalDevices()) { 1616 if (message.getSource() == device.getDeviceInfo().getLogicalAddress() 1617 && message.getSource() != Constants.ADDR_UNREGISTERED) { 1618 HdmiLogger.warning( 1619 "Unexpected source: message sent from device itself, " + message); 1620 return true; 1621 } 1622 } 1623 return false; 1624 } 1625 1626 @ServiceThreadOnly 1627 @VisibleForTesting 1628 @Constants.HandleMessageResult handleCecCommand(HdmiCecMessage message)1629 protected int handleCecCommand(HdmiCecMessage message) { 1630 assertRunOnServiceThread(); 1631 1632 @HdmiCecMessageValidator.ValidationResult 1633 int validationResult = message.getValidationResult(); 1634 if (validationResult == HdmiCecMessageValidator.ERROR_PARAMETER 1635 || validationResult == HdmiCecMessageValidator.ERROR_PARAMETER_LONG 1636 || !verifyPhysicalAddresses(message)) { 1637 return Constants.ABORT_INVALID_OPERAND; 1638 } else if (validationResult != HdmiCecMessageValidator.OK 1639 || sourceAddressIsLocal(message)) { 1640 return Constants.HANDLED; 1641 } 1642 1643 getHdmiCecNetwork().handleCecMessage(message); 1644 1645 @Constants.HandleMessageResult int handleMessageResult = 1646 dispatchMessageToLocalDevice(message); 1647 // mAddressAllocated is false during address allocation, meaning there is no device to 1648 // handle the message, so it should be buffered, if possible. 1649 if (!mAddressAllocated 1650 && mCecMessageBuffer.bufferMessage(message)) { 1651 return Constants.HANDLED; 1652 } 1653 1654 return handleMessageResult; 1655 } 1656 enableAudioReturnChannel(int portId, boolean enabled)1657 void enableAudioReturnChannel(int portId, boolean enabled) { 1658 if (!mTransitionFromArcToEarcTxEnabled && enabled && mEarcController != null) { 1659 // If the feature flag is set to false, prevent eARC from establishing if ARC is already 1660 // established. 1661 setEarcEnabledInHal(false, false); 1662 } 1663 mCecController.enableAudioReturnChannel(portId, enabled); 1664 } 1665 1666 @ServiceThreadOnly 1667 @VisibleForTesting 1668 @Constants.HandleMessageResult dispatchMessageToLocalDevice(HdmiCecMessage message)1669 protected int dispatchMessageToLocalDevice(HdmiCecMessage message) { 1670 assertRunOnServiceThread(); 1671 for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { 1672 @Constants.HandleMessageResult int messageResult = device.dispatchMessage(message); 1673 if (messageResult != Constants.NOT_HANDLED 1674 && message.getDestination() != Constants.ADDR_BROADCAST) { 1675 return messageResult; 1676 } 1677 } 1678 1679 // We should never respond <Feature Abort> to a broadcast message 1680 if (message.getDestination() == Constants.ADDR_BROADCAST) { 1681 return Constants.HANDLED; 1682 } else { 1683 HdmiLogger.warning("Unhandled cec command:" + message); 1684 return Constants.NOT_HANDLED; 1685 } 1686 } 1687 1688 /** 1689 * Called when a new hotplug event is issued. 1690 * 1691 * @param portId hdmi port number where hot plug event issued. 1692 * @param connected whether to be plugged in or not 1693 */ 1694 @ServiceThreadOnly onHotplug(int portId, boolean connected)1695 void onHotplug(int portId, boolean connected) { 1696 assertRunOnServiceThread(); 1697 // initPortInfo at hotplug event. 1698 mHdmiCecNetwork.initPortInfo(); 1699 1700 if (connected && !isTvDevice() 1701 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { 1702 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 1703 for (int type : getCecLocalDeviceTypes()) { 1704 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(type); 1705 if (localDevice == null) { 1706 localDevice = HdmiCecLocalDevice.create(this, type); 1707 localDevice.init(); 1708 } 1709 localDevices.add(localDevice); 1710 } 1711 allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG); 1712 } 1713 1714 for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { 1715 device.onHotplug(portId, connected); 1716 } 1717 1718 announceHotplugEvent(portId, connected); 1719 } 1720 1721 /** 1722 * Poll all remote devices. It sends <Polling Message> to all remote 1723 * devices. 1724 * 1725 * @param callback an interface used to get a list of all remote devices' address 1726 * @param sourceAddress a logical address of source device where sends polling message 1727 * @param pickStrategy strategy how to pick polling candidates 1728 * @param retryCount the number of retry used to send polling message to remote devices 1729 * @throws IllegalArgumentException if {@code pickStrategy} is invalid value 1730 */ 1731 @ServiceThreadOnly pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)1732 void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, 1733 int retryCount) { 1734 assertRunOnServiceThread(); 1735 mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy), 1736 retryCount); 1737 } 1738 checkPollStrategy(int pickStrategy)1739 private int checkPollStrategy(int pickStrategy) { 1740 int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK; 1741 if (strategy == 0) { 1742 throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy); 1743 } 1744 int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK; 1745 if (iterationStrategy == 0) { 1746 throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy); 1747 } 1748 return strategy | iterationStrategy; 1749 } 1750 getAllCecLocalDevices()1751 List<HdmiCecLocalDevice> getAllCecLocalDevices() { 1752 assertRunOnServiceThread(); 1753 return mHdmiCecNetwork.getLocalDeviceList(); 1754 } 1755 1756 /** 1757 * Check if a logical address is conflict with the current device's. Reallocate the logical 1758 * address of the current device if there is conflict. 1759 * 1760 * Android HDMI CEC 1.4 is handling logical address allocation in the framework side. This could 1761 * introduce delay between the logical address allocation and notifying the driver that the 1762 * address is occupied. Adding this check to avoid such case. 1763 * 1764 * @param logicalAddress logical address of the remote device that might have the same logical 1765 * address as the current device. 1766 * @param physicalAddress physical address of the given device. 1767 */ checkLogicalAddressConflictAndReallocate(int logicalAddress, int physicalAddress)1768 protected void checkLogicalAddressConflictAndReallocate(int logicalAddress, 1769 int physicalAddress) { 1770 // The given device is a local device. No logical address conflict. 1771 if (physicalAddress == getPhysicalAddress()) { 1772 return; 1773 } 1774 for (HdmiCecLocalDevice device : getAllCecLocalDevices()) { 1775 if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) { 1776 HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo()); 1777 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>(); 1778 localDevices.add(device); 1779 allocateLogicalAddress(localDevices, HdmiControlService.INITIATED_BY_HOTPLUG); 1780 return; 1781 } 1782 } 1783 } 1784 getServiceLock()1785 Object getServiceLock() { 1786 return mLock; 1787 } 1788 setAudioStatus(boolean mute, int volume)1789 void setAudioStatus(boolean mute, int volume) { 1790 if (!isTvDeviceEnabled() 1791 || !tv().isSystemAudioActivated() 1792 || !tv().isArcEstablished() // Don't update TV volume when SAM is on and ARC is off 1793 || getHdmiCecVolumeControl() 1794 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 1795 return; 1796 } 1797 AudioManagerWrapper audioManager = getAudioManager(); 1798 boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC); 1799 if (mute) { 1800 if (!muted) { 1801 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true); 1802 } 1803 } else { 1804 if (muted) { 1805 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false); 1806 } 1807 // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing 1808 // volume change notification back to hdmi control service. 1809 int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME; 1810 if (0 <= volume && volume <= 100) { 1811 Slog.i(TAG, "volume: " + volume); 1812 flag |= AudioManager.FLAG_SHOW_UI; 1813 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag); 1814 } 1815 } 1816 } 1817 announceSystemAudioModeChange(boolean enabled)1818 void announceSystemAudioModeChange(boolean enabled) { 1819 synchronized (mLock) { 1820 for (SystemAudioModeChangeListenerRecord record : 1821 mSystemAudioModeChangeListenerRecords) { 1822 invokeSystemAudioModeChangeLocked(record.mListener, enabled); 1823 } 1824 } 1825 } 1826 createDeviceInfo(int logicalAddress, int deviceType, int powerStatus, int cecVersion)1827 private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus, 1828 int cecVersion) { 1829 String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL); 1830 return HdmiDeviceInfo.cecDeviceBuilder() 1831 .setLogicalAddress(logicalAddress) 1832 .setPhysicalAddress(getPhysicalAddress()) 1833 .setPortId(pathToPortId(getPhysicalAddress())) 1834 .setDeviceType(deviceType) 1835 .setVendorId(getVendorId()) 1836 .setDisplayName(displayName) 1837 .setDevicePowerStatus(powerStatus) 1838 .setCecVersion(cecVersion) 1839 .build(); 1840 } 1841 1842 // Set the display name in HdmiDeviceInfo of the current devices to content provided by 1843 // Global.DEVICE_NAME. Only set and broadcast if the new name is different. setDisplayName(String newDisplayName)1844 private void setDisplayName(String newDisplayName) { 1845 for (HdmiCecLocalDevice device : getAllCecLocalDevices()) { 1846 HdmiDeviceInfo deviceInfo = device.getDeviceInfo(); 1847 if (deviceInfo.getDisplayName().equals(newDisplayName)) { 1848 continue; 1849 } 1850 device.setDeviceInfo(deviceInfo.toBuilder().setDisplayName(newDisplayName).build()); 1851 sendCecCommand( 1852 HdmiCecMessageBuilder.buildSetOsdNameCommand( 1853 deviceInfo.getLogicalAddress(), Constants.ADDR_TV, newDisplayName)); 1854 } 1855 } 1856 1857 @ServiceThreadOnly handleMhlHotplugEvent(int portId, boolean connected)1858 void handleMhlHotplugEvent(int portId, boolean connected) { 1859 assertRunOnServiceThread(); 1860 // Hotplug event is used to add/remove MHL devices as TV input. 1861 if (connected) { 1862 HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId); 1863 HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice); 1864 if (oldDevice != null) { 1865 oldDevice.onDeviceRemoved(); 1866 Slog.i(TAG, "Old device of port " + portId + " is removed"); 1867 } 1868 invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE); 1869 updateSafeMhlInput(); 1870 } else { 1871 HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId); 1872 if (device != null) { 1873 device.onDeviceRemoved(); 1874 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE); 1875 updateSafeMhlInput(); 1876 } else { 1877 Slog.w(TAG, "No device to remove:[portId=" + portId); 1878 } 1879 } 1880 announceHotplugEvent(portId, connected); 1881 } 1882 1883 @ServiceThreadOnly handleMhlBusModeChanged(int portId, int busmode)1884 void handleMhlBusModeChanged(int portId, int busmode) { 1885 assertRunOnServiceThread(); 1886 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1887 if (device != null) { 1888 device.setBusMode(busmode); 1889 } else { 1890 Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId + 1891 ", busmode:" + busmode + "]"); 1892 } 1893 } 1894 1895 @ServiceThreadOnly handleMhlBusOvercurrent(int portId, boolean on)1896 void handleMhlBusOvercurrent(int portId, boolean on) { 1897 assertRunOnServiceThread(); 1898 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1899 if (device != null) { 1900 device.onBusOvercurrentDetected(on); 1901 } else { 1902 Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]"); 1903 } 1904 } 1905 1906 @ServiceThreadOnly handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)1907 void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) { 1908 assertRunOnServiceThread(); 1909 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 1910 1911 if (device != null) { 1912 device.setDeviceStatusChange(adopterId, deviceId); 1913 } else { 1914 Slog.w(TAG, "No mhl device exists for device status event[portId:" 1915 + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]"); 1916 } 1917 } 1918 1919 @ServiceThreadOnly updateSafeMhlInput()1920 private void updateSafeMhlInput() { 1921 assertRunOnServiceThread(); 1922 List<HdmiDeviceInfo> inputs = Collections.emptyList(); 1923 SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices(); 1924 for (int i = 0; i < devices.size(); ++i) { 1925 HdmiMhlLocalDeviceStub device = devices.valueAt(i); 1926 HdmiDeviceInfo info = device.getInfo(); 1927 if (info != null) { 1928 if (inputs.isEmpty()) { 1929 inputs = new ArrayList<>(); 1930 } 1931 inputs.add(device.getInfo()); 1932 } 1933 } 1934 synchronized (mLock) { 1935 mMhlDevices = inputs; 1936 } 1937 } 1938 1939 @GuardedBy("mLock") getMhlDevicesLocked()1940 private List<HdmiDeviceInfo> getMhlDevicesLocked() { 1941 return mMhlDevices; 1942 } 1943 1944 private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient { 1945 private final IHdmiMhlVendorCommandListener mListener; 1946 HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener)1947 public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) { 1948 mListener = listener; 1949 } 1950 1951 @Override binderDied()1952 public void binderDied() { 1953 mMhlVendorCommandListenerRecords.remove(this); 1954 } 1955 } 1956 1957 // Record class that monitors the event of the caller of being killed. Used to clean up 1958 // the listener list and record list accordingly. 1959 private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient { 1960 private final IHdmiControlStatusChangeListener mListener; 1961 HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener)1962 HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) { 1963 mListener = listener; 1964 } 1965 1966 @Override binderDied()1967 public void binderDied() { 1968 synchronized (mLock) { 1969 mHdmiControlStatusChangeListenerRecords.remove(this); 1970 } 1971 } 1972 1973 @Override equals(Object obj)1974 public boolean equals(Object obj) { 1975 if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false; 1976 if (obj == this) return true; 1977 HdmiControlStatusChangeListenerRecord other = 1978 (HdmiControlStatusChangeListenerRecord) obj; 1979 return other.mListener == this.mListener; 1980 } 1981 1982 @Override hashCode()1983 public int hashCode() { 1984 return mListener.hashCode(); 1985 } 1986 } 1987 1988 // Record class that monitors the event of the caller of being killed. Used to clean up 1989 // the listener list and record list accordingly. 1990 private final class HotplugEventListenerRecord implements IBinder.DeathRecipient { 1991 private final IHdmiHotplugEventListener mListener; 1992 HotplugEventListenerRecord(IHdmiHotplugEventListener listener)1993 public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) { 1994 mListener = listener; 1995 } 1996 1997 @Override binderDied()1998 public void binderDied() { 1999 synchronized (mLock) { 2000 mHotplugEventListenerRecords.remove(this); 2001 } 2002 } 2003 2004 @Override equals(Object obj)2005 public boolean equals(Object obj) { 2006 if (!(obj instanceof HotplugEventListenerRecord)) return false; 2007 if (obj == this) return true; 2008 HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj; 2009 return other.mListener == this.mListener; 2010 } 2011 2012 @Override hashCode()2013 public int hashCode() { 2014 return mListener.hashCode(); 2015 } 2016 } 2017 2018 private final class DeviceEventListenerRecord implements IBinder.DeathRecipient { 2019 private final IHdmiDeviceEventListener mListener; 2020 DeviceEventListenerRecord(IHdmiDeviceEventListener listener)2021 public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) { 2022 mListener = listener; 2023 } 2024 2025 @Override binderDied()2026 public void binderDied() { 2027 synchronized (mLock) { 2028 mDeviceEventListenerRecords.remove(this); 2029 } 2030 } 2031 } 2032 2033 private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient { 2034 private final IHdmiSystemAudioModeChangeListener mListener; 2035 SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener)2036 public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) { 2037 mListener = listener; 2038 } 2039 2040 @Override binderDied()2041 public void binderDied() { 2042 synchronized (mLock) { 2043 mSystemAudioModeChangeListenerRecords.remove(this); 2044 } 2045 } 2046 } 2047 2048 class VendorCommandListenerRecord implements IBinder.DeathRecipient { 2049 private final IHdmiVendorCommandListener mListener; 2050 private final int mVendorId; 2051 VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId)2052 VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int vendorId) { 2053 mListener = listener; 2054 mVendorId = vendorId; 2055 } 2056 2057 @Override binderDied()2058 public void binderDied() { 2059 synchronized (mLock) { 2060 mVendorCommandListenerRecords.remove(this); 2061 } 2062 } 2063 } 2064 2065 private class HdmiRecordListenerRecord implements IBinder.DeathRecipient { 2066 private final IHdmiRecordListener mListener; 2067 HdmiRecordListenerRecord(IHdmiRecordListener listener)2068 public HdmiRecordListenerRecord(IHdmiRecordListener listener) { 2069 mListener = listener; 2070 } 2071 2072 @Override binderDied()2073 public void binderDied() { 2074 synchronized (mLock) { 2075 if (mRecordListenerRecord == this) { 2076 mRecordListenerRecord = null; 2077 } 2078 } 2079 } 2080 } 2081 2082 /** 2083 * Sets the work source UID to the Binder calling UID. 2084 * Work source UID allows access to the original calling UID of a Binder call in the Runnables 2085 * that it spawns. 2086 * This is necessary because Runnables that are executed on the service thread 2087 * take on the calling UID of the service thread. 2088 */ setWorkSourceUidToCallingUid()2089 private void setWorkSourceUidToCallingUid() { 2090 Binder.setCallingWorkSourceUid(Binder.getCallingUid()); 2091 } 2092 enforceAccessPermission()2093 private void enforceAccessPermission() { 2094 getContext().enforceCallingOrSelfPermission(PERMISSION, TAG); 2095 } 2096 initBinderCall()2097 private void initBinderCall() { 2098 enforceAccessPermission(); 2099 setWorkSourceUidToCallingUid(); 2100 } 2101 2102 private final class BinderService extends IHdmiControlService.Stub { 2103 @Override getSupportedTypes()2104 public int[] getSupportedTypes() { 2105 initBinderCall(); 2106 // mCecLocalDevices is an unmodifiable list - no lock necessary. 2107 int[] localDevices = new int[mCecLocalDevices.size()]; 2108 for (int i = 0; i < localDevices.length; ++i) { 2109 localDevices[i] = mCecLocalDevices.get(i); 2110 } 2111 return localDevices; 2112 } 2113 2114 @Override 2115 @Nullable getActiveSource()2116 public HdmiDeviceInfo getActiveSource() { 2117 initBinderCall(); 2118 2119 return HdmiControlService.this.getActiveSource(); 2120 } 2121 2122 @Override deviceSelect(final int deviceId, final IHdmiControlCallback callback)2123 public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) { 2124 initBinderCall(); 2125 runOnServiceThread(new Runnable() { 2126 @Override 2127 public void run() { 2128 if (callback == null) { 2129 Slog.e(TAG, "Callback cannot be null"); 2130 return; 2131 } 2132 HdmiCecLocalDeviceTv tv = tv(); 2133 HdmiCecLocalDevicePlayback playback = playback(); 2134 if (tv == null && playback == null) { 2135 if (!mAddressAllocated) { 2136 mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect( 2137 HdmiControlService.this, deviceId, callback)); 2138 return; 2139 } 2140 if (isTvDevice()) { 2141 Slog.e(TAG, "Local tv device not available"); 2142 return; 2143 } 2144 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2145 return; 2146 } 2147 if (tv != null) { 2148 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId); 2149 if (device != null) { 2150 if (device.getPortId() == tv.getActivePortId()) { 2151 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 2152 return; 2153 } 2154 // Upon selecting MHL device, we send RAP[Content On] to wake up 2155 // the connected mobile device, start routing control to switch ports. 2156 // callback is handled by MHL action. 2157 device.turnOn(callback); 2158 tv.doManualPortSwitching(device.getPortId(), null); 2159 return; 2160 } 2161 tv.deviceSelect(deviceId, callback); 2162 return; 2163 } 2164 playback.deviceSelect(deviceId, callback); 2165 } 2166 }); 2167 } 2168 2169 @Override portSelect(final int portId, final IHdmiControlCallback callback)2170 public void portSelect(final int portId, final IHdmiControlCallback callback) { 2171 initBinderCall(); 2172 runOnServiceThread(new Runnable() { 2173 @Override 2174 public void run() { 2175 if (callback == null) { 2176 Slog.e(TAG, "Callback cannot be null"); 2177 return; 2178 } 2179 HdmiCecLocalDeviceTv tv = tv(); 2180 if (tv != null) { 2181 tv.doManualPortSwitching(portId, callback); 2182 return; 2183 } 2184 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 2185 if (audioSystem != null) { 2186 audioSystem.doManualPortSwitching(portId, callback); 2187 return; 2188 } 2189 2190 if (!mAddressAllocated) { 2191 mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect( 2192 HdmiControlService.this, portId, callback)); 2193 return; 2194 } 2195 Slog.w(TAG, "Local device not available"); 2196 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2197 return; 2198 } 2199 }); 2200 } 2201 2202 @Override sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed)2203 public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) { 2204 initBinderCall(); 2205 runOnServiceThread(new Runnable() { 2206 @Override 2207 public void run() { 2208 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId); 2209 if (device != null) { 2210 device.sendKeyEvent(keyCode, isPressed); 2211 return; 2212 } 2213 if (mCecController != null) { 2214 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); 2215 if (localDevice == null) { 2216 Slog.w(TAG, "Local device not available to send key event."); 2217 return; 2218 } 2219 localDevice.sendKeyEvent(keyCode, isPressed); 2220 } 2221 } 2222 }); 2223 } 2224 2225 @Override sendVolumeKeyEvent( final int deviceType, final int keyCode, final boolean isPressed)2226 public void sendVolumeKeyEvent( 2227 final int deviceType, final int keyCode, final boolean isPressed) { 2228 initBinderCall(); 2229 runOnServiceThread(new Runnable() { 2230 @Override 2231 public void run() { 2232 if (mCecController == null) { 2233 Slog.w(TAG, "CEC controller not available to send volume key event."); 2234 return; 2235 } 2236 HdmiCecLocalDevice localDevice = mHdmiCecNetwork.getLocalDevice(deviceType); 2237 if (localDevice == null) { 2238 Slog.w(TAG, "Local device " + deviceType 2239 + " not available to send volume key event."); 2240 return; 2241 } 2242 localDevice.sendVolumeKeyEvent(keyCode, isPressed); 2243 } 2244 }); 2245 } 2246 2247 @Override oneTouchPlay(final IHdmiControlCallback callback)2248 public void oneTouchPlay(final IHdmiControlCallback callback) { 2249 initBinderCall(); 2250 int pid = Binder.getCallingPid(); 2251 Slog.d(TAG, "Process pid: " + pid + " is calling oneTouchPlay."); 2252 runOnServiceThread(new Runnable() { 2253 @Override 2254 public void run() { 2255 HdmiControlService.this.oneTouchPlay(callback); 2256 } 2257 }); 2258 } 2259 2260 @Override toggleAndFollowTvPower()2261 public void toggleAndFollowTvPower() { 2262 initBinderCall(); 2263 int pid = Binder.getCallingPid(); 2264 Slog.d(TAG, "Process pid: " + pid + " is calling toggleAndFollowTvPower."); 2265 runOnServiceThread(new Runnable() { 2266 @Override 2267 public void run() { 2268 HdmiControlService.this.toggleAndFollowTvPower(); 2269 } 2270 }); 2271 } 2272 2273 @Override shouldHandleTvPowerKey()2274 public boolean shouldHandleTvPowerKey() { 2275 initBinderCall(); 2276 return HdmiControlService.this.shouldHandleTvPowerKey(); 2277 } 2278 2279 @Override queryDisplayStatus(final IHdmiControlCallback callback)2280 public void queryDisplayStatus(final IHdmiControlCallback callback) { 2281 initBinderCall(); 2282 runOnServiceThread(new Runnable() { 2283 @Override 2284 public void run() { 2285 HdmiControlService.this.queryDisplayStatus(callback); 2286 } 2287 }); 2288 } 2289 2290 @Override addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2291 public void addHdmiControlStatusChangeListener( 2292 final IHdmiControlStatusChangeListener listener) { 2293 initBinderCall(); 2294 HdmiControlService.this.addHdmiControlStatusChangeListener(listener); 2295 } 2296 2297 @Override removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2298 public void removeHdmiControlStatusChangeListener( 2299 final IHdmiControlStatusChangeListener listener) { 2300 initBinderCall(); 2301 HdmiControlService.this.removeHdmiControlStatusChangeListener(listener); 2302 } 2303 2304 @Override addHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)2305 public void addHdmiCecVolumeControlFeatureListener( 2306 final IHdmiCecVolumeControlFeatureListener listener) { 2307 initBinderCall(); 2308 HdmiControlService.this.addHdmiCecVolumeControlFeatureListener(listener); 2309 } 2310 2311 @Override removeHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)2312 public void removeHdmiCecVolumeControlFeatureListener( 2313 final IHdmiCecVolumeControlFeatureListener listener) { 2314 initBinderCall(); 2315 HdmiControlService.this.removeHdmiControlVolumeControlStatusChangeListener(listener); 2316 } 2317 2318 2319 @Override addHotplugEventListener(final IHdmiHotplugEventListener listener)2320 public void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 2321 initBinderCall(); 2322 HdmiControlService.this.addHotplugEventListener(listener); 2323 } 2324 2325 @Override removeHotplugEventListener(final IHdmiHotplugEventListener listener)2326 public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) { 2327 initBinderCall(); 2328 HdmiControlService.this.removeHotplugEventListener(listener); 2329 } 2330 2331 @Override addDeviceEventListener(final IHdmiDeviceEventListener listener)2332 public void addDeviceEventListener(final IHdmiDeviceEventListener listener) { 2333 initBinderCall(); 2334 HdmiControlService.this.addDeviceEventListener(listener); 2335 } 2336 2337 @Override getPortInfo()2338 public List<HdmiPortInfo> getPortInfo() { 2339 initBinderCall(); 2340 return HdmiControlService.this.getPortInfo() == null 2341 ? Collections.<HdmiPortInfo>emptyList() 2342 : HdmiControlService.this.getPortInfo(); 2343 } 2344 2345 @Override canChangeSystemAudioMode()2346 public boolean canChangeSystemAudioMode() { 2347 initBinderCall(); 2348 HdmiCecLocalDeviceTv tv = tv(); 2349 if (tv == null) { 2350 return false; 2351 } 2352 return tv.hasSystemAudioDevice(); 2353 } 2354 2355 @Override getSystemAudioMode()2356 public boolean getSystemAudioMode() { 2357 // TODO(shubang): handle getSystemAudioMode() for all device types 2358 initBinderCall(); 2359 HdmiCecLocalDeviceTv tv = tv(); 2360 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 2361 return (tv != null && tv.isSystemAudioActivated()) 2362 || (audioSystem != null && audioSystem.isSystemAudioActivated()); 2363 } 2364 2365 @Override getPhysicalAddress()2366 public int getPhysicalAddress() { 2367 initBinderCall(); 2368 synchronized (mLock) { 2369 return mHdmiCecNetwork.getPhysicalAddress(); 2370 } 2371 } 2372 2373 @Override setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback)2374 public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) { 2375 initBinderCall(); 2376 runOnServiceThread(new Runnable() { 2377 @Override 2378 public void run() { 2379 HdmiCecLocalDeviceTv tv = tv(); 2380 if (tv == null) { 2381 Slog.w(TAG, "Local tv device not available"); 2382 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2383 return; 2384 } 2385 tv.changeSystemAudioMode(enabled, callback); 2386 } 2387 }); 2388 } 2389 2390 @Override addSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)2391 public void addSystemAudioModeChangeListener( 2392 final IHdmiSystemAudioModeChangeListener listener) { 2393 initBinderCall(); 2394 HdmiControlService.this.addSystemAudioModeChangeListner(listener); 2395 } 2396 2397 @Override removeSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)2398 public void removeSystemAudioModeChangeListener( 2399 final IHdmiSystemAudioModeChangeListener listener) { 2400 initBinderCall(); 2401 HdmiControlService.this.removeSystemAudioModeChangeListener(listener); 2402 } 2403 2404 @Override setInputChangeListener(final IHdmiInputChangeListener listener)2405 public void setInputChangeListener(final IHdmiInputChangeListener listener) { 2406 initBinderCall(); 2407 HdmiControlService.this.setInputChangeListener(listener); 2408 } 2409 2410 @Override getInputDevices()2411 public List<HdmiDeviceInfo> getInputDevices() { 2412 initBinderCall(); 2413 // No need to hold the lock for obtaining TV device as the local device instance 2414 // is preserved while the HDMI control is enabled. 2415 return HdmiUtils.mergeToUnmodifiableList(mHdmiCecNetwork.getSafeExternalInputsLocked(), 2416 getMhlDevicesLocked()); 2417 } 2418 2419 // Returns all the CEC devices on the bus including system audio, switch, 2420 // even those of reserved type. 2421 @Override getDeviceList()2422 public List<HdmiDeviceInfo> getDeviceList() { 2423 initBinderCall(); 2424 return mHdmiCecNetwork.getSafeCecDevicesLocked(); 2425 } 2426 2427 @Override powerOffRemoteDevice(int logicalAddress, int powerStatus)2428 public void powerOffRemoteDevice(int logicalAddress, int powerStatus) { 2429 initBinderCall(); 2430 runOnServiceThread(new Runnable() { 2431 @Override 2432 public void run() { 2433 Slog.w(TAG, "Device " 2434 + logicalAddress + " power status is " + powerStatus 2435 + " before standby command sent out"); 2436 sendCecCommand(HdmiCecMessageBuilder.buildStandby( 2437 getRemoteControlSourceAddress(), logicalAddress)); 2438 } 2439 }); 2440 } 2441 2442 @Override powerOnRemoteDevice(int logicalAddress, int powerStatus)2443 public void powerOnRemoteDevice(int logicalAddress, int powerStatus) { 2444 initBinderCall(); 2445 runOnServiceThread(new Runnable() { 2446 @Override 2447 public void run() { 2448 Slog.i(TAG, "Device " 2449 + logicalAddress + " power status is " + powerStatus 2450 + " before power on command sent out"); 2451 if (getSwitchDevice() != null) { 2452 getSwitchDevice().sendUserControlPressedAndReleased( 2453 logicalAddress, HdmiCecKeycode.CEC_KEYCODE_POWER_ON_FUNCTION); 2454 } else { 2455 Slog.e(TAG, "Can't get the correct local device to handle routing."); 2456 } 2457 } 2458 }); 2459 } 2460 2461 @Override 2462 // TODO(b/128427908): add a result callback askRemoteDeviceToBecomeActiveSource(int physicalAddress)2463 public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { 2464 initBinderCall(); 2465 runOnServiceThread(new Runnable() { 2466 @Override 2467 public void run() { 2468 HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath( 2469 getRemoteControlSourceAddress(), physicalAddress); 2470 if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) { 2471 if (getSwitchDevice() != null) { 2472 getSwitchDevice().handleSetStreamPath(setStreamPath); 2473 } else { 2474 Slog.e(TAG, "Can't get the correct local device to handle routing."); 2475 } 2476 } 2477 sendCecCommand(setStreamPath); 2478 } 2479 }); 2480 } 2481 2482 @Override setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex)2483 public void setSystemAudioVolume(final int oldIndex, final int newIndex, 2484 final int maxIndex) { 2485 initBinderCall(); 2486 runOnServiceThread(new Runnable() { 2487 @Override 2488 public void run() { 2489 HdmiCecLocalDeviceTv tv = tv(); 2490 if (tv == null) { 2491 Slog.w(TAG, "Local tv device not available"); 2492 return; 2493 } 2494 tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex); 2495 } 2496 }); 2497 } 2498 2499 @Override setSystemAudioMute(final boolean mute)2500 public void setSystemAudioMute(final boolean mute) { 2501 initBinderCall(); 2502 runOnServiceThread(new Runnable() { 2503 @Override 2504 public void run() { 2505 HdmiCecLocalDeviceTv tv = tv(); 2506 if (tv == null) { 2507 Slog.w(TAG, "Local tv device not available"); 2508 return; 2509 } 2510 tv.changeMute(mute); 2511 } 2512 }); 2513 } 2514 2515 @Override setArcMode(final boolean enabled)2516 public void setArcMode(final boolean enabled) { 2517 initBinderCall(); 2518 runOnServiceThread(new Runnable() { 2519 @Override 2520 public void run() { 2521 HdmiCecLocalDeviceTv tv = tv(); 2522 if (tv == null) { 2523 Slog.w(TAG, "Local tv device not available to change arc mode."); 2524 return; 2525 } 2526 tv.startArcAction(enabled); 2527 } 2528 }); 2529 } 2530 2531 @Override setProhibitMode(final boolean enabled)2532 public void setProhibitMode(final boolean enabled) { 2533 initBinderCall(); 2534 if (!isTvDevice()) { 2535 return; 2536 } 2537 HdmiControlService.this.setProhibitMode(enabled); 2538 } 2539 2540 @Override addVendorCommandListener( final IHdmiVendorCommandListener listener, final int vendorId)2541 public void addVendorCommandListener( 2542 final IHdmiVendorCommandListener listener, final int vendorId) { 2543 initBinderCall(); 2544 HdmiControlService.this.addVendorCommandListener(listener, vendorId); 2545 } 2546 2547 @Override sendVendorCommand(final int deviceType, final int targetAddress, final byte[] params, final boolean hasVendorId)2548 public void sendVendorCommand(final int deviceType, final int targetAddress, 2549 final byte[] params, final boolean hasVendorId) { 2550 initBinderCall(); 2551 runOnServiceThread(new Runnable() { 2552 @Override 2553 public void run() { 2554 HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); 2555 if (device == null) { 2556 Slog.w(TAG, "Local device not available"); 2557 return; 2558 } 2559 if (hasVendorId) { 2560 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId( 2561 device.getDeviceInfo().getLogicalAddress(), targetAddress, 2562 getVendorId(), params)); 2563 } else { 2564 sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand( 2565 device.getDeviceInfo().getLogicalAddress(), targetAddress, params)); 2566 } 2567 } 2568 }); 2569 } 2570 2571 @Override sendStandby(final int deviceType, final int deviceId)2572 public void sendStandby(final int deviceType, final int deviceId) { 2573 initBinderCall(); 2574 runOnServiceThread(new Runnable() { 2575 @Override 2576 public void run() { 2577 HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId); 2578 if (mhlDevice != null) { 2579 mhlDevice.sendStandby(); 2580 return; 2581 } 2582 HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); 2583 if (device == null) { 2584 device = audioSystem(); 2585 } 2586 if (device == null) { 2587 Slog.w(TAG, "Local device not available"); 2588 return; 2589 } 2590 device.sendStandby(deviceId); 2591 } 2592 }); 2593 } 2594 2595 @Override setHdmiRecordListener(IHdmiRecordListener listener)2596 public void setHdmiRecordListener(IHdmiRecordListener listener) { 2597 initBinderCall(); 2598 HdmiControlService.this.setHdmiRecordListener(listener); 2599 } 2600 2601 @Override startOneTouchRecord(final int recorderAddress, final byte[] recordSource)2602 public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) { 2603 initBinderCall(); 2604 runOnServiceThread(new Runnable() { 2605 @Override 2606 public void run() { 2607 if (!isTvDeviceEnabled()) { 2608 Slog.w(TAG, "TV device is not enabled."); 2609 return; 2610 } 2611 tv().startOneTouchRecord(recorderAddress, recordSource); 2612 } 2613 }); 2614 } 2615 2616 @Override stopOneTouchRecord(final int recorderAddress)2617 public void stopOneTouchRecord(final int recorderAddress) { 2618 initBinderCall(); 2619 runOnServiceThread(new Runnable() { 2620 @Override 2621 public void run() { 2622 if (!isTvDeviceEnabled()) { 2623 Slog.w(TAG, "TV device is not enabled."); 2624 return; 2625 } 2626 tv().stopOneTouchRecord(recorderAddress); 2627 } 2628 }); 2629 } 2630 2631 @Override startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)2632 public void startTimerRecording(final int recorderAddress, final int sourceType, 2633 final byte[] recordSource) { 2634 initBinderCall(); 2635 runOnServiceThread(new Runnable() { 2636 @Override 2637 public void run() { 2638 if (!isTvDeviceEnabled()) { 2639 Slog.w(TAG, "TV device is not enabled."); 2640 return; 2641 } 2642 tv().startTimerRecording(recorderAddress, sourceType, recordSource); 2643 } 2644 }); 2645 } 2646 2647 @Override clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)2648 public void clearTimerRecording(final int recorderAddress, final int sourceType, 2649 final byte[] recordSource) { 2650 initBinderCall(); 2651 runOnServiceThread(new Runnable() { 2652 @Override 2653 public void run() { 2654 if (!isTvDeviceEnabled()) { 2655 Slog.w(TAG, "TV device is not enabled."); 2656 return; 2657 } 2658 tv().clearTimerRecording(recorderAddress, sourceType, recordSource); 2659 } 2660 }); 2661 } 2662 2663 @Override sendMhlVendorCommand(final int portId, final int offset, final int length, final byte[] data)2664 public void sendMhlVendorCommand(final int portId, final int offset, final int length, 2665 final byte[] data) { 2666 initBinderCall(); 2667 runOnServiceThread(new Runnable() { 2668 @Override 2669 public void run() { 2670 if (!isCecControlEnabled()) { 2671 Slog.w(TAG, "Hdmi control is disabled."); 2672 return ; 2673 } 2674 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 2675 if (device == null) { 2676 Slog.w(TAG, "Invalid port id:" + portId); 2677 return; 2678 } 2679 mMhlController.sendVendorCommand(portId, offset, length, data); 2680 } 2681 }); 2682 } 2683 2684 @Override addHdmiMhlVendorCommandListener( IHdmiMhlVendorCommandListener listener)2685 public void addHdmiMhlVendorCommandListener( 2686 IHdmiMhlVendorCommandListener listener) { 2687 initBinderCall(); 2688 HdmiControlService.this.addHdmiMhlVendorCommandListener(listener); 2689 } 2690 2691 @Override setStandbyMode(final boolean isStandbyModeOn)2692 public void setStandbyMode(final boolean isStandbyModeOn) { 2693 initBinderCall(); 2694 runOnServiceThread(new Runnable() { 2695 @Override 2696 public void run() { 2697 HdmiControlService.this.setStandbyMode(isStandbyModeOn); 2698 } 2699 }); 2700 } 2701 2702 @Override reportAudioStatus(final int deviceType, final int volume, final int maxVolume, final boolean isMute)2703 public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume, 2704 final boolean isMute) { 2705 initBinderCall(); 2706 runOnServiceThread(new Runnable() { 2707 @Override 2708 public void run() { 2709 HdmiCecLocalDevice device = mHdmiCecNetwork.getLocalDevice(deviceType); 2710 if (device == null) { 2711 Slog.w(TAG, "Local device not available"); 2712 return; 2713 } 2714 if (audioSystem() == null) { 2715 Slog.w(TAG, "audio system is not available"); 2716 return; 2717 } 2718 if (!audioSystem().isSystemAudioActivated()) { 2719 Slog.w(TAG, "audio system is not in system audio mode"); 2720 return; 2721 } 2722 audioSystem().reportAudioStatus(Constants.ADDR_TV); 2723 } 2724 }); 2725 } 2726 2727 @Override setSystemAudioModeOnForAudioOnlySource()2728 public void setSystemAudioModeOnForAudioOnlySource() { 2729 initBinderCall(); 2730 runOnServiceThread(new Runnable() { 2731 @Override 2732 public void run() { 2733 if (!isAudioSystemDevice()) { 2734 Slog.e(TAG, "Not an audio system device. Won't set system audio mode on"); 2735 return; 2736 } 2737 if (audioSystem() == null) { 2738 Slog.e(TAG, "Audio System local device is not registered"); 2739 return; 2740 } 2741 if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) { 2742 Slog.e(TAG, "System Audio Mode is not supported."); 2743 return; 2744 } 2745 sendCecCommand( 2746 HdmiCecMessageBuilder.buildSetSystemAudioMode( 2747 audioSystem().getDeviceInfo().getLogicalAddress(), 2748 Constants.ADDR_BROADCAST, 2749 true)); 2750 } 2751 }); 2752 } 2753 2754 @Override onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, String[] args, @Nullable ShellCallback callback, ResultReceiver resultReceiver)2755 public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, 2756 @Nullable FileDescriptor err, String[] args, 2757 @Nullable ShellCallback callback, ResultReceiver resultReceiver) 2758 throws RemoteException { 2759 initBinderCall(); 2760 new HdmiControlShellCommand(this) 2761 .exec(this, in, out, err, args, callback, resultReceiver); 2762 } 2763 2764 @Override dump(FileDescriptor fd, final PrintWriter writer, String[] args)2765 protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 2766 if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return; 2767 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 2768 2769 synchronized (mLock) { 2770 pw.println("mProhibitMode: " + mProhibitMode); 2771 } 2772 pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus()); 2773 pw.println("mIsCecAvailable: " + mIsCecAvailable); 2774 pw.println("mCecVersion: " + mCecVersion); 2775 pw.println("mIsAbsoluteVolumeBehaviorEnabled: " + isAbsoluteVolumeBehaviorEnabled()); 2776 2777 // System settings 2778 pw.println("System_settings:"); 2779 pw.increaseIndent(); 2780 pw.println("mMhlInputChangeEnabled: " + isMhlInputChangeEnabled()); 2781 pw.println("mSystemAudioActivated: " + isSystemAudioActivated()); 2782 pw.println("mHdmiCecVolumeControlEnabled: " + getHdmiCecVolumeControl()); 2783 pw.decreaseIndent(); 2784 2785 // CEC settings 2786 pw.println("CEC settings:"); 2787 pw.increaseIndent(); 2788 HdmiCecConfig hdmiCecConfig = HdmiControlService.this.getHdmiCecConfig(); 2789 List<String> allSettings = hdmiCecConfig.getAllSettings(); 2790 Set<String> userSettings = new HashSet<>(hdmiCecConfig.getUserSettings()); 2791 for (String setting : allSettings) { 2792 if (hdmiCecConfig.isStringValueType(setting)) { 2793 pw.println(setting + " (string): " + hdmiCecConfig.getStringValue(setting) 2794 + " (default: " + hdmiCecConfig.getDefaultStringValue(setting) + ")" 2795 + (userSettings.contains(setting) ? " [modifiable]" : "")); 2796 } else if (hdmiCecConfig.isIntValueType(setting)) { 2797 pw.println(setting + " (int): " + hdmiCecConfig.getIntValue(setting) 2798 + " (default: " + hdmiCecConfig.getDefaultIntValue(setting) + ")" 2799 + (userSettings.contains(setting) ? " [modifiable]" : "")); 2800 } 2801 } 2802 pw.decreaseIndent(); 2803 2804 pw.println("mMhlController: "); 2805 pw.increaseIndent(); 2806 mMhlController.dump(pw); 2807 pw.decreaseIndent(); 2808 pw.print("eARC local device: "); 2809 pw.increaseIndent(); 2810 if (mEarcLocalDevice == null) { 2811 pw.println("None. eARC is either disabled or not available."); 2812 } else { 2813 mEarcLocalDevice.dump(pw); 2814 } 2815 pw.decreaseIndent(); 2816 mHdmiCecNetwork.dump(pw); 2817 if (mCecController != null) { 2818 pw.println("mCecController: "); 2819 pw.increaseIndent(); 2820 mCecController.dump(pw); 2821 pw.decreaseIndent(); 2822 } 2823 } 2824 2825 @Override setMessageHistorySize(int newSize)2826 public boolean setMessageHistorySize(int newSize) { 2827 enforceAccessPermission(); 2828 if (mCecController == null) { 2829 return false; 2830 } 2831 return mCecController.setMessageHistorySize(newSize); 2832 } 2833 2834 @Override getMessageHistorySize()2835 public int getMessageHistorySize() { 2836 enforceAccessPermission(); 2837 if (mCecController != null) { 2838 return mCecController.getMessageHistorySize(); 2839 } else { 2840 return 0; 2841 } 2842 } 2843 2844 @Override addCecSettingChangeListener(String name, final IHdmiCecSettingChangeListener listener)2845 public void addCecSettingChangeListener(String name, 2846 final IHdmiCecSettingChangeListener listener) { 2847 enforceAccessPermission(); 2848 HdmiControlService.this.addCecSettingChangeListener(name, listener); 2849 } 2850 2851 @Override removeCecSettingChangeListener(String name, final IHdmiCecSettingChangeListener listener)2852 public void removeCecSettingChangeListener(String name, 2853 final IHdmiCecSettingChangeListener listener) { 2854 enforceAccessPermission(); 2855 HdmiControlService.this.removeCecSettingChangeListener(name, listener); 2856 } 2857 2858 @Override getUserCecSettings()2859 public List<String> getUserCecSettings() { 2860 initBinderCall(); 2861 final long token = Binder.clearCallingIdentity(); 2862 try { 2863 return HdmiControlService.this.getHdmiCecConfig().getUserSettings(); 2864 } finally { 2865 Binder.restoreCallingIdentity(token); 2866 } 2867 } 2868 2869 @Override getAllowedCecSettingStringValues(String name)2870 public List<String> getAllowedCecSettingStringValues(String name) { 2871 initBinderCall(); 2872 final long token = Binder.clearCallingIdentity(); 2873 try { 2874 return HdmiControlService.this.getHdmiCecConfig().getAllowedStringValues(name); 2875 } finally { 2876 Binder.restoreCallingIdentity(token); 2877 } 2878 } 2879 2880 @Override getAllowedCecSettingIntValues(String name)2881 public int[] getAllowedCecSettingIntValues(String name) { 2882 initBinderCall(); 2883 final long token = Binder.clearCallingIdentity(); 2884 try { 2885 List<Integer> allowedValues = 2886 HdmiControlService.this.getHdmiCecConfig().getAllowedIntValues(name); 2887 return allowedValues.stream().mapToInt(i->i).toArray(); 2888 } finally { 2889 Binder.restoreCallingIdentity(token); 2890 } 2891 } 2892 2893 @Override getCecSettingStringValue(String name)2894 public String getCecSettingStringValue(String name) { 2895 initBinderCall(); 2896 final long token = Binder.clearCallingIdentity(); 2897 try { 2898 return HdmiControlService.this.getHdmiCecConfig().getStringValue(name); 2899 } finally { 2900 Binder.restoreCallingIdentity(token); 2901 } 2902 } 2903 2904 @Override setCecSettingStringValue(String name, String value)2905 public void setCecSettingStringValue(String name, String value) { 2906 initBinderCall(); 2907 final long token = Binder.clearCallingIdentity(); 2908 try { 2909 HdmiControlService.this.getHdmiCecConfig().setStringValue(name, value); 2910 } finally { 2911 Binder.restoreCallingIdentity(token); 2912 } 2913 } 2914 2915 @Override getCecSettingIntValue(String name)2916 public int getCecSettingIntValue(String name) { 2917 initBinderCall(); 2918 final long token = Binder.clearCallingIdentity(); 2919 try { 2920 return HdmiControlService.this.getHdmiCecConfig().getIntValue(name); 2921 } finally { 2922 Binder.restoreCallingIdentity(token); 2923 } 2924 } 2925 2926 @Override setCecSettingIntValue(String name, int value)2927 public void setCecSettingIntValue(String name, int value) { 2928 initBinderCall(); 2929 final long token = Binder.clearCallingIdentity(); 2930 try { 2931 HdmiControlService.this.getHdmiCecConfig().setIntValue(name, value); 2932 } finally { 2933 Binder.restoreCallingIdentity(token); 2934 } 2935 } 2936 } 2937 2938 @VisibleForTesting setHdmiCecVolumeControlEnabledInternal( @dmiControlManager.VolumeControl int hdmiCecVolumeControl)2939 void setHdmiCecVolumeControlEnabledInternal( 2940 @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) { 2941 mHdmiCecVolumeControl = hdmiCecVolumeControl; 2942 announceHdmiCecVolumeControlFeatureChange(hdmiCecVolumeControl); 2943 runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior); 2944 } 2945 2946 // Get the source address to send out commands to devices connected to the current device 2947 // when other services interact with HdmiControlService. getRemoteControlSourceAddress()2948 private int getRemoteControlSourceAddress() { 2949 if (isAudioSystemDevice()) { 2950 return audioSystem().getDeviceInfo().getLogicalAddress(); 2951 } else if (isPlaybackDevice()) { 2952 return playback().getDeviceInfo().getLogicalAddress(); 2953 } 2954 return ADDR_UNREGISTERED; 2955 } 2956 2957 // Get the switch device to do CEC routing control 2958 @Nullable getSwitchDevice()2959 private HdmiCecLocalDeviceSource getSwitchDevice() { 2960 if (isAudioSystemDevice()) { 2961 return audioSystem(); 2962 } 2963 if (isPlaybackDevice()) { 2964 return playback(); 2965 } 2966 return null; 2967 } 2968 2969 @ServiceThreadOnly 2970 @VisibleForTesting oneTouchPlay(final IHdmiControlCallback callback)2971 protected void oneTouchPlay(final IHdmiControlCallback callback) { 2972 assertRunOnServiceThread(); 2973 if (!mAddressAllocated) { 2974 mOtpCallbackPendingAddressAllocation = callback; 2975 Slog.d(TAG, "Local device is under address allocation. " 2976 + "Save OTP callback for later process."); 2977 return; 2978 } 2979 2980 HdmiCecLocalDeviceSource source = playback(); 2981 if (source == null) { 2982 source = audioSystem(); 2983 } 2984 2985 if (source == null) { 2986 Slog.w(TAG, "Local source device not available"); 2987 invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE); 2988 return; 2989 } 2990 source.oneTouchPlay(callback); 2991 } 2992 2993 @ServiceThreadOnly 2994 @VisibleForTesting toggleAndFollowTvPower()2995 protected void toggleAndFollowTvPower() { 2996 assertRunOnServiceThread(); 2997 HdmiCecLocalDeviceSource source = playback(); 2998 if (source == null) { 2999 source = audioSystem(); 3000 } 3001 3002 if (source == null) { 3003 Slog.w(TAG, "Local source device not available"); 3004 return; 3005 } 3006 source.toggleAndFollowTvPower(); 3007 } 3008 3009 @VisibleForTesting shouldHandleTvPowerKey()3010 protected boolean shouldHandleTvPowerKey() { 3011 if (isTvDevice()) { 3012 return false; 3013 } 3014 String powerControlMode = getHdmiCecConfig().getStringValue( 3015 HdmiControlManager.CEC_SETTING_NAME_POWER_CONTROL_MODE); 3016 if (powerControlMode.equals(HdmiControlManager.POWER_CONTROL_MODE_NONE)) { 3017 return false; 3018 } 3019 int hdmiCecEnabled = getHdmiCecConfig().getIntValue( 3020 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED); 3021 if (hdmiCecEnabled != HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) { 3022 return false; 3023 } 3024 return mIsCecAvailable; 3025 } 3026 3027 /** 3028 * Queries the display status of the TV and calls {@code callback} upon completion. 3029 * 3030 * If this is a non-source device, or if the query fails for any reason, the callback will 3031 * be called with {@link HdmiControlManager.POWER_STATUS_UNKNOWN}. 3032 * 3033 * If the query succeeds, the callback will be called with one of the other power status 3034 * constants. 3035 */ 3036 @ServiceThreadOnly queryDisplayStatus(final IHdmiControlCallback callback)3037 protected void queryDisplayStatus(final IHdmiControlCallback callback) { 3038 assertRunOnServiceThread(); 3039 if (!mAddressAllocated) { 3040 mDisplayStatusCallback = callback; 3041 Slog.d(TAG, "Local device is under address allocation. " 3042 + "Queue display callback for later process."); 3043 return; 3044 } 3045 3046 HdmiCecLocalDeviceSource source = playback(); 3047 if (source == null) { 3048 source = audioSystem(); 3049 } 3050 3051 if (source == null) { 3052 Slog.w(TAG, "Local source device not available"); 3053 invokeCallback(callback, HdmiControlManager.POWER_STATUS_UNKNOWN); 3054 return; 3055 } 3056 source.queryDisplayStatus(callback); 3057 } 3058 getActiveSource()3059 protected HdmiDeviceInfo getActiveSource() { 3060 // If a the device is a playback device that is the current active source, return the 3061 // local device info 3062 if (playback() != null && playback().isActiveSource()) { 3063 return playback().getDeviceInfo(); 3064 } 3065 3066 // Otherwise get the active source and look for it from the device list 3067 ActiveSource activeSource = getLocalActiveSource(); 3068 3069 if (activeSource.isValid()) { 3070 HdmiDeviceInfo activeSourceInfo = mHdmiCecNetwork.getSafeCecDeviceInfo( 3071 activeSource.logicalAddress); 3072 if (activeSourceInfo != null) { 3073 return activeSourceInfo; 3074 } 3075 3076 return HdmiDeviceInfo.hardwarePort(activeSource.physicalAddress, 3077 pathToPortId(activeSource.physicalAddress)); 3078 } 3079 3080 if (tv() != null) { 3081 int activePath = tv().getActivePath(); 3082 if (activePath != HdmiDeviceInfo.PATH_INVALID) { 3083 HdmiDeviceInfo info = mHdmiCecNetwork.getSafeDeviceInfoByPath(activePath); 3084 return (info != null) ? info : HdmiDeviceInfo.hardwarePort(activePath, 3085 tv().getActivePortId()); 3086 } 3087 } 3088 3089 return null; 3090 } 3091 3092 @VisibleForTesting addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)3093 void addHdmiControlStatusChangeListener( 3094 final IHdmiControlStatusChangeListener listener) { 3095 final HdmiControlStatusChangeListenerRecord record = 3096 new HdmiControlStatusChangeListenerRecord(listener); 3097 try { 3098 listener.asBinder().linkToDeath(record, 0); 3099 } catch (RemoteException e) { 3100 Slog.w(TAG, "Listener already died"); 3101 return; 3102 } 3103 synchronized (mLock) { 3104 mHdmiControlStatusChangeListenerRecords.add(record); 3105 } 3106 3107 // Inform the listener of the initial state of each HDMI port by generating 3108 // hotplug events. 3109 runOnServiceThread(new Runnable() { 3110 @Override 3111 public void run() { 3112 synchronized (mLock) { 3113 if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return; 3114 } 3115 3116 // Return the current status of mHdmiControlEnabled; 3117 synchronized (mLock) { 3118 invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled); 3119 } 3120 } 3121 }); 3122 } 3123 removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)3124 private void removeHdmiControlStatusChangeListener( 3125 final IHdmiControlStatusChangeListener listener) { 3126 synchronized (mLock) { 3127 for (HdmiControlStatusChangeListenerRecord record : 3128 mHdmiControlStatusChangeListenerRecords) { 3129 if (record.mListener.asBinder() == listener.asBinder()) { 3130 listener.asBinder().unlinkToDeath(record, 0); 3131 mHdmiControlStatusChangeListenerRecords.remove(record); 3132 break; 3133 } 3134 } 3135 } 3136 } 3137 3138 @VisibleForTesting addHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)3139 void addHdmiCecVolumeControlFeatureListener( 3140 final IHdmiCecVolumeControlFeatureListener listener) { 3141 mHdmiCecVolumeControlFeatureListenerRecords.register(listener); 3142 3143 runOnServiceThread(new Runnable() { 3144 @Override 3145 public void run() { 3146 // Return the current status of mHdmiCecVolumeControlEnabled; 3147 synchronized (mLock) { 3148 try { 3149 listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControl); 3150 } catch (RemoteException e) { 3151 Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: " 3152 + mHdmiCecVolumeControl, e); 3153 } 3154 } 3155 } 3156 }); 3157 } 3158 3159 @VisibleForTesting removeHdmiControlVolumeControlStatusChangeListener( final IHdmiCecVolumeControlFeatureListener listener)3160 void removeHdmiControlVolumeControlStatusChangeListener( 3161 final IHdmiCecVolumeControlFeatureListener listener) { 3162 mHdmiCecVolumeControlFeatureListenerRecords.unregister(listener); 3163 } 3164 addHotplugEventListener(final IHdmiHotplugEventListener listener)3165 private void addHotplugEventListener(final IHdmiHotplugEventListener listener) { 3166 final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener); 3167 try { 3168 listener.asBinder().linkToDeath(record, 0); 3169 } catch (RemoteException e) { 3170 Slog.w(TAG, "Listener already died"); 3171 return; 3172 } 3173 synchronized (mLock) { 3174 mHotplugEventListenerRecords.add(record); 3175 } 3176 3177 // Inform the listener of the initial state of each HDMI port by generating 3178 // hotplug events. 3179 runOnServiceThread(new Runnable() { 3180 @Override 3181 public void run() { 3182 synchronized (mLock) { 3183 if (!mHotplugEventListenerRecords.contains(record)) return; 3184 } 3185 for (HdmiPortInfo port : getPortInfo()) { 3186 HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(), 3187 mCecController.isConnected(port.getId())); 3188 synchronized (mLock) { 3189 invokeHotplugEventListenerLocked(listener, event); 3190 } 3191 } 3192 } 3193 }); 3194 } 3195 removeHotplugEventListener(IHdmiHotplugEventListener listener)3196 private void removeHotplugEventListener(IHdmiHotplugEventListener listener) { 3197 synchronized (mLock) { 3198 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 3199 if (record.mListener.asBinder() == listener.asBinder()) { 3200 listener.asBinder().unlinkToDeath(record, 0); 3201 mHotplugEventListenerRecords.remove(record); 3202 break; 3203 } 3204 } 3205 } 3206 } 3207 addDeviceEventListener(IHdmiDeviceEventListener listener)3208 private void addDeviceEventListener(IHdmiDeviceEventListener listener) { 3209 DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener); 3210 try { 3211 listener.asBinder().linkToDeath(record, 0); 3212 } catch (RemoteException e) { 3213 Slog.w(TAG, "Listener already died"); 3214 return; 3215 } 3216 synchronized (mLock) { 3217 mDeviceEventListenerRecords.add(record); 3218 } 3219 } 3220 invokeDeviceEventListeners(HdmiDeviceInfo device, int status)3221 void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) { 3222 synchronized (mLock) { 3223 for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) { 3224 try { 3225 record.mListener.onStatusChanged(device, status); 3226 } catch (RemoteException e) { 3227 Slog.e(TAG, "Failed to report device event:" + e); 3228 } 3229 } 3230 } 3231 } 3232 addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener)3233 private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) { 3234 SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord( 3235 listener); 3236 try { 3237 listener.asBinder().linkToDeath(record, 0); 3238 } catch (RemoteException e) { 3239 Slog.w(TAG, "Listener already died"); 3240 return; 3241 } 3242 synchronized (mLock) { 3243 mSystemAudioModeChangeListenerRecords.add(record); 3244 } 3245 } 3246 removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener)3247 private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { 3248 synchronized (mLock) { 3249 for (SystemAudioModeChangeListenerRecord record : 3250 mSystemAudioModeChangeListenerRecords) { 3251 if (record.mListener.asBinder() == listener) { 3252 listener.asBinder().unlinkToDeath(record, 0); 3253 mSystemAudioModeChangeListenerRecords.remove(record); 3254 break; 3255 } 3256 } 3257 } 3258 } 3259 3260 private final class InputChangeListenerRecord implements IBinder.DeathRecipient { 3261 private final IHdmiInputChangeListener mListener; 3262 InputChangeListenerRecord(IHdmiInputChangeListener listener)3263 public InputChangeListenerRecord(IHdmiInputChangeListener listener) { 3264 mListener = listener; 3265 } 3266 3267 @Override binderDied()3268 public void binderDied() { 3269 synchronized (mLock) { 3270 if (mInputChangeListenerRecord == this) { 3271 mInputChangeListenerRecord = null; 3272 } 3273 } 3274 } 3275 } 3276 setInputChangeListener(IHdmiInputChangeListener listener)3277 private void setInputChangeListener(IHdmiInputChangeListener listener) { 3278 synchronized (mLock) { 3279 mInputChangeListenerRecord = new InputChangeListenerRecord(listener); 3280 try { 3281 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0); 3282 } catch (RemoteException e) { 3283 Slog.w(TAG, "Listener already died"); 3284 return; 3285 } 3286 } 3287 } 3288 invokeInputChangeListener(HdmiDeviceInfo info)3289 void invokeInputChangeListener(HdmiDeviceInfo info) { 3290 synchronized (mLock) { 3291 if (mInputChangeListenerRecord != null) { 3292 try { 3293 mInputChangeListenerRecord.mListener.onChanged(info); 3294 } catch (RemoteException e) { 3295 Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e); 3296 } 3297 } 3298 } 3299 } 3300 setHdmiRecordListener(IHdmiRecordListener listener)3301 private void setHdmiRecordListener(IHdmiRecordListener listener) { 3302 synchronized (mLock) { 3303 mRecordListenerRecord = new HdmiRecordListenerRecord(listener); 3304 try { 3305 listener.asBinder().linkToDeath(mRecordListenerRecord, 0); 3306 } catch (RemoteException e) { 3307 Slog.w(TAG, "Listener already died.", e); 3308 } 3309 } 3310 } 3311 invokeRecordRequestListener(int recorderAddress)3312 byte[] invokeRecordRequestListener(int recorderAddress) { 3313 synchronized (mLock) { 3314 if (mRecordListenerRecord != null) { 3315 try { 3316 return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress); 3317 } catch (RemoteException e) { 3318 Slog.w(TAG, "Failed to start record.", e); 3319 } 3320 } 3321 return EmptyArray.BYTE; 3322 } 3323 } 3324 invokeOneTouchRecordResult(int recorderAddress, int result)3325 void invokeOneTouchRecordResult(int recorderAddress, int result) { 3326 synchronized (mLock) { 3327 if (mRecordListenerRecord != null) { 3328 try { 3329 mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result); 3330 } catch (RemoteException e) { 3331 Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e); 3332 } 3333 } 3334 } 3335 } 3336 invokeTimerRecordingResult(int recorderAddress, int result)3337 void invokeTimerRecordingResult(int recorderAddress, int result) { 3338 synchronized (mLock) { 3339 if (mRecordListenerRecord != null) { 3340 try { 3341 mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result); 3342 } catch (RemoteException e) { 3343 Slog.w(TAG, "Failed to call onTimerRecordingResult.", e); 3344 } 3345 } 3346 } 3347 } 3348 invokeClearTimerRecordingResult(int recorderAddress, int result)3349 void invokeClearTimerRecordingResult(int recorderAddress, int result) { 3350 synchronized (mLock) { 3351 if (mRecordListenerRecord != null) { 3352 try { 3353 mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress, 3354 result); 3355 } catch (RemoteException e) { 3356 Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e); 3357 } 3358 } 3359 } 3360 } 3361 invokeCallback(IHdmiControlCallback callback, int result)3362 private void invokeCallback(IHdmiControlCallback callback, int result) { 3363 if (callback == null) { 3364 return; 3365 } 3366 try { 3367 callback.onComplete(result); 3368 } catch (RemoteException e) { 3369 Slog.e(TAG, "Invoking callback failed:" + e); 3370 } 3371 } 3372 invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, boolean enabled)3373 private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, 3374 boolean enabled) { 3375 try { 3376 listener.onStatusChanged(enabled); 3377 } catch (RemoteException e) { 3378 Slog.e(TAG, "Invoking callback failed:" + e); 3379 } 3380 } 3381 announceHotplugEvent(int portId, boolean connected)3382 private void announceHotplugEvent(int portId, boolean connected) { 3383 HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected); 3384 synchronized (mLock) { 3385 for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) { 3386 invokeHotplugEventListenerLocked(record.mListener, event); 3387 } 3388 } 3389 } 3390 invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event)3391 private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, 3392 HdmiHotplugEvent event) { 3393 try { 3394 listener.onReceived(event); 3395 } catch (RemoteException e) { 3396 Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e); 3397 } 3398 } 3399 announceHdmiControlStatusChange(@dmiControlManager.HdmiCecControl int isEnabled)3400 private void announceHdmiControlStatusChange(@HdmiControlManager.HdmiCecControl int isEnabled) { 3401 assertRunOnServiceThread(); 3402 synchronized (mLock) { 3403 List<IHdmiControlStatusChangeListener> listeners = new ArrayList<>( 3404 mHdmiControlStatusChangeListenerRecords.size()); 3405 for (HdmiControlStatusChangeListenerRecord record : 3406 mHdmiControlStatusChangeListenerRecords) { 3407 listeners.add(record.mListener); 3408 } 3409 invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled); 3410 } 3411 } 3412 invokeHdmiControlStatusChangeListenerLocked( IHdmiControlStatusChangeListener listener, @HdmiControlManager.HdmiCecControl int isEnabled)3413 private void invokeHdmiControlStatusChangeListenerLocked( 3414 IHdmiControlStatusChangeListener listener, 3415 @HdmiControlManager.HdmiCecControl int isEnabled) { 3416 invokeHdmiControlStatusChangeListenerLocked(Collections.singletonList(listener), isEnabled); 3417 } 3418 invokeHdmiControlStatusChangeListenerLocked( Collection<IHdmiControlStatusChangeListener> listeners, @HdmiControlManager.HdmiCecControl int isEnabled)3419 private void invokeHdmiControlStatusChangeListenerLocked( 3420 Collection<IHdmiControlStatusChangeListener> listeners, 3421 @HdmiControlManager.HdmiCecControl int isEnabled) { 3422 if (isEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) { 3423 queryDisplayStatus(new IHdmiControlCallback.Stub() { 3424 public void onComplete(int status) { 3425 mIsCecAvailable = status != HdmiControlManager.POWER_STATUS_UNKNOWN; 3426 if (!listeners.isEmpty()) { 3427 invokeHdmiControlStatusChangeListenerLocked(listeners, 3428 isEnabled, mIsCecAvailable); 3429 } 3430 } 3431 }); 3432 } else { 3433 mIsCecAvailable = false; 3434 if (!listeners.isEmpty()) { 3435 invokeHdmiControlStatusChangeListenerLocked(listeners, isEnabled, mIsCecAvailable); 3436 } 3437 } 3438 } 3439 invokeHdmiControlStatusChangeListenerLocked( Collection<IHdmiControlStatusChangeListener> listeners, @HdmiControlManager.HdmiCecControl int isEnabled, boolean isCecAvailable)3440 private void invokeHdmiControlStatusChangeListenerLocked( 3441 Collection<IHdmiControlStatusChangeListener> listeners, 3442 @HdmiControlManager.HdmiCecControl int isEnabled, 3443 boolean isCecAvailable) { 3444 for (IHdmiControlStatusChangeListener listener : listeners) { 3445 try { 3446 listener.onStatusChange(isEnabled, isCecAvailable); 3447 } catch (RemoteException e) { 3448 Slog.e(TAG, 3449 "Failed to report HdmiControlStatusChange: " + isEnabled + " isAvailable: " 3450 + isCecAvailable, e); 3451 } 3452 } 3453 } 3454 announceHdmiCecVolumeControlFeatureChange( @dmiControlManager.VolumeControl int hdmiCecVolumeControl)3455 private void announceHdmiCecVolumeControlFeatureChange( 3456 @HdmiControlManager.VolumeControl int hdmiCecVolumeControl) { 3457 assertRunOnServiceThread(); 3458 synchronized (mLock) { 3459 mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> { 3460 try { 3461 listener.onHdmiCecVolumeControlFeature(hdmiCecVolumeControl); 3462 } catch (RemoteException e) { 3463 Slog.e(TAG, 3464 "Failed to report HdmiControlVolumeControlStatusChange: " 3465 + hdmiCecVolumeControl); 3466 } 3467 }); 3468 } 3469 } 3470 tv()3471 public HdmiCecLocalDeviceTv tv() { 3472 return (HdmiCecLocalDeviceTv) mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_TV); 3473 } 3474 isTvDevice()3475 boolean isTvDevice() { 3476 return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV); 3477 } 3478 isAudioSystemDevice()3479 boolean isAudioSystemDevice() { 3480 return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 3481 } 3482 isPlaybackDevice()3483 boolean isPlaybackDevice() { 3484 return mCecLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK); 3485 } 3486 isSwitchDevice()3487 boolean isSwitchDevice() { 3488 return HdmiProperties.is_switch().orElse(false); 3489 } 3490 isTvDeviceEnabled()3491 boolean isTvDeviceEnabled() { 3492 return isTvDevice() && tv() != null; 3493 } 3494 playback()3495 protected HdmiCecLocalDevicePlayback playback() { 3496 return (HdmiCecLocalDevicePlayback) 3497 mHdmiCecNetwork.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK); 3498 } 3499 audioSystem()3500 public HdmiCecLocalDeviceAudioSystem audioSystem() { 3501 return (HdmiCecLocalDeviceAudioSystem) mHdmiCecNetwork.getLocalDevice( 3502 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 3503 } 3504 3505 /** 3506 * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. 3507 */ 3508 @Nullable getAudioManager()3509 AudioManagerWrapper getAudioManager() { 3510 return mAudioManager; 3511 } 3512 3513 /** 3514 * Returns null before the boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. 3515 */ 3516 @Nullable getAudioDeviceVolumeManager()3517 private AudioDeviceVolumeManagerWrapper getAudioDeviceVolumeManager() { 3518 return mAudioDeviceVolumeManager; 3519 } 3520 isCecControlEnabled()3521 boolean isCecControlEnabled() { 3522 synchronized (mLock) { 3523 return mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED; 3524 } 3525 } 3526 isEarcEnabled()3527 private boolean isEarcEnabled() { 3528 synchronized (mLock) { 3529 return mEarcEnabled; 3530 } 3531 } 3532 isEarcSupported()3533 private boolean isEarcSupported() { 3534 synchronized (mLock) { 3535 return mEarcSupported; 3536 } 3537 } 3538 3539 @ServiceThreadOnly getPowerStatus()3540 int getPowerStatus() { 3541 assertRunOnServiceThread(); 3542 return mPowerStatusController.getPowerStatus(); 3543 } 3544 3545 @ServiceThreadOnly 3546 @VisibleForTesting setPowerStatus(int powerStatus)3547 void setPowerStatus(int powerStatus) { 3548 assertRunOnServiceThread(); 3549 mPowerStatusController.setPowerStatus(powerStatus); 3550 } 3551 3552 @ServiceThreadOnly isPowerOnOrTransient()3553 boolean isPowerOnOrTransient() { 3554 assertRunOnServiceThread(); 3555 return mPowerStatusController.isPowerStatusOn() 3556 || mPowerStatusController.isPowerStatusTransientToOn(); 3557 } 3558 3559 @ServiceThreadOnly isPowerStandbyOrTransient()3560 boolean isPowerStandbyOrTransient() { 3561 assertRunOnServiceThread(); 3562 return mPowerStatusController.isPowerStatusStandby() 3563 || mPowerStatusController.isPowerStatusTransientToStandby(); 3564 } 3565 3566 @ServiceThreadOnly isPowerStandby()3567 boolean isPowerStandby() { 3568 assertRunOnServiceThread(); 3569 return mPowerStatusController.isPowerStatusStandby(); 3570 } 3571 3572 @ServiceThreadOnly wakeUp()3573 void wakeUp() { 3574 assertRunOnServiceThread(); 3575 mWakeUpMessageReceived = true; 3576 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI, 3577 "android.server.hdmi:WAKE"); 3578 // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets 3579 // the intent, the sequence will continue at onWakeUp(). 3580 } 3581 3582 @ServiceThreadOnly standby()3583 void standby() { 3584 assertRunOnServiceThread(); 3585 if (!canGoToStandby()) { 3586 return; 3587 } 3588 mStandbyMessageReceived = true; 3589 mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 3590 // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets 3591 // the intent, the sequence will continue at onStandby(). 3592 } 3593 isWakeUpMessageReceived()3594 boolean isWakeUpMessageReceived() { 3595 return mWakeUpMessageReceived; 3596 } 3597 3598 @VisibleForTesting isStandbyMessageReceived()3599 protected boolean isStandbyMessageReceived() { 3600 return mStandbyMessageReceived; 3601 } 3602 3603 @ServiceThreadOnly 3604 @VisibleForTesting onWakeUp(@akeReason final int wakeUpAction)3605 protected void onWakeUp(@WakeReason final int wakeUpAction) { 3606 assertRunOnServiceThread(); 3607 mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON, 3608 false); 3609 if (mCecController != null) { 3610 if (isCecControlEnabled()) { 3611 int startReason = -1; 3612 switch (wakeUpAction) { 3613 case WAKE_UP_SCREEN_ON: 3614 startReason = INITIATED_BY_SCREEN_ON; 3615 if (mWakeUpMessageReceived) { 3616 startReason = INITIATED_BY_WAKE_UP_MESSAGE; 3617 } 3618 break; 3619 case WAKE_UP_BOOT_UP: 3620 startReason = INITIATED_BY_BOOT_UP; 3621 break; 3622 default: 3623 Slog.e(TAG, "wakeUpAction " + wakeUpAction + " not defined."); 3624 return; 3625 3626 } 3627 initializeCec(startReason); 3628 } 3629 } else { 3630 Slog.i(TAG, "Device does not support HDMI-CEC."); 3631 } 3632 if (isEarcSupported()) { 3633 if (isEarcEnabled()) { 3634 int startReason = -1; 3635 switch (wakeUpAction) { 3636 case WAKE_UP_SCREEN_ON: 3637 startReason = INITIATED_BY_SCREEN_ON; 3638 break; 3639 case WAKE_UP_BOOT_UP: 3640 startReason = INITIATED_BY_BOOT_UP; 3641 break; 3642 default: 3643 Slog.e(TAG, "wakeUpAction " + wakeUpAction + " not defined."); 3644 return; 3645 } 3646 initializeEarc(startReason); 3647 } else { 3648 setEarcEnabledInHal(false, false); 3649 } 3650 } 3651 // TODO: Initialize MHL local devices. 3652 } 3653 3654 @ServiceThreadOnly 3655 @VisibleForTesting onStandby(final int standbyAction)3656 protected void onStandby(final int standbyAction) { 3657 mWakeUpMessageReceived = false; 3658 assertRunOnServiceThread(); 3659 mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY, 3660 false); 3661 invokeVendorCommandListenersOnControlStateChanged(false, 3662 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY); 3663 3664 final List<HdmiCecLocalDevice> devices = getAllCecLocalDevices(); 3665 3666 if (!isStandbyMessageReceived() && !canGoToStandby()) { 3667 mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY); 3668 for (HdmiCecLocalDevice device : devices) { 3669 device.onStandby(mStandbyMessageReceived, standbyAction); 3670 } 3671 return; 3672 } 3673 3674 disableCecLocalDevices(new PendingActionClearedCallback() { 3675 @Override 3676 public void onCleared(HdmiCecLocalDevice device) { 3677 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType); 3678 devices.remove(device); 3679 if (devices.isEmpty()) { 3680 onPendingActionsCleared(standbyAction); 3681 // We will not clear local devices here, since some OEM/SOC will keep passing 3682 // the received packets until the application processor enters to the sleep 3683 // actually. 3684 } 3685 } 3686 }); 3687 } 3688 canGoToStandby()3689 boolean canGoToStandby() { 3690 for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { 3691 if (!device.canGoToStandby()) return false; 3692 } 3693 return true; 3694 } 3695 3696 @ServiceThreadOnly onLanguageChanged(String language)3697 private void onLanguageChanged(String language) { 3698 assertRunOnServiceThread(); 3699 mMenuLanguage = language; 3700 3701 if (isTvDeviceEnabled()) { 3702 tv().broadcastMenuLanguage(language); 3703 mCecController.setLanguage(language); 3704 } 3705 } 3706 3707 /** 3708 * Gets the CEC menu language. 3709 * 3710 * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu 3711 * Language>}. 3712 * See HDMI 1.4b section CEC 13.6.2 3713 * 3714 * @see {@link Locale#getISO3Language()} 3715 */ 3716 @ServiceThreadOnly getLanguage()3717 String getLanguage() { 3718 assertRunOnServiceThread(); 3719 return mMenuLanguage; 3720 } 3721 disableCecLocalDevices(PendingActionClearedCallback callback)3722 private void disableCecLocalDevices(PendingActionClearedCallback callback) { 3723 if (mCecController != null) { 3724 for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { 3725 device.disableDevice(mStandbyMessageReceived, callback); 3726 } 3727 } 3728 mMhlController.clearAllLocalDevices(); 3729 } 3730 3731 @ServiceThreadOnly 3732 @VisibleForTesting clearCecLocalDevices()3733 protected void clearCecLocalDevices() { 3734 assertRunOnServiceThread(); 3735 if (mCecController == null) { 3736 return; 3737 } 3738 mCecController.clearLogicalAddress(); 3739 mHdmiCecNetwork.clearLocalDevices(); 3740 } 3741 3742 /** 3743 * Normally called after all devices have cleared their pending actions, to execute the final 3744 * phase of the standby flow. 3745 * 3746 * This can also be called during wakeup, when pending actions are cleared after failing to be 3747 * cleared during standby. In this case, it does not execute the standby flow. 3748 */ 3749 @ServiceThreadOnly onPendingActionsCleared(int standbyAction)3750 private void onPendingActionsCleared(int standbyAction) { 3751 assertRunOnServiceThread(); 3752 Slog.v(TAG, "onPendingActionsCleared"); 3753 3754 if (mPowerStatusController.isPowerStatusTransientToStandby()) { 3755 mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_STANDBY); 3756 for (HdmiCecLocalDevice device : mHdmiCecNetwork.getLocalDeviceList()) { 3757 device.onStandby(mStandbyMessageReceived, standbyAction); 3758 } 3759 if (!isAudioSystemDevice()) { 3760 mCecController.enableSystemCecControl(false); 3761 mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED); 3762 } 3763 } 3764 3765 // Always reset this flag to set up for the next standby 3766 mStandbyMessageReceived = false; 3767 } 3768 3769 @VisibleForTesting addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId)3770 void addVendorCommandListener(IHdmiVendorCommandListener listener, int vendorId) { 3771 VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, vendorId); 3772 try { 3773 listener.asBinder().linkToDeath(record, 0); 3774 } catch (RemoteException e) { 3775 Slog.w(TAG, "Listener already died"); 3776 return; 3777 } 3778 synchronized (mLock) { 3779 mVendorCommandListenerRecords.add(record); 3780 } 3781 } 3782 invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)3783 boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, 3784 byte[] params, boolean hasVendorId) { 3785 synchronized (mLock) { 3786 if (mVendorCommandListenerRecords.isEmpty()) { 3787 return false; 3788 } 3789 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 3790 if (hasVendorId) { 3791 int vendorId = 3792 ((params[0] & 0xFF) << 16) 3793 + ((params[1] & 0xFF) << 8) 3794 + (params[2] & 0xFF); 3795 if (record.mVendorId != vendorId) { 3796 continue; 3797 } 3798 } 3799 try { 3800 record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId); 3801 return true; 3802 } catch (RemoteException e) { 3803 Slog.e(TAG, "Failed to notify vendor command reception", e); 3804 } 3805 } 3806 return false; 3807 } 3808 } 3809 invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)3810 boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) { 3811 synchronized (mLock) { 3812 if (mVendorCommandListenerRecords.isEmpty()) { 3813 return false; 3814 } 3815 for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) { 3816 try { 3817 record.mListener.onControlStateChanged(enabled, reason); 3818 } catch (RemoteException e) { 3819 Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e); 3820 } 3821 } 3822 return true; 3823 } 3824 } 3825 addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener)3826 private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { 3827 HdmiMhlVendorCommandListenerRecord record = 3828 new HdmiMhlVendorCommandListenerRecord(listener); 3829 try { 3830 listener.asBinder().linkToDeath(record, 0); 3831 } catch (RemoteException e) { 3832 Slog.w(TAG, "Listener already died."); 3833 return; 3834 } 3835 3836 synchronized (mLock) { 3837 mMhlVendorCommandListenerRecords.add(record); 3838 } 3839 } 3840 invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)3841 void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) { 3842 synchronized (mLock) { 3843 for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) { 3844 try { 3845 record.mListener.onReceived(portId, offest, length, data); 3846 } catch (RemoteException e) { 3847 Slog.e(TAG, "Failed to notify MHL vendor command", e); 3848 } 3849 } 3850 } 3851 } 3852 setStandbyMode(boolean isStandbyModeOn)3853 void setStandbyMode(boolean isStandbyModeOn) { 3854 assertRunOnServiceThread(); 3855 if (isPowerOnOrTransient() && isStandbyModeOn) { 3856 mPowerManager.goToSleep(SystemClock.uptimeMillis(), 3857 PowerManager.GO_TO_SLEEP_REASON_HDMI, 0); 3858 if (playback() != null) { 3859 playback().sendStandby(0 /* unused */); 3860 } 3861 } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) { 3862 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI, 3863 "android.server.hdmi:WAKE"); 3864 if (playback() != null) { 3865 oneTouchPlay(new IHdmiControlCallback.Stub() { 3866 @Override 3867 public void onComplete(int result) { 3868 if (result != HdmiControlManager.RESULT_SUCCESS) { 3869 Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result); 3870 } 3871 } 3872 }); 3873 } 3874 } 3875 } 3876 3877 @HdmiControlManager.VolumeControl getHdmiCecVolumeControl()3878 int getHdmiCecVolumeControl() { 3879 synchronized (mLock) { 3880 return mHdmiCecVolumeControl; 3881 } 3882 } 3883 isProhibitMode()3884 boolean isProhibitMode() { 3885 synchronized (mLock) { 3886 return mProhibitMode; 3887 } 3888 } 3889 setProhibitMode(boolean enabled)3890 void setProhibitMode(boolean enabled) { 3891 synchronized (mLock) { 3892 mProhibitMode = enabled; 3893 } 3894 } 3895 isSystemAudioActivated()3896 boolean isSystemAudioActivated() { 3897 synchronized (mLock) { 3898 return mSystemAudioActivated; 3899 } 3900 } 3901 setSystemAudioActivated(boolean on)3902 void setSystemAudioActivated(boolean on) { 3903 synchronized (mLock) { 3904 mSystemAudioActivated = on; 3905 } 3906 runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior); 3907 } 3908 3909 @ServiceThreadOnly setCecEnabled(@dmiControlManager.HdmiCecControl int enabled)3910 void setCecEnabled(@HdmiControlManager.HdmiCecControl int enabled) { 3911 assertRunOnServiceThread(); 3912 3913 synchronized (mLock) { 3914 mHdmiControlEnabled = enabled; 3915 } 3916 3917 if (enabled == HDMI_CEC_CONTROL_ENABLED) { 3918 onEnableCec(); 3919 setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue( 3920 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE)); 3921 return; 3922 } 3923 3924 setHdmiCecVolumeControlEnabledInternal(HdmiControlManager.VOLUME_CONTROL_DISABLED); 3925 // Call the vendor handler before the service is disabled. 3926 invokeVendorCommandListenersOnControlStateChanged(false, 3927 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING); 3928 // Post the remained tasks in the service thread again to give the vendor-issued-tasks 3929 // a chance to run. 3930 runOnServiceThread(new Runnable() { 3931 @Override 3932 public void run() { 3933 onDisableCec(); 3934 } 3935 }); 3936 announceHdmiControlStatusChange(enabled); 3937 3938 return; 3939 } 3940 3941 @ServiceThreadOnly onEnableCec()3942 private void onEnableCec() { 3943 mCecController.enableCec(true); 3944 mCecController.enableSystemCecControl(true); 3945 mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED); 3946 3947 initializeCec(INITIATED_BY_ENABLE_CEC); 3948 } 3949 3950 @ServiceThreadOnly onDisableCec()3951 private void onDisableCec() { 3952 disableCecLocalDevices( 3953 new PendingActionClearedCallback() { 3954 @Override 3955 public void onCleared(HdmiCecLocalDevice device) { 3956 assertRunOnServiceThread(); 3957 mCecController.flush( 3958 new Runnable() { 3959 @Override 3960 public void run() { 3961 mCecController.enableCec(false); 3962 mCecController.enableSystemCecControl(false); 3963 mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED); 3964 clearCecLocalDevices(); 3965 } 3966 }); 3967 } 3968 }); 3969 } 3970 3971 @ServiceThreadOnly setActivePortId(int portId)3972 void setActivePortId(int portId) { 3973 assertRunOnServiceThread(); 3974 mActivePortId = portId; 3975 3976 // Resets last input for MHL, which stays valid only after the MHL device was selected, 3977 // and no further switching is done. 3978 setLastInputForMhl(Constants.INVALID_PORT_ID); 3979 } 3980 getLocalActiveSource()3981 ActiveSource getLocalActiveSource() { 3982 synchronized (mLock) { 3983 return mActiveSource; 3984 } 3985 } 3986 3987 @VisibleForTesting pauseActiveMediaSessions()3988 void pauseActiveMediaSessions() { 3989 MediaSessionManager mediaSessionManager = getContext() 3990 .getSystemService(MediaSessionManager.class); 3991 List<MediaController> mediaControllers = mediaSessionManager.getActiveSessions(null); 3992 for (MediaController mediaController : mediaControllers) { 3993 mediaController.getTransportControls().pause(); 3994 } 3995 } 3996 setActiveSource(int logicalAddress, int physicalAddress, String caller)3997 void setActiveSource(int logicalAddress, int physicalAddress, String caller) { 3998 synchronized (mLock) { 3999 mActiveSource.logicalAddress = logicalAddress; 4000 mActiveSource.physicalAddress = physicalAddress; 4001 } 4002 4003 getAtomWriter().activeSourceChanged(logicalAddress, physicalAddress, 4004 HdmiUtils.pathRelationship(getPhysicalAddress(), physicalAddress)); 4005 4006 // If the current device is a source device, check if the current Active Source matches 4007 // the local device info. 4008 for (HdmiCecLocalDevice device : getAllCecLocalDevices()) { 4009 boolean deviceIsActiveSource = 4010 logicalAddress == device.getDeviceInfo().getLogicalAddress() 4011 && physicalAddress == getPhysicalAddress(); 4012 4013 device.addActiveSourceHistoryItem(new ActiveSource(logicalAddress, physicalAddress), 4014 deviceIsActiveSource, caller); 4015 } 4016 4017 runOnServiceThread(this::checkAndUpdateAbsoluteVolumeBehavior); 4018 } 4019 4020 // This method should only be called when the device can be the active source 4021 // and all the device types call into this method. 4022 // For example, when receiving broadcast messages, all the device types will call this 4023 // method but only one of them will be the Active Source. setAndBroadcastActiveSource( int physicalAddress, int deviceType, int source, String caller)4024 protected void setAndBroadcastActiveSource( 4025 int physicalAddress, int deviceType, int source, String caller) { 4026 // If the device has both playback and audio system logical addresses, 4027 // playback will claim active source. Otherwise audio system will. 4028 if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) { 4029 HdmiCecLocalDevicePlayback playback = playback(); 4030 playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress, 4031 caller); 4032 playback.wakeUpIfActiveSource(); 4033 playback.maySendActiveSource(source); 4034 } 4035 4036 if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 4037 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 4038 if (playback() == null) { 4039 audioSystem.setActiveSource(audioSystem.getDeviceInfo().getLogicalAddress(), 4040 physicalAddress, caller); 4041 audioSystem.wakeUpIfActiveSource(); 4042 audioSystem.maySendActiveSource(source); 4043 } 4044 } 4045 } 4046 4047 // This method should only be called when the device can be the active source 4048 // and only one of the device types calls into this method. 4049 // For example, when receiving One Touch Play, only playback device handles it 4050 // and this method updates Active Source in all the device types sharing the same 4051 // Physical Address. setAndBroadcastActiveSourceFromOneDeviceType( int sourceAddress, int physicalAddress, String caller)4052 protected void setAndBroadcastActiveSourceFromOneDeviceType( 4053 int sourceAddress, int physicalAddress, String caller) { 4054 // If the device has both playback and audio system logical addresses, 4055 // playback will claim active source. Otherwise audio system will. 4056 HdmiCecLocalDevicePlayback playback = playback(); 4057 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem(); 4058 if (playback != null) { 4059 playback.setActiveSource(playback.getDeviceInfo().getLogicalAddress(), physicalAddress, 4060 caller); 4061 playback.wakeUpIfActiveSource(); 4062 playback.maySendActiveSource(sourceAddress); 4063 } else if (audioSystem != null) { 4064 audioSystem.setActiveSource(audioSystem.getDeviceInfo().getLogicalAddress(), 4065 physicalAddress, caller); 4066 audioSystem.wakeUpIfActiveSource(); 4067 audioSystem.maySendActiveSource(sourceAddress); 4068 } 4069 } 4070 4071 @ServiceThreadOnly setLastInputForMhl(int portId)4072 void setLastInputForMhl(int portId) { 4073 assertRunOnServiceThread(); 4074 mLastInputMhl = portId; 4075 } 4076 4077 @ServiceThreadOnly getLastInputForMhl()4078 int getLastInputForMhl() { 4079 assertRunOnServiceThread(); 4080 return mLastInputMhl; 4081 } 4082 4083 /** 4084 * Performs input change, routing control for MHL device. 4085 * 4086 * @param portId MHL port, or the last port to go back to if {@code contentOn} is false 4087 * @param contentOn {@code true} if RAP data is content on; otherwise false 4088 */ 4089 @ServiceThreadOnly changeInputForMhl(int portId, boolean contentOn)4090 void changeInputForMhl(int portId, boolean contentOn) { 4091 assertRunOnServiceThread(); 4092 if (tv() == null) return; 4093 final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID; 4094 if (portId != Constants.INVALID_PORT_ID) { 4095 tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() { 4096 @Override 4097 public void onComplete(int result) throws RemoteException { 4098 // Keep the last input to switch back later when RAP[ContentOff] is received. 4099 // This effectively sets the port to invalid one if the switching is for 4100 // RAP[ContentOff]. 4101 setLastInputForMhl(lastInput); 4102 } 4103 }); 4104 } 4105 // MHL device is always directly connected to the port. Update the active port ID to avoid 4106 // unnecessary post-routing control task. 4107 tv().setActivePortId(portId); 4108 4109 // The port is either the MHL-enabled port where the mobile device is connected, or 4110 // the last port to go back to when turnoff command is received. Note that the last port 4111 // may not be the MHL-enabled one. In this case the device info to be passed to 4112 // input change listener should be the one describing the corresponding HDMI port. 4113 HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId); 4114 HdmiDeviceInfo info = (device != null) ? device.getInfo() 4115 : mHdmiCecNetwork.getDeviceForPortId(portId); 4116 invokeInputChangeListener(info); 4117 } 4118 setMhlInputChangeEnabled(boolean enabled)4119 void setMhlInputChangeEnabled(boolean enabled) { 4120 mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled)); 4121 4122 synchronized (mLock) { 4123 mMhlInputChangeEnabled = enabled; 4124 } 4125 } 4126 4127 @VisibleForTesting getAtomWriter()4128 protected HdmiCecAtomWriter getAtomWriter() { 4129 return mAtomWriter; 4130 } 4131 isMhlInputChangeEnabled()4132 boolean isMhlInputChangeEnabled() { 4133 synchronized (mLock) { 4134 return mMhlInputChangeEnabled; 4135 } 4136 } 4137 4138 @ServiceThreadOnly displayOsd(int messageId)4139 void displayOsd(int messageId) { 4140 assertRunOnServiceThread(); 4141 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 4142 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 4143 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 4144 HdmiControlService.PERMISSION); 4145 } 4146 4147 @ServiceThreadOnly displayOsd(int messageId, int extra)4148 void displayOsd(int messageId, int extra) { 4149 assertRunOnServiceThread(); 4150 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 4151 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 4152 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra); 4153 getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 4154 HdmiControlService.PERMISSION); 4155 } 4156 4157 @VisibleForTesting getHdmiCecConfig()4158 protected HdmiCecConfig getHdmiCecConfig() { 4159 return mHdmiCecConfig; 4160 } 4161 4162 private HdmiCecConfig.SettingChangeListener mSettingChangeListener = 4163 new HdmiCecConfig.SettingChangeListener() { 4164 @Override 4165 public void onChange(String name) { 4166 synchronized (mLock) { 4167 if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) { 4168 return; 4169 } 4170 mHdmiCecSettingChangeListenerRecords.get(name).broadcast(listener -> { 4171 invokeCecSettingChangeListenerLocked(name, listener); 4172 }); 4173 } 4174 } 4175 }; 4176 addCecSettingChangeListener(String name, final IHdmiCecSettingChangeListener listener)4177 private void addCecSettingChangeListener(String name, 4178 final IHdmiCecSettingChangeListener listener) { 4179 synchronized (mLock) { 4180 if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) { 4181 mHdmiCecSettingChangeListenerRecords.put(name, new RemoteCallbackList<>()); 4182 mHdmiCecConfig.registerChangeListener(name, mSettingChangeListener); 4183 } 4184 mHdmiCecSettingChangeListenerRecords.get(name).register(listener); 4185 } 4186 } 4187 removeCecSettingChangeListener(String name, final IHdmiCecSettingChangeListener listener)4188 private void removeCecSettingChangeListener(String name, 4189 final IHdmiCecSettingChangeListener listener) { 4190 synchronized (mLock) { 4191 if (!mHdmiCecSettingChangeListenerRecords.containsKey(name)) { 4192 return; 4193 } 4194 mHdmiCecSettingChangeListenerRecords.get(name).unregister(listener); 4195 if (mHdmiCecSettingChangeListenerRecords.get(name).getRegisteredCallbackCount() == 0) { 4196 mHdmiCecSettingChangeListenerRecords.remove(name); 4197 mHdmiCecConfig.removeChangeListener(name, mSettingChangeListener); 4198 } 4199 } 4200 } 4201 invokeCecSettingChangeListenerLocked(String name, final IHdmiCecSettingChangeListener listener)4202 private void invokeCecSettingChangeListenerLocked(String name, 4203 final IHdmiCecSettingChangeListener listener) { 4204 try { 4205 listener.onChange(name); 4206 } catch (RemoteException e) { 4207 Slog.e(TAG, "Failed to report setting change", e); 4208 } 4209 } 4210 4211 /** 4212 * Listener for changes to the volume behavior of an audio output device. Caches the 4213 * volume behavior of devices used for absolute volume behavior. 4214 */ 4215 @VisibleForTesting 4216 @ServiceThreadOnly onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior)4217 void onDeviceVolumeBehaviorChanged(AudioDeviceAttributes device, int volumeBehavior) { 4218 assertRunOnServiceThread(); 4219 if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) { 4220 synchronized (mLock) { 4221 mAudioDeviceVolumeBehaviors.put(device, volumeBehavior); 4222 } 4223 checkAndUpdateAbsoluteVolumeBehavior(); 4224 } 4225 } 4226 4227 /** 4228 * Wrapper for {@link AudioManager#getDeviceVolumeBehavior} that takes advantage of cached 4229 * results for the volume behaviors of HDMI audio devices. 4230 */ 4231 @AudioManager.DeviceVolumeBehavior getDeviceVolumeBehavior(AudioDeviceAttributes device)4232 private int getDeviceVolumeBehavior(AudioDeviceAttributes device) { 4233 if (AVB_AUDIO_OUTPUT_DEVICES.contains(device)) { 4234 synchronized (mLock) { 4235 if (mAudioDeviceVolumeBehaviors.containsKey(device)) { 4236 return mAudioDeviceVolumeBehaviors.get(device); 4237 } 4238 } 4239 } 4240 return getAudioManager().getDeviceVolumeBehavior(device); 4241 } 4242 4243 /** 4244 * Returns whether absolute volume behavior is enabled or not. This is determined by the 4245 * volume behavior of the relevant HDMI audio output device(s) for this device's type. 4246 */ isAbsoluteVolumeBehaviorEnabled()4247 public boolean isAbsoluteVolumeBehaviorEnabled() { 4248 if (!isTvDevice() && !isPlaybackDevice()) { 4249 return false; 4250 } 4251 AudioDeviceAttributes avbAudioOutputDevice = getAvbAudioOutputDevice(); 4252 if (avbAudioOutputDevice == null) { 4253 return false; 4254 } 4255 4256 @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior = 4257 getDeviceVolumeBehavior(avbAudioOutputDevice); 4258 4259 return deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE 4260 || deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY; 4261 } 4262 getAvbAudioOutputDevice()4263 private AudioDeviceAttributes getAvbAudioOutputDevice() { 4264 if (tv() != null) { 4265 return tv().getSystemAudioOutputDevice(); 4266 } else if (playback() != null) { 4267 return AUDIO_OUTPUT_DEVICE_HDMI; 4268 } else { 4269 return null; 4270 } 4271 } 4272 4273 /** 4274 * This method is responsible for adopting or disabling absolute volume behavior and 4275 * adjust-only absolute volume behavior in AudioService. These volume behaviors are adopted on 4276 * specific audio output devices: HDMI for playback devices, and HDMI_ARC or HDMI_EARC for TVs. 4277 * 4278 * This method enables absolute volume behavior on a Playback device or TV panel when it is 4279 * playing audio on an external device (the System Audio device) that supports the feature. 4280 * This allows the volume level of the System Audio device to be tracked and set by Android. 4281 * 4282 * Absolute volume behavior requires the following conditions: 4283 * 1. If the System Audio Device is an Audio System: System Audio Mode is active 4284 * 2. Our HDMI audio output device is using full volume behavior 4285 * 3. CEC volume is enabled 4286 * 4. The System Audio device supports the <Set Audio Volume Level> message 4287 * 4288 * This method enables adjust-only absolute volume behavior on TV panels when conditions 4289 * 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of 4290 * the System Audio device and display numeric volume UI for it, even if the System Audio device 4291 * does not support <Set Audio Volume Level>. 4292 */ 4293 @ServiceThreadOnly checkAndUpdateAbsoluteVolumeBehavior()4294 void checkAndUpdateAbsoluteVolumeBehavior() { 4295 assertRunOnServiceThread(); 4296 4297 // Can't set volume behavior before we have access to system services 4298 if (getAudioManager() == null) { 4299 return; 4300 } 4301 4302 HdmiCecLocalDevice localCecDevice; 4303 if (isTvDevice() && tv() != null) { 4304 localCecDevice = tv(); 4305 // Condition 1: TVs need System Audio Mode to be active 4306 // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the 4307 // TV is the System Audio Device instead.) 4308 if (!isSystemAudioActivated()) { 4309 switchToFullVolumeBehavior(); 4310 return; 4311 } 4312 } else if (isPlaybackDevice() && playback() != null) { 4313 localCecDevice = playback(); 4314 } else { 4315 // Either this device type doesn't support AVB, or it hasn't fully initialized yet 4316 return; 4317 } 4318 4319 HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo( 4320 localCecDevice.findAudioReceiverAddress()); 4321 @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior = 4322 getDeviceVolumeBehavior(getAvbAudioOutputDevice()); 4323 4324 // Condition 2: Already using full or absolute volume behavior 4325 boolean alreadyUsingFullOrAbsoluteVolume = 4326 (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL) 4327 || (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) 4328 || (currentVolumeBehavior 4329 == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); 4330 // Condition 3: CEC volume is enabled 4331 boolean cecVolumeEnabled = 4332 getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED; 4333 4334 if (!cecVolumeEnabled || !alreadyUsingFullOrAbsoluteVolume) { 4335 switchToFullVolumeBehavior(); 4336 return; 4337 } 4338 4339 // Check for safety: if the System Audio device is a candidate for AVB, we should already 4340 // have received messages from it to trigger the other conditions. 4341 if (systemAudioDeviceInfo == null) { 4342 switchToFullVolumeBehavior(); 4343 return; 4344 } 4345 4346 // Condition 4: The System Audio device supports <Set Audio Volume Level> 4347 switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) { 4348 case DeviceFeatures.FEATURE_SUPPORTED: 4349 if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { 4350 // Start an action that will call enableAbsoluteVolumeBehavior 4351 // once the System Audio device sends <Report Audio Status> 4352 localCecDevice.startNewAvbAudioStatusAction( 4353 systemAudioDeviceInfo.getLogicalAddress()); 4354 } 4355 return; 4356 case DeviceFeatures.FEATURE_NOT_SUPPORTED: 4357 // TVs may adopt adjust-only absolute volume behavior if condition 4 isn't met. 4358 // This allows the device to display numeric volume UI for the System Audio device. 4359 if (tv() != null && mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled) { 4360 if (currentVolumeBehavior 4361 != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) { 4362 // If we're currently using absolute volume behavior, switch to full volume 4363 // behavior until we successfully adopt adjust-only absolute volume behavior 4364 if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { 4365 getAudioManager().setDeviceVolumeBehavior(getAvbAudioOutputDevice(), 4366 AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); 4367 } 4368 // Start an action that will call enableAbsoluteVolumeBehavior 4369 // once the System Audio device sends <Report Audio Status> 4370 localCecDevice.startNewAvbAudioStatusAction( 4371 systemAudioDeviceInfo.getLogicalAddress()); 4372 } 4373 } else { 4374 switchToFullVolumeBehavior(); 4375 } 4376 return; 4377 case DeviceFeatures.FEATURE_SUPPORT_UNKNOWN: 4378 if (currentVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) { 4379 switchToFullVolumeBehavior(); 4380 } 4381 localCecDevice.querySetAudioVolumeLevelSupport( 4382 systemAudioDeviceInfo.getLogicalAddress()); 4383 } 4384 } 4385 4386 /** 4387 * Switches to full volume behavior, if either absolute or adjust-only absolute volume behavior 4388 * are currently used. Removes the action for handling volume updates for these behaviors. 4389 */ switchToFullVolumeBehavior()4390 private void switchToFullVolumeBehavior() { 4391 if (playback() != null) { 4392 playback().removeAvbAudioStatusAction(); 4393 } else if (tv() != null) { 4394 tv().removeAvbAudioStatusAction(); 4395 } 4396 AudioDeviceAttributes device = getAvbAudioOutputDevice(); 4397 int volumeBehavior = getDeviceVolumeBehavior(device); 4398 if (volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE 4399 || volumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY) { 4400 getAudioManager().setDeviceVolumeBehavior(device, 4401 AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL); 4402 } 4403 } 4404 4405 /** 4406 * Enables absolute volume behavior or adjust-only absolute volume behavior. Should only be 4407 * called when the conditions for one of these behaviors is met - 4408 * see {@link #checkAndUpdateAbsoluteVolumeBehavior}. 4409 * 4410 * @param audioStatus The initial audio status to set the audio output device to 4411 */ enableAbsoluteVolumeBehavior(AudioStatus audioStatus)4412 void enableAbsoluteVolumeBehavior(AudioStatus audioStatus) { 4413 HdmiCecLocalDevice localDevice = isPlaybackDevice() ? playback() : tv(); 4414 HdmiDeviceInfo systemAudioDevice = getDeviceInfo(localDevice.findAudioReceiverAddress()); 4415 VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC) 4416 .setMuted(audioStatus.getMute()) 4417 .setVolumeIndex(audioStatus.getVolume()) 4418 .setMaxVolumeIndex(AudioStatus.MAX_VOLUME) 4419 .setMinVolumeIndex(AudioStatus.MIN_VOLUME) 4420 .build(); 4421 mAbsoluteVolumeChangedListener = new AbsoluteVolumeChangedListener( 4422 localDevice, systemAudioDevice); 4423 4424 // AudioService sets the volume of the stream and device based on the input VolumeInfo 4425 // when enabling absolute volume behavior, but not the mute state 4426 notifyAvbMuteChange(audioStatus.getMute()); 4427 4428 // If <Set Audio Volume Level> is supported, enable absolute volume behavior. 4429 // Otherwise, enable adjust-only AVB on TVs only. 4430 if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport() 4431 == DeviceFeatures.FEATURE_SUPPORTED) { 4432 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior( 4433 getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor, 4434 mAbsoluteVolumeChangedListener, true); 4435 } else if (tv() != null) { 4436 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior( 4437 getAvbAudioOutputDevice(), volumeInfo, mServiceThreadExecutor, 4438 mAbsoluteVolumeChangedListener, true); 4439 } 4440 4441 } 4442 4443 private AbsoluteVolumeChangedListener mAbsoluteVolumeChangedListener; 4444 4445 @VisibleForTesting getAbsoluteVolumeChangedListener()4446 AbsoluteVolumeChangedListener getAbsoluteVolumeChangedListener() { 4447 return mAbsoluteVolumeChangedListener; 4448 } 4449 4450 /** 4451 * Listeners for changes reported by AudioService to the state of an audio output device using 4452 * absolute volume behavior. 4453 */ 4454 @VisibleForTesting 4455 class AbsoluteVolumeChangedListener implements 4456 AudioDeviceVolumeManager.OnAudioDeviceVolumeChangedListener { 4457 private HdmiCecLocalDevice mLocalDevice; 4458 private HdmiDeviceInfo mSystemAudioDevice; 4459 AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice, HdmiDeviceInfo systemAudioDevice)4460 private AbsoluteVolumeChangedListener(HdmiCecLocalDevice localDevice, 4461 HdmiDeviceInfo systemAudioDevice) { 4462 mLocalDevice = localDevice; 4463 mSystemAudioDevice = systemAudioDevice; 4464 } 4465 4466 /** 4467 * Called when AudioService sets the volume level of an absolute volume audio output device 4468 * to a numeric value. 4469 */ 4470 @Override onAudioDeviceVolumeChanged( @onNull AudioDeviceAttributes audioDevice, @NonNull VolumeInfo volumeInfo)4471 public void onAudioDeviceVolumeChanged( 4472 @NonNull AudioDeviceAttributes audioDevice, 4473 @NonNull VolumeInfo volumeInfo) { 4474 4475 // Do nothing if the System Audio device does not support <Set Audio Volume Level> 4476 if (mSystemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport() 4477 != DeviceFeatures.FEATURE_SUPPORTED) { 4478 return; 4479 } 4480 4481 // Send <Set Audio Volume Level> to notify the System Audio device of the volume change 4482 int localDeviceAddress = mLocalDevice.getDeviceInfo().getLogicalAddress(); 4483 sendCecCommand(SetAudioVolumeLevelMessage.build( 4484 localDeviceAddress, 4485 mSystemAudioDevice.getLogicalAddress(), 4486 volumeInfo.getVolumeIndex()), 4487 // If sending the message fails, ask the System Audio device for its 4488 // audio status so that we can update AudioService 4489 (int errorCode) -> { 4490 if (errorCode == SendMessageResult.SUCCESS) { 4491 // Update the volume tracked in our AbsoluteVolumeAudioStatusAction 4492 // so it correctly processes incoming <Report Audio Status> messages 4493 HdmiCecLocalDevice avbDevice = isTvDevice() ? tv() : playback(); 4494 avbDevice.updateAvbVolume(volumeInfo.getVolumeIndex()); 4495 } else { 4496 sendCecCommand(HdmiCecMessageBuilder.buildGiveAudioStatus( 4497 localDeviceAddress, 4498 mSystemAudioDevice.getLogicalAddress() 4499 )); 4500 } 4501 }); 4502 } 4503 4504 /** 4505 * Called when AudioService adjusts the volume or mute state of an absolute volume 4506 * audio output device 4507 */ 4508 @Override onAudioDeviceVolumeAdjusted( @onNull AudioDeviceAttributes audioDevice, @NonNull VolumeInfo volumeInfo, @AudioManager.VolumeAdjustment int direction, @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode )4509 public void onAudioDeviceVolumeAdjusted( 4510 @NonNull AudioDeviceAttributes audioDevice, 4511 @NonNull VolumeInfo volumeInfo, 4512 @AudioManager.VolumeAdjustment int direction, 4513 @AudioDeviceVolumeManager.VolumeAdjustmentMode int mode 4514 ) { 4515 int keyCode; 4516 switch (direction) { 4517 case AudioManager.ADJUST_RAISE: 4518 keyCode = KeyEvent.KEYCODE_VOLUME_UP; 4519 break; 4520 case AudioManager.ADJUST_LOWER: 4521 keyCode = KeyEvent.KEYCODE_VOLUME_DOWN; 4522 break; 4523 case AudioManager.ADJUST_TOGGLE_MUTE: 4524 case AudioManager.ADJUST_MUTE: 4525 case AudioManager.ADJUST_UNMUTE: 4526 // Many CEC devices only support toggle mute. Therefore, we send the 4527 // same keycode for all three mute options. 4528 keyCode = KeyEvent.KEYCODE_VOLUME_MUTE; 4529 break; 4530 default: 4531 return; 4532 } 4533 switch (mode) { 4534 case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL: 4535 mLocalDevice.sendVolumeKeyEvent(keyCode, true); 4536 mLocalDevice.sendVolumeKeyEvent(keyCode, false); 4537 break; 4538 case AudioDeviceVolumeManager.ADJUST_MODE_START: 4539 mLocalDevice.sendVolumeKeyEvent(keyCode, true); 4540 break; 4541 case AudioDeviceVolumeManager.ADJUST_MODE_END: 4542 mLocalDevice.sendVolumeKeyEvent(keyCode, false); 4543 break; 4544 default: 4545 return; 4546 } 4547 } 4548 } 4549 4550 /** 4551 * Notifies AudioService of a change in the volume of the System Audio device. Has no effect if 4552 * AVB is disabled, or the audio output device for AVB is not playing for STREAM_MUSIC 4553 */ notifyAvbVolumeChange(int volume)4554 void notifyAvbVolumeChange(int volume) { 4555 if (!isAbsoluteVolumeBehaviorEnabled()) return; 4556 List<AudioDeviceAttributes> streamMusicDevices = 4557 getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); 4558 if (streamMusicDevices.contains(getAvbAudioOutputDevice())) { 4559 int flags = AudioManager.FLAG_ABSOLUTE_VOLUME; 4560 if (isTvDevice()) { 4561 flags |= AudioManager.FLAG_SHOW_UI; 4562 } 4563 setStreamMusicVolume(volume, flags); 4564 } 4565 } 4566 4567 /** 4568 * Notifies AudioService of a change in the mute status of the System Audio device. Has no 4569 * effect if AVB is disabled, or the audio output device for AVB is not playing for STREAM_MUSIC 4570 */ notifyAvbMuteChange(boolean mute)4571 void notifyAvbMuteChange(boolean mute) { 4572 if (!isAbsoluteVolumeBehaviorEnabled()) return; 4573 List<AudioDeviceAttributes> streamMusicDevices = 4574 getAudioManager().getDevicesForAttributes(STREAM_MUSIC_ATTRIBUTES); 4575 if (streamMusicDevices.contains(getAvbAudioOutputDevice())) { 4576 int direction = mute ? AudioManager.ADJUST_MUTE : AudioManager.ADJUST_UNMUTE; 4577 int flags = AudioManager.FLAG_ABSOLUTE_VOLUME; 4578 if (isTvDevice()) { 4579 flags |= AudioManager.FLAG_SHOW_UI; 4580 } 4581 getAudioManager().adjustStreamVolume(AudioManager.STREAM_MUSIC, direction, flags); 4582 } 4583 } 4584 4585 /** 4586 * Sets the volume index of {@link AudioManager#STREAM_MUSIC}. Rescales the input volume index 4587 * from HDMI-CEC volume range to STREAM_MUSIC's. 4588 */ setStreamMusicVolume(int volume, int flags)4589 void setStreamMusicVolume(int volume, int flags) { 4590 getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC, 4591 volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags); 4592 } 4593 initializeEarc(int initiatedBy)4594 private void initializeEarc(int initiatedBy) { 4595 Slog.i(TAG, "eARC initialized, reason = " + initiatedBy); 4596 initializeEarcLocalDevice(initiatedBy); 4597 4598 if (initiatedBy == INITIATED_BY_ENABLE_EARC) { 4599 // Since ARC and eARC cannot be connected simultaneously, we need to terminate ARC 4600 // before even enabling eARC. 4601 setEarcEnabledInHal(true, true); 4602 } else { 4603 // On boot, wake-up, and hotplug in, eARC will always be attempted before ARC. 4604 // So there is no need to explicitly terminate ARC before enabling eARC. 4605 setEarcEnabledInHal(true, false); 4606 } 4607 } 4608 4609 @ServiceThreadOnly 4610 @VisibleForTesting initializeEarcLocalDevice(final int initiatedBy)4611 protected void initializeEarcLocalDevice(final int initiatedBy) { 4612 // TODO remove initiatedBy argument if it stays unused 4613 assertRunOnServiceThread(); 4614 if (mEarcLocalDevice == null) { 4615 mEarcLocalDevice = HdmiEarcLocalDevice.create(this, HdmiDeviceInfo.DEVICE_TV); 4616 } 4617 // TODO create HdmiEarcLocalDeviceRx if we're an audio system device. 4618 } 4619 4620 @ServiceThreadOnly 4621 @VisibleForTesting setEarcEnabled(@dmiControlManager.EarcFeature int enabled)4622 protected void setEarcEnabled(@HdmiControlManager.EarcFeature int enabled) { 4623 assertRunOnServiceThread(); 4624 synchronized (mLock) { 4625 mEarcEnabled = (enabled == EARC_FEATURE_ENABLED); 4626 4627 if (!isEarcSupported()) { 4628 Slog.i(TAG, "Enabled/disabled eARC setting, but the hardware doesn´t support eARC. " 4629 + "This settings change doesn´t have an effect."); 4630 return; 4631 } 4632 4633 if (mEarcEnabled) { 4634 onEnableEarc(); 4635 return; 4636 } 4637 } 4638 runOnServiceThread(new Runnable() { 4639 @Override 4640 public void run() { 4641 onDisableEarc(); 4642 } 4643 }); 4644 } 4645 4646 @VisibleForTesting setEarcSupported(boolean supported)4647 protected void setEarcSupported(boolean supported) { 4648 synchronized (mLock) { 4649 mEarcSupported = supported; 4650 } 4651 } 4652 4653 @ServiceThreadOnly onEnableEarc()4654 private void onEnableEarc() { 4655 // This will terminate ARC as well. 4656 initializeEarc(INITIATED_BY_ENABLE_EARC); 4657 } 4658 4659 @ServiceThreadOnly onDisableEarc()4660 private void onDisableEarc() { 4661 disableEarcLocalDevice(); 4662 setEarcEnabledInHal(false, false); 4663 clearEarcLocalDevice(); 4664 } 4665 4666 @ServiceThreadOnly 4667 @VisibleForTesting clearEarcLocalDevice()4668 protected void clearEarcLocalDevice() { 4669 assertRunOnServiceThread(); 4670 mEarcLocalDevice = null; 4671 } 4672 4673 @ServiceThreadOnly 4674 @VisibleForTesting addEarcLocalDevice(HdmiEarcLocalDevice localDevice)4675 protected void addEarcLocalDevice(HdmiEarcLocalDevice localDevice) { 4676 assertRunOnServiceThread(); 4677 mEarcLocalDevice = localDevice; 4678 } 4679 4680 @ServiceThreadOnly 4681 @VisibleForTesting getEarcLocalDevice()4682 HdmiEarcLocalDevice getEarcLocalDevice() { 4683 assertRunOnServiceThread(); 4684 return mEarcLocalDevice; 4685 } 4686 disableEarcLocalDevice()4687 private void disableEarcLocalDevice() { 4688 if (mEarcLocalDevice == null) { 4689 return; 4690 } 4691 mEarcLocalDevice.disableDevice(); 4692 } 4693 4694 @ServiceThreadOnly 4695 @VisibleForTesting setEarcEnabledInHal(boolean enabled, boolean terminateArcFirst)4696 protected void setEarcEnabledInHal(boolean enabled, boolean terminateArcFirst) { 4697 assertRunOnServiceThread(); 4698 if (terminateArcFirst) { 4699 startArcAction(false, new IHdmiControlCallback.Stub() { 4700 @Override 4701 public void onComplete(int result) throws RemoteException { 4702 if (result != HdmiControlManager.RESULT_SUCCESS) { 4703 Slog.w(TAG, 4704 "ARC termination before enabling eARC in the HAL failed with " 4705 + "result: " + result); 4706 } 4707 // Independently of the result (i.e. independently of whether the ARC RX device 4708 // responded with <Terminate ARC> or not), we always end up terminating ARC in 4709 // the HAL. As soon as we do that, we can enable eARC in the HAL. 4710 mEarcController.setEarcEnabled(enabled); 4711 mCecController.setHpdSignalType( 4712 enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT 4713 : Constants.HDMI_HPD_TYPE_PHYSICAL, 4714 mEarcPortId); 4715 } 4716 }); 4717 } else { 4718 mEarcController.setEarcEnabled(enabled); 4719 mCecController.setHpdSignalType( 4720 enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL, 4721 mEarcPortId); 4722 } 4723 } 4724 4725 @ServiceThreadOnly handleEarcStateChange(int status, int portId)4726 void handleEarcStateChange(int status, int portId) { 4727 assertRunOnServiceThread(); 4728 if (!getPortInfo(portId).isEarcSupported()) { 4729 Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC."); 4730 return; 4731 } 4732 if (mEarcLocalDevice != null) { 4733 mEarcLocalDevice.handleEarcStateChange(status); 4734 } else if (status == HDMI_EARC_STATUS_ARC_PENDING) { 4735 // If eARC is disabled, the local device is null. This is why we notify 4736 // AudioService here that the eARC connection is terminated. 4737 notifyEarcStatusToAudioService(false, new ArrayList<>()); 4738 startArcAction(true, null); 4739 } 4740 } 4741 notifyEarcStatusToAudioService( boolean enabled, List<AudioDescriptor> audioDescriptors)4742 protected void notifyEarcStatusToAudioService( 4743 boolean enabled, List<AudioDescriptor> audioDescriptors) { 4744 AudioDeviceAttributes attributes = new AudioDeviceAttributes( 4745 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "", 4746 new ArrayList<AudioProfile>(), audioDescriptors); 4747 getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); 4748 } 4749 4750 @ServiceThreadOnly handleEarcCapabilitiesReported(byte[] rawCapabilities, int portId)4751 void handleEarcCapabilitiesReported(byte[] rawCapabilities, int portId) { 4752 assertRunOnServiceThread(); 4753 if (!getPortInfo(portId).isEarcSupported()) { 4754 Slog.w(TAG, 4755 "Tried to process eARC capabilities from a port that doesn't support eARC."); 4756 return; 4757 } 4758 // If eARC is disabled, the local device is null. In this case, the HAL shouldn't have 4759 // reported eARC capabilities, but even if it did, it won't take effect. 4760 if (mEarcLocalDevice != null) { 4761 mEarcLocalDevice.handleEarcCapabilitiesReported(rawCapabilities); 4762 } 4763 } 4764 earcBlocksArcConnection()4765 protected boolean earcBlocksArcConnection() { 4766 if (mEarcLocalDevice == null) { 4767 return false; 4768 } 4769 synchronized (mLock) { 4770 return mEarcLocalDevice.mEarcStatus != HDMI_EARC_STATUS_ARC_PENDING; 4771 } 4772 } 4773 startArcAction(boolean enabled, IHdmiControlCallback callback)4774 protected void startArcAction(boolean enabled, IHdmiControlCallback callback) { 4775 if (!isTvDeviceEnabled()) { 4776 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 4777 } else { 4778 tv().startArcAction(enabled, callback); 4779 } 4780 } 4781 } 4782