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