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