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