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