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