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 
22 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
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.Nullable;
33 import android.content.BroadcastReceiver;
34 import android.content.ContentResolver;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.IntentFilter;
38 import android.database.ContentObserver;
39 import android.hardware.hdmi.HdmiControlManager;
40 import android.hardware.hdmi.HdmiDeviceInfo;
41 import android.hardware.hdmi.HdmiHotplugEvent;
42 import android.hardware.hdmi.HdmiPortInfo;
43 import android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener;
44 import android.hardware.hdmi.IHdmiControlCallback;
45 import android.hardware.hdmi.IHdmiControlService;
46 import android.hardware.hdmi.IHdmiControlStatusChangeListener;
47 import android.hardware.hdmi.IHdmiDeviceEventListener;
48 import android.hardware.hdmi.IHdmiHotplugEventListener;
49 import android.hardware.hdmi.IHdmiInputChangeListener;
50 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
51 import android.hardware.hdmi.IHdmiRecordListener;
52 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
53 import android.hardware.hdmi.IHdmiVendorCommandListener;
54 import android.hardware.tv.cec.V1_0.OptionKey;
55 import android.hardware.tv.cec.V1_0.SendMessageResult;
56 import android.media.AudioManager;
57 import android.media.tv.TvInputManager;
58 import android.media.tv.TvInputManager.TvInputCallback;
59 import android.net.Uri;
60 import android.os.Binder;
61 import android.os.Build;
62 import android.os.Handler;
63 import android.os.HandlerThread;
64 import android.os.IBinder;
65 import android.os.Looper;
66 import android.os.PowerManager;
67 import android.os.RemoteCallbackList;
68 import android.os.RemoteException;
69 import android.os.SystemClock;
70 import android.os.SystemProperties;
71 import android.os.UserHandle;
72 import android.provider.Settings.Global;
73 import android.sysprop.HdmiProperties;
74 import android.text.TextUtils;
75 import android.util.ArraySet;
76 import android.util.Slog;
77 import android.util.SparseArray;
78 import android.util.SparseIntArray;
79 
80 import com.android.internal.annotations.GuardedBy;
81 import com.android.internal.annotations.VisibleForTesting;
82 import com.android.internal.util.DumpUtils;
83 import com.android.internal.util.IndentingPrintWriter;
84 import com.android.server.SystemService;
85 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
86 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
87 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
88 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
89 
90 import libcore.util.EmptyArray;
91 
92 import java.io.FileDescriptor;
93 import java.io.PrintWriter;
94 import java.util.ArrayList;
95 import java.util.Arrays;
96 import java.util.Collections;
97 import java.util.HashMap;
98 import java.util.List;
99 import java.util.Locale;
100 import java.util.Map;
101 import java.util.Objects;
102 import java.util.stream.Collectors;
103 
104 /**
105  * Provides a service for sending and processing HDMI control messages,
106  * HDMI-CEC and MHL control command, and providing the information on both standard.
107  */
108 public class HdmiControlService extends SystemService {
109     private static final String TAG = "HdmiControlService";
110     private static final Locale HONG_KONG = new Locale("zh", "HK");
111     private static final Locale MACAU = new Locale("zh", "MO");
112 
113     private static final Map<String, String> sTerminologyToBibliographicMap =
114             createsTerminologyToBibliographicMap();
115 
createsTerminologyToBibliographicMap()116     private static Map<String, String> createsTerminologyToBibliographicMap() {
117         Map<String, String> temp = new HashMap<>();
118         // NOTE: (TERMINOLOGY_CODE, BIBLIOGRAPHIC_CODE)
119         temp.put("sqi", "alb"); // Albanian
120         temp.put("hye", "arm"); // Armenian
121         temp.put("eus", "baq"); // Basque
122         temp.put("mya", "bur"); // Burmese
123         temp.put("ces", "cze"); // Czech
124         temp.put("nld", "dut"); // Dutch
125         temp.put("kat", "geo"); // Georgian
126         temp.put("deu", "ger"); // German
127         temp.put("ell", "gre"); // Greek
128         temp.put("fra", "fre"); // French
129         temp.put("isl", "ice"); // Icelandic
130         temp.put("mkd", "mac"); // Macedonian
131         temp.put("mri", "mao"); // Maori
132         temp.put("msa", "may"); // Malay
133         temp.put("fas", "per"); // Persian
134         temp.put("ron", "rum"); // Romanian
135         temp.put("slk", "slo"); // Slovak
136         temp.put("bod", "tib"); // Tibetan
137         temp.put("cym", "wel"); // Welsh
138         return Collections.unmodifiableMap(temp);
139     }
140 
localeToMenuLanguage(Locale locale)141     @VisibleForTesting static String localeToMenuLanguage(Locale locale) {
142         if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
143             // Android always returns "zho" for all Chinese variants.
144             // Use "bibliographic" code defined in CEC639-2 for traditional
145             // Chinese used in Taiwan/Hong Kong/Macau.
146             return "chi";
147         } else {
148             String language = locale.getISO3Language();
149 
150             // locale.getISO3Language() returns terminology code and need to
151             // send it as bibliographic code instead since the Bibliographic
152             // codes of ISO/FDIS 639-2 shall be used.
153             // NOTE: Chinese also has terminology/bibliographic code "zho" and "chi"
154             // But, as it depends on the locale, is not handled here.
155             if (sTerminologyToBibliographicMap.containsKey(language)) {
156                 language = sTerminologyToBibliographicMap.get(language);
157             }
158 
159             return language;
160         }
161     }
162 
163     static final String PERMISSION = "android.permission.HDMI_CEC";
164 
165     // The reason code to initiate initializeCec().
166     static final int INITIATED_BY_ENABLE_CEC = 0;
167     static final int INITIATED_BY_BOOT_UP = 1;
168     static final int INITIATED_BY_SCREEN_ON = 2;
169     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
170     static final int INITIATED_BY_HOTPLUG = 4;
171 
172     // The reason code representing the intent action that drives the standby
173     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
174     // Intent.ACTION_SHUTDOWN.
175     static final int STANDBY_SCREEN_OFF = 0;
176     static final int STANDBY_SHUTDOWN = 1;
177 
178     // Logical address of the active source.
179     @GuardedBy("mLock")
180     protected final ActiveSource mActiveSource = new ActiveSource();
181 
182     // Whether HDMI CEC volume control is enabled or not.
183     @GuardedBy("mLock")
184     private boolean mHdmiCecVolumeControlEnabled;
185 
186     // Whether System Audio Mode is activated or not.
187     @GuardedBy("mLock")
188     private boolean mSystemAudioActivated = false;
189 
190     private static final boolean isHdmiCecNeverClaimPlaybackLogicAddr =
191             SystemProperties.getBoolean(
192                     Constants.PROPERTY_HDMI_CEC_NEVER_CLAIM_PLAYBACK_LOGICAL_ADDRESS, false);
193 
194     /**
195      * Interface to report send result.
196      */
197     interface SendMessageCallback {
198         /**
199          * Called when {@link HdmiControlService#sendCecCommand} is completed.
200          *
201          * @param error result of send request.
202          * <ul>
203          * <li>{@link SendMessageResult#SUCCESS}
204          * <li>{@link SendMessageResult#NACK}
205          * <li>{@link SendMessageResult#BUSY}
206          * <li>{@link SendMessageResult#FAIL}
207          * </ul>
208          */
onSendCompleted(int error)209         void onSendCompleted(int error);
210     }
211 
212     /**
213      * Interface to get a list of available logical devices.
214      */
215     interface DevicePollingCallback {
216         /**
217          * Called when device polling is finished.
218          *
219          * @param ackedAddress a list of logical addresses of available devices
220          */
onPollingFinished(List<Integer> ackedAddress)221         void onPollingFinished(List<Integer> ackedAddress);
222     }
223 
224     private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
225         @ServiceThreadOnly
226         @Override
onReceive(Context context, Intent intent)227         public void onReceive(Context context, Intent intent) {
228             assertRunOnServiceThread();
229             boolean isReboot = SystemProperties.get(SHUTDOWN_ACTION_PROPERTY).contains("1");
230             switch (intent.getAction()) {
231                 case Intent.ACTION_SCREEN_OFF:
232                     if (isPowerOnOrTransient() && !isReboot) {
233                         onStandby(STANDBY_SCREEN_OFF);
234                     }
235                     break;
236                 case Intent.ACTION_SCREEN_ON:
237                     if (isPowerStandbyOrTransient()) {
238                         onWakeUp();
239                     }
240                     break;
241                 case Intent.ACTION_CONFIGURATION_CHANGED:
242                     String language = HdmiControlService.localeToMenuLanguage(Locale.getDefault());
243                     if (!mMenuLanguage.equals(language)) {
244                         onLanguageChanged(language);
245                     }
246                     break;
247                 case Intent.ACTION_SHUTDOWN:
248                     if (isPowerOnOrTransient() && !isReboot) {
249                         onStandby(STANDBY_SHUTDOWN);
250                     }
251                     break;
252             }
253         }
254 
255     }
256 
257     // A thread to handle synchronous IO of CEC and MHL control service.
258     // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
259     // and sparse call it shares a thread to handle IO operations.
260     private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
261 
262     // Used to synchronize the access to the service.
263     private final Object mLock = new Object();
264 
265     // Type of logical devices hosted in the system. Stored in the unmodifiable list.
266     private final List<Integer> mLocalDevices;
267 
268     // List of records for HDMI control status change listener for death monitoring.
269     @GuardedBy("mLock")
270     private final ArrayList<HdmiControlStatusChangeListenerRecord>
271             mHdmiControlStatusChangeListenerRecords = new ArrayList<>();
272 
273     // List of records for HDMI control volume control status change listener for death monitoring.
274     @GuardedBy("mLock")
275     private final RemoteCallbackList<IHdmiCecVolumeControlFeatureListener>
276             mHdmiCecVolumeControlFeatureListenerRecords = new RemoteCallbackList<>();
277 
278     // List of records for hotplug event listener to handle the the caller killed in action.
279     @GuardedBy("mLock")
280     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
281             new ArrayList<>();
282 
283     // List of records for device event listener to handle the caller killed in action.
284     @GuardedBy("mLock")
285     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
286             new ArrayList<>();
287 
288     // List of records for vendor command listener to handle the caller killed in action.
289     @GuardedBy("mLock")
290     private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
291             new ArrayList<>();
292 
293     @GuardedBy("mLock")
294     private InputChangeListenerRecord mInputChangeListenerRecord;
295 
296     @GuardedBy("mLock")
297     private HdmiRecordListenerRecord mRecordListenerRecord;
298 
299     // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
300     // handling will be disabled and no request will be handled.
301     @GuardedBy("mLock")
302     private boolean mHdmiControlEnabled;
303 
304     // Set to true while the service is in normal mode. While set to false, no input change is
305     // allowed. Used for situations where input change can confuse users such as channel auto-scan,
306     // system upgrade, etc., a.k.a. "prohibit mode".
307     @GuardedBy("mLock")
308     private boolean mProhibitMode;
309 
310     // List of records for system audio mode change to handle the the caller killed in action.
311     private final ArrayList<SystemAudioModeChangeListenerRecord>
312             mSystemAudioModeChangeListenerRecords = new ArrayList<>();
313 
314     // Handler used to run a task in service thread.
315     private final Handler mHandler = new Handler();
316 
317     private final SettingsObserver mSettingsObserver;
318 
319     private final HdmiControlBroadcastReceiver
320             mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
321 
322     @Nullable
323     // Save callback when the device is still under logcial address allocation
324     // Invoke once new local device is ready.
325     private IHdmiControlCallback mDisplayStatusCallback = null;
326 
327     @Nullable
328     // Save callback when the device is still under logcial address allocation
329     // Invoke once new local device is ready.
330     private IHdmiControlCallback mOtpCallbackPendingAddressAllocation = null;
331 
332     @Nullable
333     private HdmiCecController mCecController;
334 
335     // HDMI port information. Stored in the unmodifiable list to keep the static information
336     // from being modified.
337     // This variable is null if the current device does not have hdmi input.
338     @GuardedBy("mLock")
339     private List<HdmiPortInfo> mPortInfo = null;
340 
341     // Map from path(physical address) to port ID.
342     private UnmodifiableSparseIntArray mPortIdMap;
343 
344     // Map from port ID to HdmiPortInfo.
345     private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
346 
347     // Map from port ID to HdmiDeviceInfo.
348     private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
349 
350     private HdmiCecMessageValidator mMessageValidator;
351 
352     @ServiceThreadOnly
353     private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
354 
355     @ServiceThreadOnly
356     private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
357 
358     @ServiceThreadOnly
359     private boolean mStandbyMessageReceived = false;
360 
361     @ServiceThreadOnly
362     private boolean mWakeUpMessageReceived = false;
363 
364     @ServiceThreadOnly
365     private int mActivePortId = Constants.INVALID_PORT_ID;
366 
367     // Set to true while the input change by MHL is allowed.
368     @GuardedBy("mLock")
369     private boolean mMhlInputChangeEnabled;
370 
371     // List of records for MHL Vendor command listener to handle the caller killed in action.
372     @GuardedBy("mLock")
373     private final ArrayList<HdmiMhlVendorCommandListenerRecord>
374             mMhlVendorCommandListenerRecords = new ArrayList<>();
375 
376     @GuardedBy("mLock")
377     private List<HdmiDeviceInfo> mMhlDevices;
378 
379     @Nullable
380     private HdmiMhlControllerStub mMhlController;
381 
382     @Nullable
383     private TvInputManager mTvInputManager;
384 
385     @Nullable
386     private PowerManager mPowerManager;
387 
388     @Nullable
389     private Looper mIoLooper;
390 
391     // Thread safe physical address
392     @GuardedBy("mLock")
393     private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
394 
395     // Last input port before switching to the MHL port. Should switch back to this port
396     // when the mobile device sends the request one touch play with off.
397     // Gets invalidated if we go to other port/input.
398     @ServiceThreadOnly
399     private int mLastInputMhl = Constants.INVALID_PORT_ID;
400 
401     // Set to true if the logical address allocation is completed.
402     private boolean mAddressAllocated = false;
403 
404     // Buffer for processing the incoming cec messages while allocating logical addresses.
405     private final class CecMessageBuffer {
406         private List<HdmiCecMessage> mBuffer = new ArrayList<>();
407 
bufferMessage(HdmiCecMessage message)408         public boolean bufferMessage(HdmiCecMessage message) {
409             switch (message.getOpcode()) {
410                 case Constants.MESSAGE_ACTIVE_SOURCE:
411                     bufferActiveSource(message);
412                     return true;
413                 case Constants.MESSAGE_IMAGE_VIEW_ON:
414                 case Constants.MESSAGE_TEXT_VIEW_ON:
415                     bufferImageOrTextViewOn(message);
416                     return true;
417                 case Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST:
418                     bufferSystemAudioModeRequest(message);
419                     return true;
420                     // Add here if new message that needs to buffer
421                 default:
422                     // Do not need to buffer messages other than above
423                     return false;
424             }
425         }
426 
processMessages()427         public void processMessages() {
428             for (final HdmiCecMessage message : mBuffer) {
429                 runOnServiceThread(new Runnable() {
430                     @Override
431                     public void run() {
432                         handleCecCommand(message);
433                     }
434                 });
435             }
436             mBuffer.clear();
437         }
438 
bufferActiveSource(HdmiCecMessage message)439         private void bufferActiveSource(HdmiCecMessage message) {
440             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
441                 mBuffer.add(message);
442             }
443         }
444 
bufferImageOrTextViewOn(HdmiCecMessage message)445         private void bufferImageOrTextViewOn(HdmiCecMessage message) {
446             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
447                 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
448                 mBuffer.add(message);
449             }
450         }
451 
bufferSystemAudioModeRequest(HdmiCecMessage message)452         private void bufferSystemAudioModeRequest(HdmiCecMessage message) {
453             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_SYSTEM_AUDIO_MODE_REQUEST)) {
454                 mBuffer.add(message);
455             }
456         }
457 
458         // Returns true if the message is replaced
replaceMessageIfBuffered(HdmiCecMessage message, int opcode)459         private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
460             for (int i = 0; i < mBuffer.size(); i++) {
461                 HdmiCecMessage bufferedMessage = mBuffer.get(i);
462                 if (bufferedMessage.getOpcode() == opcode) {
463                     mBuffer.set(i, message);
464                     return true;
465                 }
466             }
467             return false;
468         }
469     }
470 
471     private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
472 
473     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
474 
HdmiControlService(Context context)475     public HdmiControlService(Context context) {
476         super(context);
477         List<Integer> deviceTypes = HdmiProperties.device_type();
478         if (deviceTypes.contains(null)) {
479             Slog.w(TAG, "Error parsing ro.hdmi.device.type: " + SystemProperties.get(
480                     "ro.hdmi.device_type"));
481             deviceTypes = deviceTypes.stream().filter(Objects::nonNull).collect(
482                     Collectors.toList());
483         }
484         mLocalDevices = deviceTypes;
485         mSettingsObserver = new SettingsObserver(mHandler);
486     }
487 
getIntList(String string)488     protected static List<Integer> getIntList(String string) {
489         ArrayList<Integer> list = new ArrayList<>();
490         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
491         splitter.setString(string);
492         for (String item : splitter) {
493             try {
494                 list.add(Integer.parseInt(item));
495             } catch (NumberFormatException e) {
496                 Slog.w(TAG, "Can't parseInt: " + item);
497             }
498         }
499         return Collections.unmodifiableList(list);
500     }
501 
502     @Override
onStart()503     public void onStart() {
504         if (mIoLooper == null) {
505             mIoThread.start();
506             mIoLooper = mIoThread.getLooper();
507         }
508         mPowerStatus = getInitialPowerStatus();
509         mProhibitMode = false;
510         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
511         mHdmiCecVolumeControlEnabled = readBooleanSetting(
512                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true);
513         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
514 
515         if (mCecController == null) {
516             mCecController = HdmiCecController.create(this);
517         }
518         if (mCecController != null) {
519             if (mHdmiControlEnabled) {
520                 initializeCec(INITIATED_BY_BOOT_UP);
521             } else {
522                 mCecController.setOption(OptionKey.ENABLE_CEC, false);
523             }
524         } else {
525             Slog.i(TAG, "Device does not support HDMI-CEC.");
526             return;
527         }
528         if (mMhlController == null) {
529             mMhlController = HdmiMhlControllerStub.create(this);
530         }
531         if (!mMhlController.isReady()) {
532             Slog.i(TAG, "Device does not support MHL-control.");
533         }
534         mMhlDevices = Collections.emptyList();
535 
536         initPortInfo();
537         if (mMessageValidator == null) {
538             mMessageValidator = new HdmiCecMessageValidator(this);
539         }
540         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
541 
542         if (mCecController != null) {
543             // Register broadcast receiver for power state change.
544             IntentFilter filter = new IntentFilter();
545             filter.addAction(Intent.ACTION_SCREEN_OFF);
546             filter.addAction(Intent.ACTION_SCREEN_ON);
547             filter.addAction(Intent.ACTION_SHUTDOWN);
548             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
549             getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
550 
551             // Register ContentObserver to monitor the settings change.
552             registerContentObserver();
553         }
554         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
555     }
556 
bootCompleted()557     private void bootCompleted() {
558         // on boot, if device is interactive, set HDMI CEC state as powered on as well
559         if (mPowerManager.isInteractive() && isPowerStandbyOrTransient()) {
560             onWakeUp();
561         }
562     }
563 
564     /**
565      * Returns the initial power status used when the HdmiControlService starts.
566      */
567     @VisibleForTesting
getInitialPowerStatus()568     int getInitialPowerStatus() {
569         // The initial power status is POWER_STATUS_TRANSIENT_TO_STANDBY.
570         // Once boot completes the service transitions to POWER_STATUS_ON if the device is
571         // interactive.
572         // Quiescent boot is a special boot mode, in which the screen stays off during boot
573         // and the device goes to sleep after boot has finished.
574         // We don't transition to POWER_STATUS_ON initially, as we might be booting in quiescent
575         // mode, during which we don't want to appear powered on to avoid being made active source.
576         return HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
577     }
578 
579     @VisibleForTesting
setCecController(HdmiCecController cecController)580     void setCecController(HdmiCecController cecController) {
581         mCecController = cecController;
582     }
583 
584     @VisibleForTesting
setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController)585     void setHdmiMhlController(HdmiMhlControllerStub hdmiMhlController) {
586         mMhlController = hdmiMhlController;
587     }
588 
589     @Override
onBootPhase(int phase)590     public void onBootPhase(int phase) {
591         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
592             mTvInputManager = (TvInputManager) getContext().getSystemService(
593                     Context.TV_INPUT_SERVICE);
594             mPowerManager = getContext().getSystemService(PowerManager.class);
595         } else if (phase == SystemService.PHASE_BOOT_COMPLETED) {
596             runOnServiceThread(this::bootCompleted);
597         }
598     }
599 
getTvInputManager()600     TvInputManager getTvInputManager() {
601         return mTvInputManager;
602     }
603 
registerTvInputCallback(TvInputCallback callback)604     void registerTvInputCallback(TvInputCallback callback) {
605         if (mTvInputManager == null) return;
606         mTvInputManager.registerCallback(callback, mHandler);
607     }
608 
unregisterTvInputCallback(TvInputCallback callback)609     void unregisterTvInputCallback(TvInputCallback callback) {
610         if (mTvInputManager == null) return;
611         mTvInputManager.unregisterCallback(callback);
612     }
613 
getPowerManager()614     PowerManager getPowerManager() {
615         return mPowerManager;
616     }
617 
618     /**
619      * Called when the initialization of local devices is complete.
620      */
onInitializeCecComplete(int initiatedBy)621     private void onInitializeCecComplete(int initiatedBy) {
622         updatePowerStatusOnInitializeCecComplete();
623         mWakeUpMessageReceived = false;
624 
625         if (isTvDeviceEnabled()) {
626             mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
627         }
628         int reason = -1;
629         switch (initiatedBy) {
630             case INITIATED_BY_BOOT_UP:
631                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
632                 break;
633             case INITIATED_BY_ENABLE_CEC:
634                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
635                 break;
636             case INITIATED_BY_SCREEN_ON:
637             case INITIATED_BY_WAKE_UP_MESSAGE:
638                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
639                 break;
640         }
641         if (reason != -1) {
642             invokeVendorCommandListenersOnControlStateChanged(true, reason);
643             announceHdmiControlStatusChange(true);
644         }
645     }
646 
647     /**
648      * Updates the power status once the initialization of local devices is complete.
649      */
updatePowerStatusOnInitializeCecComplete()650     private void updatePowerStatusOnInitializeCecComplete() {
651         if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
652             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
653         } else if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
654             mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
655         }
656     }
657 
registerContentObserver()658     private void registerContentObserver() {
659         ContentResolver resolver = getContext().getContentResolver();
660         String[] settings = new String[] {
661                 Global.HDMI_CONTROL_ENABLED,
662                 Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
663                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
664                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
665                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
666                 Global.MHL_INPUT_SWITCHING_ENABLED,
667                 Global.MHL_POWER_CHARGE_ENABLED,
668                 Global.HDMI_CEC_SWITCH_ENABLED,
669                 Global.DEVICE_NAME
670         };
671         for (String s : settings) {
672             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
673                     UserHandle.USER_ALL);
674         }
675     }
676 
677     private class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)678         public SettingsObserver(Handler handler) {
679             super(handler);
680         }
681 
682         // onChange is set up to run in service thread.
683         @Override
onChange(boolean selfChange, Uri uri)684         public void onChange(boolean selfChange, Uri uri) {
685             String option = uri.getLastPathSegment();
686             boolean enabled = readBooleanSetting(option, true);
687             switch (option) {
688                 case Global.HDMI_CONTROL_ENABLED:
689                     setControlEnabled(enabled);
690                     break;
691                 case Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED:
692                     setHdmiCecVolumeControlEnabled(enabled);
693                     break;
694                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
695                     if (isTvDeviceEnabled()) {
696                         tv().setAutoWakeup(enabled);
697                     }
698                     setCecOption(OptionKey.WAKEUP, enabled);
699                     break;
700                 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
701                     for (int type : mLocalDevices) {
702                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
703                         if (localDevice != null) {
704                             localDevice.setAutoDeviceOff(enabled);
705                         }
706                     }
707                     // No need to propagate to HAL.
708                     break;
709                 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
710                     if (isTvDeviceEnabled()) {
711                         tv().setSystemAudioControlFeatureEnabled(enabled);
712                     }
713                     if (isAudioSystemDevice()) {
714                         if (audioSystem() == null) {
715                             Slog.e(TAG, "Audio System device has not registered yet."
716                                     + " Can't turn system audio mode on.");
717                             break;
718                         }
719                         audioSystem().onSystemAduioControlFeatureSupportChanged(enabled);
720                     }
721                     break;
722                 case Global.HDMI_CEC_SWITCH_ENABLED:
723                     if (isAudioSystemDevice()) {
724                         if (audioSystem() == null) {
725                             Slog.w(TAG, "Switch device has not registered yet."
726                                     + " Can't turn routing on.");
727                             break;
728                         }
729                         audioSystem().setRoutingControlFeatureEnables(enabled);
730                     }
731                     break;
732                 case Global.MHL_INPUT_SWITCHING_ENABLED:
733                     setMhlInputChangeEnabled(enabled);
734                     break;
735                 case Global.MHL_POWER_CHARGE_ENABLED:
736                     mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
737                     break;
738                 case Global.DEVICE_NAME:
739                     String deviceName = readStringSetting(option, Build.MODEL);
740                     setDisplayName(deviceName);
741                     break;
742             }
743         }
744     }
745 
toInt(boolean enabled)746     private static int toInt(boolean enabled) {
747         return enabled ? ENABLED : DISABLED;
748     }
749 
750     @VisibleForTesting
readBooleanSetting(String key, boolean defVal)751     boolean readBooleanSetting(String key, boolean defVal) {
752         ContentResolver cr = getContext().getContentResolver();
753         return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
754     }
755 
writeBooleanSetting(String key, boolean value)756     void writeBooleanSetting(String key, boolean value) {
757         ContentResolver cr = getContext().getContentResolver();
758         Global.putInt(cr, key, toInt(value));
759     }
760 
writeStringSystemProperty(String key, String value)761     void writeStringSystemProperty(String key, String value) {
762         SystemProperties.set(key, value);
763     }
764 
765     @VisibleForTesting
readBooleanSystemProperty(String key, boolean defVal)766     boolean readBooleanSystemProperty(String key, boolean defVal) {
767         return SystemProperties.getBoolean(key, defVal);
768     }
769 
readStringSetting(String key, String defVal)770     String readStringSetting(String key, String defVal) {
771         ContentResolver cr = getContext().getContentResolver();
772         String content = Global.getString(cr, key);
773         if (TextUtils.isEmpty(content)) {
774             return defVal;
775         }
776         return content;
777     }
778 
initializeCec(int initiatedBy)779     private void initializeCec(int initiatedBy) {
780         mAddressAllocated = false;
781         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
782         mCecController.setLanguage(mMenuLanguage);
783         initializeLocalDevices(initiatedBy);
784     }
785 
786     @ServiceThreadOnly
initializeLocalDevices(final int initiatedBy)787     private void initializeLocalDevices(final int initiatedBy) {
788         assertRunOnServiceThread();
789         // A container for [Device type, Local device info].
790         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
791         for (int type : mLocalDevices) {
792             if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
793                     && isHdmiCecNeverClaimPlaybackLogicAddr) {
794                 continue;
795             }
796             HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
797             if (localDevice == null) {
798                 localDevice = HdmiCecLocalDevice.create(this, type);
799             }
800             localDevice.init();
801             localDevices.add(localDevice);
802         }
803         // It's now safe to flush existing local devices from mCecController since they were
804         // already moved to 'localDevices'.
805         clearLocalDevices();
806         allocateLogicalAddress(localDevices, initiatedBy);
807     }
808 
809     @ServiceThreadOnly
810     @VisibleForTesting
allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, final int initiatedBy)811     protected void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
812             final int initiatedBy) {
813         assertRunOnServiceThread();
814         mCecController.clearLogicalAddress();
815         final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
816         final int[] finished = new int[1];
817         mAddressAllocated = allocatingDevices.isEmpty();
818 
819         // For TV device, select request can be invoked while address allocation or device
820         // discovery is in progress. Initialize the request here at the start of allocation,
821         // and process the collected requests later when the allocation and device discovery
822         // is all completed.
823         mSelectRequestBuffer.clear();
824 
825         for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
826             mCecController.allocateLogicalAddress(localDevice.getType(),
827                     localDevice.getPreferredAddress(), new AllocateAddressCallback() {
828                 @Override
829                 public void onAllocated(int deviceType, int logicalAddress) {
830                     if (logicalAddress == Constants.ADDR_UNREGISTERED) {
831                         Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
832                     } else {
833                         // Set POWER_STATUS_ON to all local devices because they share lifetime
834                         // with system.
835                         HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
836                                 HdmiControlManager.POWER_STATUS_ON);
837                         localDevice.setDeviceInfo(deviceInfo);
838                         mCecController.addLocalDevice(deviceType, localDevice);
839                         mCecController.addLogicalAddress(logicalAddress);
840                         allocatedDevices.add(localDevice);
841                     }
842 
843                     // Address allocation completed for all devices. Notify each device.
844                     if (allocatingDevices.size() == ++finished[0]) {
845                         mAddressAllocated = true;
846                         if (initiatedBy != INITIATED_BY_HOTPLUG) {
847                             // In case of the hotplug we don't call onInitializeCecComplete()
848                             // since we reallocate the logical address only.
849                             onInitializeCecComplete(initiatedBy);
850                         }
851                         notifyAddressAllocated(allocatedDevices, initiatedBy);
852                         // Reinvoke the saved display status callback once the local device is ready.
853                         if (mDisplayStatusCallback != null) {
854                             queryDisplayStatus(mDisplayStatusCallback);
855                             mDisplayStatusCallback = null;
856                         }
857                         if (mOtpCallbackPendingAddressAllocation != null) {
858                             oneTouchPlay(mOtpCallbackPendingAddressAllocation);
859                             mOtpCallbackPendingAddressAllocation = null;
860                         }
861                         mCecMessageBuffer.processMessages();
862                     }
863                 }
864             });
865         }
866     }
867 
868     @ServiceThreadOnly
notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy)869     private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
870         assertRunOnServiceThread();
871         for (HdmiCecLocalDevice device : devices) {
872             int address = device.getDeviceInfo().getLogicalAddress();
873             device.handleAddressAllocated(address, initiatedBy);
874         }
875         if (isTvDeviceEnabled()) {
876             tv().setSelectRequestBuffer(mSelectRequestBuffer);
877         }
878     }
879 
isAddressAllocated()880     boolean isAddressAllocated() {
881         return mAddressAllocated;
882     }
883 
884     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
885     // keep them in one place.
886     @ServiceThreadOnly
887     @VisibleForTesting
initPortInfo()888     protected void initPortInfo() {
889         assertRunOnServiceThread();
890         HdmiPortInfo[] cecPortInfo = null;
891 
892         synchronized (mLock) {
893             mPhysicalAddress = getPhysicalAddress();
894         }
895 
896         // CEC HAL provides majority of the info while MHL does only MHL support flag for
897         // each port. Return empty array if CEC HAL didn't provide the info.
898         if (mCecController != null) {
899             cecPortInfo = mCecController.getPortInfos();
900         }
901         if (cecPortInfo == null) {
902             return;
903         }
904 
905         SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
906         SparseIntArray portIdMap = new SparseIntArray();
907         SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
908         for (HdmiPortInfo info : cecPortInfo) {
909             portIdMap.put(info.getAddress(), info.getId());
910             portInfoMap.put(info.getId(), info);
911             portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
912         }
913         mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
914         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
915         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
916 
917         if (mMhlController == null) {
918             return;
919         }
920         HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
921         ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
922         for (HdmiPortInfo info : mhlPortInfo) {
923             if (info.isMhlSupported()) {
924                 mhlSupportedPorts.add(info.getId());
925             }
926         }
927 
928         // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
929         // cec port info if we do not have have port that supports MHL.
930         if (mhlSupportedPorts.isEmpty()) {
931             setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo)));
932             return;
933         }
934         ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
935         for (HdmiPortInfo info : cecPortInfo) {
936             if (mhlSupportedPorts.contains(info.getId())) {
937                 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
938                         info.isCecSupported(), true, info.isArcSupported()));
939             } else {
940                 result.add(info);
941             }
942         }
943         setPortInfo(Collections.unmodifiableList(result));
944     }
945 
getPortInfo()946     List<HdmiPortInfo> getPortInfo() {
947         synchronized (mLock) {
948             return mPortInfo;
949         }
950     }
951 
setPortInfo(List<HdmiPortInfo> portInfo)952     void setPortInfo(List<HdmiPortInfo> portInfo) {
953         synchronized (mLock) {
954             mPortInfo = portInfo;
955         }
956     }
957 
958     /**
959      * Returns HDMI port information for the given port id.
960      *
961      * @param portId HDMI port id
962      * @return {@link HdmiPortInfo} for the given port
963      */
getPortInfo(int portId)964     HdmiPortInfo getPortInfo(int portId) {
965         return mPortInfoMap.get(portId, null);
966     }
967 
968     /**
969      * Returns the routing path (physical address) of the HDMI port for the given
970      * port id.
971      */
portIdToPath(int portId)972     int portIdToPath(int portId) {
973         HdmiPortInfo portInfo = getPortInfo(portId);
974         if (portInfo == null) {
975             Slog.e(TAG, "Cannot find the port info: " + portId);
976             return Constants.INVALID_PHYSICAL_ADDRESS;
977         }
978         return portInfo.getAddress();
979     }
980 
981     /**
982      * Returns the id of HDMI port located at the current device that runs this method.
983      *
984      * For TV with physical address 0x0000, target device 0x1120, we want port physical address
985      * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
986      * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
987      *
988      * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
989      *
990      * @param path the target device's physical address.
991      * @return the id of the port that the target device eventually connects to
992      * on the current device.
993      */
pathToPortId(int path)994     int pathToPortId(int path) {
995         int mask = 0xF000;
996         int finalMask = 0xF000;
997         int physicalAddress;
998         synchronized (mLock) {
999             physicalAddress = mPhysicalAddress;
1000         }
1001         int maskedAddress = physicalAddress;
1002 
1003         while (maskedAddress != 0) {
1004             maskedAddress = physicalAddress & mask;
1005             finalMask |= mask;
1006             mask >>= 4;
1007         }
1008 
1009         int portAddress = path & finalMask;
1010         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
1011     }
1012 
isValidPortId(int portId)1013     boolean isValidPortId(int portId) {
1014         return getPortInfo(portId) != null;
1015     }
1016 
1017     /**
1018      * Returns {@link Looper} for IO operation.
1019      *
1020      * <p>Declared as package-private.
1021      */
1022     @Nullable
getIoLooper()1023     Looper getIoLooper() {
1024         return mIoLooper;
1025     }
1026 
1027     @VisibleForTesting
setIoLooper(Looper ioLooper)1028     void setIoLooper(Looper ioLooper) {
1029         mIoLooper = ioLooper;
1030     }
1031 
1032     @VisibleForTesting
setMessageValidator(HdmiCecMessageValidator messageValidator)1033     void setMessageValidator(HdmiCecMessageValidator messageValidator) {
1034         mMessageValidator = messageValidator;
1035     }
1036 
1037     /**
1038      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
1039      * for tasks that are running on main service thread.
1040      *
1041      * <p>Declared as package-private.
1042      */
getServiceLooper()1043     Looper getServiceLooper() {
1044         return mHandler.getLooper();
1045     }
1046 
1047     /**
1048      * Returns physical address of the device.
1049      */
getPhysicalAddress()1050     int getPhysicalAddress() {
1051         return mCecController.getPhysicalAddress();
1052     }
1053 
1054     /**
1055      * Returns vendor id of CEC service.
1056      */
getVendorId()1057     int getVendorId() {
1058         return mCecController.getVendorId();
1059     }
1060 
1061     @ServiceThreadOnly
getDeviceInfo(int logicalAddress)1062     HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
1063         assertRunOnServiceThread();
1064         return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
1065     }
1066 
1067     @ServiceThreadOnly
getDeviceInfoByPort(int port)1068     HdmiDeviceInfo getDeviceInfoByPort(int port) {
1069         assertRunOnServiceThread();
1070         HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
1071         if (info != null) {
1072             return info.getInfo();
1073         }
1074         return null;
1075     }
1076 
1077     /**
1078      * Returns version of CEC.
1079      */
getCecVersion()1080     int getCecVersion() {
1081         return mCecController.getVersion();
1082     }
1083 
1084     /**
1085      * Whether a device of the specified physical address is connected to ARC enabled port.
1086      */
isConnectedToArcPort(int physicalAddress)1087     boolean isConnectedToArcPort(int physicalAddress) {
1088         int portId = pathToPortId(physicalAddress);
1089         if (portId != Constants.INVALID_PORT_ID) {
1090             return mPortInfoMap.get(portId).isArcSupported();
1091         }
1092         return false;
1093     }
1094 
1095     @ServiceThreadOnly
isConnected(int portId)1096     boolean isConnected(int portId) {
1097         assertRunOnServiceThread();
1098         return mCecController.isConnected(portId);
1099     }
1100 
runOnServiceThread(Runnable runnable)1101     void runOnServiceThread(Runnable runnable) {
1102         mHandler.post(runnable);
1103     }
1104 
runOnServiceThreadAtFrontOfQueue(Runnable runnable)1105     void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
1106         mHandler.postAtFrontOfQueue(runnable);
1107     }
1108 
assertRunOnServiceThread()1109     private void assertRunOnServiceThread() {
1110         if (Looper.myLooper() != mHandler.getLooper()) {
1111             throw new IllegalStateException("Should run on service thread.");
1112         }
1113     }
1114 
1115     /**
1116      * Transmit a CEC command to CEC bus.
1117      *
1118      * @param command CEC command to send out
1119      * @param callback interface used to the result of send command
1120      */
1121     @ServiceThreadOnly
sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback)1122     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
1123         assertRunOnServiceThread();
1124         if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
1125             mCecController.sendCommand(command, callback);
1126         } else {
1127             HdmiLogger.error("Invalid message type:" + command);
1128             if (callback != null) {
1129                 callback.onSendCompleted(SendMessageResult.FAIL);
1130             }
1131         }
1132     }
1133 
1134     @ServiceThreadOnly
sendCecCommand(HdmiCecMessage command)1135     void sendCecCommand(HdmiCecMessage command) {
1136         assertRunOnServiceThread();
1137         sendCecCommand(command, null);
1138     }
1139 
1140     /**
1141      * Send <Feature Abort> command on the given CEC message if possible.
1142      * If the aborted message is invalid, then it wont send the message.
1143      * @param command original command to be aborted
1144      * @param reason reason of feature abort
1145      */
1146     @ServiceThreadOnly
maySendFeatureAbortCommand(HdmiCecMessage command, int reason)1147     void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
1148         assertRunOnServiceThread();
1149         mCecController.maySendFeatureAbortCommand(command, reason);
1150     }
1151 
1152     @ServiceThreadOnly
handleCecCommand(HdmiCecMessage message)1153     boolean handleCecCommand(HdmiCecMessage message) {
1154         assertRunOnServiceThread();
1155         int errorCode = mMessageValidator.isValid(message);
1156         if (errorCode != HdmiCecMessageValidator.OK) {
1157             // We'll not response on the messages with the invalid source or destination
1158             // or with parameter length shorter than specified in the standard.
1159             if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
1160                 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
1161             }
1162             return true;
1163         }
1164 
1165         if (dispatchMessageToLocalDevice(message)) {
1166             return true;
1167         }
1168 
1169         return (!mAddressAllocated) ? mCecMessageBuffer.bufferMessage(message) : false;
1170     }
1171 
enableAudioReturnChannel(int portId, boolean enabled)1172     void enableAudioReturnChannel(int portId, boolean enabled) {
1173         mCecController.enableAudioReturnChannel(portId, enabled);
1174     }
1175 
1176     @ServiceThreadOnly
dispatchMessageToLocalDevice(HdmiCecMessage message)1177     private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
1178         assertRunOnServiceThread();
1179         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1180             if (device.dispatchMessage(message)
1181                     && message.getDestination() != Constants.ADDR_BROADCAST) {
1182                 return true;
1183             }
1184         }
1185 
1186         if (message.getDestination() != Constants.ADDR_BROADCAST) {
1187             HdmiLogger.warning("Unhandled cec command:" + message);
1188         }
1189         return false;
1190     }
1191 
1192     /**
1193      * Called when a new hotplug event is issued.
1194      *
1195      * @param portId hdmi port number where hot plug event issued.
1196      * @param connected whether to be plugged in or not
1197      */
1198     @ServiceThreadOnly
onHotplug(int portId, boolean connected)1199     void onHotplug(int portId, boolean connected) {
1200         assertRunOnServiceThread();
1201 
1202         if (connected && !isTvDevice()
1203                 && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
1204             if (isSwitchDevice()) {
1205                 initPortInfo();
1206                 HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
1207             }
1208             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1209             for (int type : mLocalDevices) {
1210                 if (type == HdmiDeviceInfo.DEVICE_PLAYBACK
1211                         && isHdmiCecNeverClaimPlaybackLogicAddr) {
1212                     continue;
1213                 }
1214                 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
1215                 if (localDevice == null) {
1216                     localDevice = HdmiCecLocalDevice.create(this, type);
1217                     localDevice.init();
1218                 }
1219                 localDevices.add(localDevice);
1220             }
1221             allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
1222         }
1223 
1224         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1225             device.onHotplug(portId, connected);
1226         }
1227         announceHotplugEvent(portId, connected);
1228     }
1229 
1230     /**
1231      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
1232      * devices.
1233      *
1234      * @param callback an interface used to get a list of all remote devices' address
1235      * @param sourceAddress a logical address of source device where sends polling message
1236      * @param pickStrategy strategy how to pick polling candidates
1237      * @param retryCount the number of retry used to send polling message to remote devices
1238      * @throws IllegalArgumentException if {@code pickStrategy} is invalid value
1239      */
1240     @ServiceThreadOnly
pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)1241     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
1242             int retryCount) {
1243         assertRunOnServiceThread();
1244         mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
1245                 retryCount);
1246     }
1247 
checkPollStrategy(int pickStrategy)1248     private int checkPollStrategy(int pickStrategy) {
1249         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
1250         if (strategy == 0) {
1251             throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
1252         }
1253         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
1254         if (iterationStrategy == 0) {
1255             throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
1256         }
1257         return strategy | iterationStrategy;
1258     }
1259 
getAllLocalDevices()1260     List<HdmiCecLocalDevice> getAllLocalDevices() {
1261         assertRunOnServiceThread();
1262         return mCecController.getLocalDeviceList();
1263     }
1264 
1265     /**
1266      * Check if a logical address is conflict with the current device's. Reallocate the logical
1267      * address of the current device if there is conflict.
1268      *
1269      * Android HDMI CEC 1.4 is handling logical address allocation in the framework side. This could
1270      * introduce delay between the logical address allocation and notifying the driver that the
1271      * address is occupied. Adding this check to avoid such case.
1272      *
1273      * @param logicalAddress logical address of the remote device that might have the same logical
1274      * address as the current device.
1275      */
checkLogicalAddressConflictAndReallocate(int logicalAddress)1276     protected void checkLogicalAddressConflictAndReallocate(int logicalAddress) {
1277         for (HdmiCecLocalDevice device : getAllLocalDevices()) {
1278             if (device.getDeviceInfo().getLogicalAddress() == logicalAddress) {
1279                 HdmiLogger.debug("allocate logical address for " + device.getDeviceInfo());
1280                 ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
1281                 localDevices.add(device);
1282                 allocateLogicalAddress(localDevices, HdmiControlService.INITIATED_BY_HOTPLUG);
1283                 return;
1284             }
1285         }
1286     }
1287 
getServiceLock()1288     Object getServiceLock() {
1289         return mLock;
1290     }
1291 
setAudioStatus(boolean mute, int volume)1292     void setAudioStatus(boolean mute, int volume) {
1293         if (!isTvDeviceEnabled()
1294                 || !tv().isSystemAudioActivated()
1295                 || !isHdmiCecVolumeControlEnabled()) {
1296             return;
1297         }
1298         AudioManager audioManager = getAudioManager();
1299         boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
1300         if (mute) {
1301             if (!muted) {
1302                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
1303             }
1304         } else {
1305             if (muted) {
1306                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
1307             }
1308             // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
1309             // volume change notification back to hdmi control service.
1310             int flag = AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME;
1311             if (0 <= volume && volume <= 100) {
1312                 Slog.i(TAG, "volume: " + volume);
1313                 flag |= AudioManager.FLAG_SHOW_UI;
1314                 audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, flag);
1315             }
1316         }
1317     }
1318 
announceSystemAudioModeChange(boolean enabled)1319     void announceSystemAudioModeChange(boolean enabled) {
1320         synchronized (mLock) {
1321             for (SystemAudioModeChangeListenerRecord record :
1322                     mSystemAudioModeChangeListenerRecords) {
1323                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1324             }
1325         }
1326     }
1327 
createDeviceInfo(int logicalAddress, int deviceType, int powerStatus)1328     private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
1329         String displayName = readStringSetting(Global.DEVICE_NAME, Build.MODEL);
1330         return new HdmiDeviceInfo(logicalAddress,
1331                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1332                 getVendorId(), displayName, powerStatus);
1333     }
1334 
1335     // Set the display name in HdmiDeviceInfo of the current devices to content provided by
1336     // Global.DEVICE_NAME. Only set and broadcast if the new name is different.
setDisplayName(String newDisplayName)1337     private void setDisplayName(String newDisplayName) {
1338         for (HdmiCecLocalDevice device : getAllLocalDevices()) {
1339             HdmiDeviceInfo deviceInfo = device.getDeviceInfo();
1340             if (deviceInfo.getDisplayName().equals(newDisplayName)) {
1341                 continue;
1342             }
1343             device.setDeviceInfo(new HdmiDeviceInfo(
1344                     deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress(),
1345                     deviceInfo.getPortId(), deviceInfo.getDeviceType(), deviceInfo.getVendorId(),
1346                     newDisplayName, deviceInfo.getDevicePowerStatus()));
1347             sendCecCommand(HdmiCecMessageBuilder.buildSetOsdNameCommand(
1348                     device.mAddress, Constants.ADDR_TV, newDisplayName));
1349         }
1350     }
1351 
1352     @ServiceThreadOnly
handleMhlHotplugEvent(int portId, boolean connected)1353     void handleMhlHotplugEvent(int portId, boolean connected) {
1354         assertRunOnServiceThread();
1355         // Hotplug event is used to add/remove MHL devices as TV input.
1356         if (connected) {
1357             HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1358             HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
1359             if (oldDevice != null) {
1360                 oldDevice.onDeviceRemoved();
1361                 Slog.i(TAG, "Old device of port " + portId + " is removed");
1362             }
1363             invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1364             updateSafeMhlInput();
1365         } else {
1366             HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
1367             if (device != null) {
1368                 device.onDeviceRemoved();
1369                 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1370                 updateSafeMhlInput();
1371             } else {
1372                 Slog.w(TAG, "No device to remove:[portId=" + portId);
1373             }
1374         }
1375         announceHotplugEvent(portId, connected);
1376     }
1377 
1378     @ServiceThreadOnly
handleMhlBusModeChanged(int portId, int busmode)1379     void handleMhlBusModeChanged(int portId, int busmode) {
1380         assertRunOnServiceThread();
1381         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1382         if (device != null) {
1383             device.setBusMode(busmode);
1384         } else {
1385             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1386                     ", busmode:" + busmode + "]");
1387         }
1388     }
1389 
1390     @ServiceThreadOnly
handleMhlBusOvercurrent(int portId, boolean on)1391     void handleMhlBusOvercurrent(int portId, boolean on) {
1392         assertRunOnServiceThread();
1393         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1394         if (device != null) {
1395             device.onBusOvercurrentDetected(on);
1396         } else {
1397             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
1398         }
1399     }
1400 
1401     @ServiceThreadOnly
handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)1402     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
1403         assertRunOnServiceThread();
1404         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1405 
1406         if (device != null) {
1407             device.setDeviceStatusChange(adopterId, deviceId);
1408         } else {
1409             Slog.w(TAG, "No mhl device exists for device status event[portId:"
1410                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1411         }
1412     }
1413 
1414     @ServiceThreadOnly
updateSafeMhlInput()1415     private void updateSafeMhlInput() {
1416         assertRunOnServiceThread();
1417         List<HdmiDeviceInfo> inputs = Collections.emptyList();
1418         SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
1419         for (int i = 0; i < devices.size(); ++i) {
1420             HdmiMhlLocalDeviceStub device = devices.valueAt(i);
1421             HdmiDeviceInfo info = device.getInfo();
1422             if (info != null) {
1423                 if (inputs.isEmpty()) {
1424                     inputs = new ArrayList<>();
1425                 }
1426                 inputs.add(device.getInfo());
1427             }
1428         }
1429         synchronized (mLock) {
1430             mMhlDevices = inputs;
1431         }
1432     }
1433 
1434     @GuardedBy("mLock")
getMhlDevicesLocked()1435     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1436         return mMhlDevices;
1437     }
1438 
1439     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1440         private final IHdmiMhlVendorCommandListener mListener;
1441 
HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener)1442         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
1443             mListener = listener;
1444         }
1445 
1446         @Override
binderDied()1447         public void binderDied() {
1448             mMhlVendorCommandListenerRecords.remove(this);
1449         }
1450     }
1451 
1452     // Record class that monitors the event of the caller of being killed. Used to clean up
1453     // the listener list and record list accordingly.
1454     private final class HdmiControlStatusChangeListenerRecord implements IBinder.DeathRecipient {
1455         private final IHdmiControlStatusChangeListener mListener;
1456 
HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener)1457         HdmiControlStatusChangeListenerRecord(IHdmiControlStatusChangeListener listener) {
1458             mListener = listener;
1459         }
1460 
1461         @Override
binderDied()1462         public void binderDied() {
1463             synchronized (mLock) {
1464                 mHdmiControlStatusChangeListenerRecords.remove(this);
1465             }
1466         }
1467 
1468         @Override
equals(Object obj)1469         public boolean equals(Object obj) {
1470             if (!(obj instanceof HdmiControlStatusChangeListenerRecord)) return false;
1471             if (obj == this) return true;
1472             HdmiControlStatusChangeListenerRecord other =
1473                     (HdmiControlStatusChangeListenerRecord) obj;
1474             return other.mListener == this.mListener;
1475         }
1476 
1477         @Override
hashCode()1478         public int hashCode() {
1479             return mListener.hashCode();
1480         }
1481     }
1482 
1483     // Record class that monitors the event of the caller of being killed. Used to clean up
1484     // the listener list and record list accordingly.
1485     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1486         private final IHdmiHotplugEventListener mListener;
1487 
HotplugEventListenerRecord(IHdmiHotplugEventListener listener)1488         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1489             mListener = listener;
1490         }
1491 
1492         @Override
binderDied()1493         public void binderDied() {
1494             synchronized (mLock) {
1495                 mHotplugEventListenerRecords.remove(this);
1496             }
1497         }
1498 
1499         @Override
equals(Object obj)1500         public boolean equals(Object obj) {
1501             if (!(obj instanceof HotplugEventListenerRecord)) return false;
1502             if (obj == this) return true;
1503             HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1504             return other.mListener == this.mListener;
1505         }
1506 
1507         @Override
hashCode()1508         public int hashCode() {
1509             return mListener.hashCode();
1510         }
1511     }
1512 
1513     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1514         private final IHdmiDeviceEventListener mListener;
1515 
DeviceEventListenerRecord(IHdmiDeviceEventListener listener)1516         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1517             mListener = listener;
1518         }
1519 
1520         @Override
binderDied()1521         public void binderDied() {
1522             synchronized (mLock) {
1523                 mDeviceEventListenerRecords.remove(this);
1524             }
1525         }
1526     }
1527 
1528     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
1529         private final IHdmiSystemAudioModeChangeListener mListener;
1530 
SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener)1531         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1532             mListener = listener;
1533         }
1534 
1535         @Override
binderDied()1536         public void binderDied() {
1537             synchronized (mLock) {
1538                 mSystemAudioModeChangeListenerRecords.remove(this);
1539             }
1540         }
1541     }
1542 
1543     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1544         private final IHdmiVendorCommandListener mListener;
1545         private final int mDeviceType;
1546 
VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType)1547         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1548             mListener = listener;
1549             mDeviceType = deviceType;
1550         }
1551 
1552         @Override
binderDied()1553         public void binderDied() {
1554             synchronized (mLock) {
1555                 mVendorCommandListenerRecords.remove(this);
1556             }
1557         }
1558     }
1559 
1560     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1561         private final IHdmiRecordListener mListener;
1562 
HdmiRecordListenerRecord(IHdmiRecordListener listener)1563         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1564             mListener = listener;
1565         }
1566 
1567         @Override
binderDied()1568         public void binderDied() {
1569             synchronized (mLock) {
1570                 if (mRecordListenerRecord == this) {
1571                     mRecordListenerRecord = null;
1572                 }
1573             }
1574         }
1575     }
1576 
enforceAccessPermission()1577     private void enforceAccessPermission() {
1578         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1579     }
1580 
1581     private final class BinderService extends IHdmiControlService.Stub {
1582         @Override
getSupportedTypes()1583         public int[] getSupportedTypes() {
1584             enforceAccessPermission();
1585             // mLocalDevices is an unmodifiable list - no lock necesary.
1586             int[] localDevices = new int[mLocalDevices.size()];
1587             for (int i = 0; i < localDevices.length; ++i) {
1588                 localDevices[i] = mLocalDevices.get(i);
1589             }
1590             return localDevices;
1591         }
1592 
1593         @Override
1594         @Nullable
getActiveSource()1595         public HdmiDeviceInfo getActiveSource() {
1596             enforceAccessPermission();
1597             HdmiCecLocalDeviceTv tv = tv();
1598             if (tv == null) {
1599                 if (isTvDevice()) {
1600                     Slog.e(TAG, "Local tv device not available.");
1601                     return null;
1602                 }
1603                 if (isPlaybackDevice()) {
1604                     // if playback device itself is the active source,
1605                     // return its own device info.
1606                     if (playback() != null && playback().mIsActiveSource) {
1607                         return playback().getDeviceInfo();
1608                     }
1609                     // Otherwise get the active source and look for it from the device list
1610                     ActiveSource activeSource = getLocalActiveSource();
1611                     // If the physical address is not set yet, return null
1612                     if (activeSource.physicalAddress == Constants.INVALID_PHYSICAL_ADDRESS) {
1613                         return null;
1614                     }
1615                     if (audioSystem() != null) {
1616                         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1617                         for (HdmiDeviceInfo info : audioSystem.getSafeCecDevicesLocked()) {
1618                             if (info.getPhysicalAddress() == activeSource.physicalAddress) {
1619                                 return info;
1620                             }
1621                         }
1622                     }
1623                     // If the device info is not in the list yet, return a device info with minimum
1624                     // information from mActiveSource.
1625                     // If the Active Source has unregistered logical address, return with an
1626                     // HdmiDeviceInfo built from physical address information only.
1627                     return HdmiUtils.isValidAddress(activeSource.logicalAddress)
1628                         ?
1629                         new HdmiDeviceInfo(activeSource.logicalAddress,
1630                             activeSource.physicalAddress,
1631                             pathToPortId(activeSource.physicalAddress),
1632                             HdmiUtils.getTypeFromAddress(activeSource.logicalAddress), 0,
1633                             HdmiUtils.getDefaultDeviceName(activeSource.logicalAddress))
1634                         :
1635                             new HdmiDeviceInfo(activeSource.physicalAddress,
1636                                 pathToPortId(activeSource.physicalAddress));
1637 
1638                 }
1639                 return null;
1640             }
1641             ActiveSource activeSource = tv.getActiveSource();
1642             if (activeSource.isValid()) {
1643                 return new HdmiDeviceInfo(activeSource.logicalAddress,
1644                         activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1645                         HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1646             }
1647             int activePath = tv.getActivePath();
1648             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1649                 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
1650                 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
1651             }
1652             return null;
1653         }
1654 
1655         @Override
deviceSelect(final int deviceId, final IHdmiControlCallback callback)1656         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1657             enforceAccessPermission();
1658             runOnServiceThread(new Runnable() {
1659                 @Override
1660                 public void run() {
1661                     if (callback == null) {
1662                         Slog.e(TAG, "Callback cannot be null");
1663                         return;
1664                     }
1665                     if (isPowerStandby()) {
1666                         Slog.e(TAG, "Device is in standby. Not handling deviceSelect");
1667                         invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1668                         return;
1669                     }
1670                     HdmiCecLocalDeviceTv tv = tv();
1671                     if (tv == null) {
1672                         if (!mAddressAllocated) {
1673                             mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1674                                     HdmiControlService.this, deviceId, callback));
1675                             return;
1676                         }
1677                         if (isTvDevice()) {
1678                             Slog.e(TAG, "Local tv device not available");
1679                             return;
1680                         }
1681                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1682                         return;
1683                     }
1684                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1685                     if (device != null) {
1686                         if (device.getPortId() == tv.getActivePortId()) {
1687                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1688                             return;
1689                         }
1690                         // Upon selecting MHL device, we send RAP[Content On] to wake up
1691                         // the connected mobile device, start routing control to switch ports.
1692                         // callback is handled by MHL action.
1693                         device.turnOn(callback);
1694                         tv.doManualPortSwitching(device.getPortId(), null);
1695                         return;
1696                     }
1697                     tv.deviceSelect(deviceId, callback);
1698                 }
1699             });
1700         }
1701 
1702         @Override
portSelect(final int portId, final IHdmiControlCallback callback)1703         public void portSelect(final int portId, final IHdmiControlCallback callback) {
1704             enforceAccessPermission();
1705             runOnServiceThread(new Runnable() {
1706                 @Override
1707                 public void run() {
1708                     if (callback == null) {
1709                         Slog.e(TAG, "Callback cannot be null");
1710                         return;
1711                     }
1712                     if (isPowerStandby()) {
1713                         Slog.e(TAG, "Device is in standby. Not handling portSelect");
1714                         invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
1715                         return;
1716                     }
1717                     HdmiCecLocalDeviceTv tv = tv();
1718                     if (tv != null) {
1719                         tv.doManualPortSwitching(portId, callback);
1720                         return;
1721                     }
1722                     HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1723                     if (audioSystem != null) {
1724                         audioSystem.doManualPortSwitching(portId, callback);
1725                         return;
1726                     }
1727 
1728                     if (!mAddressAllocated) {
1729                         mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1730                                 HdmiControlService.this, portId, callback));
1731                         return;
1732                     }
1733                     Slog.w(TAG, "Local device not available");
1734                     invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1735                     return;
1736                 }
1737             });
1738         }
1739 
1740         @Override
sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed)1741         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1742             enforceAccessPermission();
1743             runOnServiceThread(new Runnable() {
1744                 @Override
1745                 public void run() {
1746                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1747                     if (device != null) {
1748                         device.sendKeyEvent(keyCode, isPressed);
1749                         return;
1750                     }
1751                     if (mCecController != null) {
1752                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1753                         if (localDevice == null) {
1754                             Slog.w(TAG, "Local device not available to send key event.");
1755                             return;
1756                         }
1757                         localDevice.sendKeyEvent(keyCode, isPressed);
1758                     }
1759                 }
1760             });
1761         }
1762 
1763         @Override
sendVolumeKeyEvent( final int deviceType, final int keyCode, final boolean isPressed)1764         public void sendVolumeKeyEvent(
1765             final int deviceType, final int keyCode, final boolean isPressed) {
1766             enforceAccessPermission();
1767             runOnServiceThread(new Runnable() {
1768                 @Override
1769                 public void run() {
1770                     if (mCecController == null) {
1771                         Slog.w(TAG, "CEC controller not available to send volume key event.");
1772                         return;
1773                     }
1774                     HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1775                     if (localDevice == null) {
1776                         Slog.w(TAG, "Local device " + deviceType
1777                               + " not available to send volume key event.");
1778                         return;
1779                     }
1780                     localDevice.sendVolumeKeyEvent(keyCode, isPressed);
1781                 }
1782             });
1783         }
1784 
1785         @Override
oneTouchPlay(final IHdmiControlCallback callback)1786         public void oneTouchPlay(final IHdmiControlCallback callback) {
1787             enforceAccessPermission();
1788             int pid = Binder.getCallingPid();
1789             Slog.d(TAG, "Proccess pid: " + pid + " is calling oneTouchPlay.");
1790             runOnServiceThread(new Runnable() {
1791                 @Override
1792                 public void run() {
1793                     HdmiControlService.this.oneTouchPlay(callback);
1794                 }
1795             });
1796         }
1797 
1798         @Override
queryDisplayStatus(final IHdmiControlCallback callback)1799         public void queryDisplayStatus(final IHdmiControlCallback callback) {
1800             enforceAccessPermission();
1801             runOnServiceThread(new Runnable() {
1802                 @Override
1803                 public void run() {
1804                     HdmiControlService.this.queryDisplayStatus(callback);
1805                 }
1806             });
1807         }
1808 
1809         @Override
addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)1810         public void addHdmiControlStatusChangeListener(
1811                 final IHdmiControlStatusChangeListener listener) {
1812             enforceAccessPermission();
1813             HdmiControlService.this.addHdmiControlStatusChangeListener(listener);
1814         }
1815 
1816         @Override
removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)1817         public void removeHdmiControlStatusChangeListener(
1818                 final IHdmiControlStatusChangeListener listener) {
1819             enforceAccessPermission();
1820             HdmiControlService.this.removeHdmiControlStatusChangeListener(listener);
1821         }
1822 
1823         @Override
addHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)1824         public void addHdmiCecVolumeControlFeatureListener(
1825                 final IHdmiCecVolumeControlFeatureListener listener) {
1826             enforceAccessPermission();
1827             HdmiControlService.this.addHdmiCecVolumeControlFeatureListener(listener);
1828         }
1829 
1830         @Override
removeHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)1831         public void removeHdmiCecVolumeControlFeatureListener(
1832                 final IHdmiCecVolumeControlFeatureListener listener) {
1833             enforceAccessPermission();
1834             HdmiControlService.this.removeHdmiControlVolumeControlStatusChangeListener(listener);
1835         }
1836 
1837 
1838         @Override
addHotplugEventListener(final IHdmiHotplugEventListener listener)1839         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1840             enforceAccessPermission();
1841             HdmiControlService.this.addHotplugEventListener(listener);
1842         }
1843 
1844         @Override
removeHotplugEventListener(final IHdmiHotplugEventListener listener)1845         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1846             enforceAccessPermission();
1847             HdmiControlService.this.removeHotplugEventListener(listener);
1848         }
1849 
1850         @Override
addDeviceEventListener(final IHdmiDeviceEventListener listener)1851         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1852             enforceAccessPermission();
1853             HdmiControlService.this.addDeviceEventListener(listener);
1854         }
1855 
1856         @Override
getPortInfo()1857         public List<HdmiPortInfo> getPortInfo() {
1858             enforceAccessPermission();
1859             return HdmiControlService.this.getPortInfo() == null
1860                 ? Collections.<HdmiPortInfo>emptyList()
1861                 : HdmiControlService.this.getPortInfo();
1862         }
1863 
1864         @Override
canChangeSystemAudioMode()1865         public boolean canChangeSystemAudioMode() {
1866             enforceAccessPermission();
1867             HdmiCecLocalDeviceTv tv = tv();
1868             if (tv == null) {
1869                 return false;
1870             }
1871             return tv.hasSystemAudioDevice();
1872         }
1873 
1874         @Override
getSystemAudioMode()1875         public boolean getSystemAudioMode() {
1876             // TODO(shubang): handle getSystemAudioMode() for all device types
1877             enforceAccessPermission();
1878             HdmiCecLocalDeviceTv tv = tv();
1879             HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1880             return (tv != null && tv.isSystemAudioActivated())
1881                     || (audioSystem != null && audioSystem.isSystemAudioActivated());
1882         }
1883 
1884         @Override
getPhysicalAddress()1885         public int getPhysicalAddress() {
1886             enforceAccessPermission();
1887             synchronized (mLock) {
1888                 return mPhysicalAddress;
1889             }
1890         }
1891 
1892         @Override
setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback)1893         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1894             enforceAccessPermission();
1895             runOnServiceThread(new Runnable() {
1896                 @Override
1897                 public void run() {
1898                     HdmiCecLocalDeviceTv tv = tv();
1899                     if (tv == null) {
1900                         Slog.w(TAG, "Local tv device not available");
1901                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1902                         return;
1903                     }
1904                     tv.changeSystemAudioMode(enabled, callback);
1905                 }
1906             });
1907         }
1908 
1909         @Override
addSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1910         public void addSystemAudioModeChangeListener(
1911                 final IHdmiSystemAudioModeChangeListener listener) {
1912             enforceAccessPermission();
1913             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1914         }
1915 
1916         @Override
removeSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1917         public void removeSystemAudioModeChangeListener(
1918                 final IHdmiSystemAudioModeChangeListener listener) {
1919             enforceAccessPermission();
1920             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1921         }
1922 
1923         @Override
setInputChangeListener(final IHdmiInputChangeListener listener)1924         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1925             enforceAccessPermission();
1926             HdmiControlService.this.setInputChangeListener(listener);
1927         }
1928 
1929         @Override
getInputDevices()1930         public List<HdmiDeviceInfo> getInputDevices() {
1931             enforceAccessPermission();
1932             // No need to hold the lock for obtaining TV device as the local device instance
1933             // is preserved while the HDMI control is enabled.
1934             HdmiCecLocalDeviceTv tv = tv();
1935             synchronized (mLock) {
1936                 List<HdmiDeviceInfo> cecDevices = (tv == null)
1937                         ? Collections.<HdmiDeviceInfo>emptyList()
1938                         : tv.getSafeExternalInputsLocked();
1939                 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1940             }
1941         }
1942 
1943         // Returns all the CEC devices on the bus including system audio, switch,
1944         // even those of reserved type.
1945         @Override
getDeviceList()1946         public List<HdmiDeviceInfo> getDeviceList() {
1947             enforceAccessPermission();
1948             HdmiCecLocalDeviceTv tv = tv();
1949             if (tv != null) {
1950                 synchronized (mLock) {
1951                     return tv.getSafeCecDevicesLocked();
1952                 }
1953             } else {
1954                 HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
1955                 synchronized (mLock) {
1956                     return (audioSystem == null)
1957                         ? Collections.<HdmiDeviceInfo>emptyList()
1958                         : audioSystem.getSafeCecDevicesLocked();
1959                 }
1960             }
1961         }
1962 
1963         @Override
powerOffRemoteDevice(int logicalAddress, int powerStatus)1964         public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {
1965             enforceAccessPermission();
1966             runOnServiceThread(new Runnable() {
1967                 @Override
1968                 public void run() {
1969                     Slog.w(TAG, "Device "
1970                             + logicalAddress + " power status is " + powerStatus
1971                             + " before standby command sent out");
1972                     sendCecCommand(HdmiCecMessageBuilder.buildStandby(
1973                             getRemoteControlSourceAddress(), logicalAddress));
1974                 }
1975             });
1976         }
1977 
1978         @Override
powerOnRemoteDevice(int logicalAddress, int powerStatus)1979         public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {
1980             // TODO(amyjojo): implement the method
1981         }
1982 
1983         @Override
1984         // TODO(b/128427908): add a result callback
askRemoteDeviceToBecomeActiveSource(int physicalAddress)1985         public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {
1986             enforceAccessPermission();
1987             runOnServiceThread(new Runnable() {
1988                 @Override
1989                 public void run() {
1990                     HdmiCecMessage setStreamPath = HdmiCecMessageBuilder.buildSetStreamPath(
1991                             getRemoteControlSourceAddress(), physicalAddress);
1992                     if (pathToPortId(physicalAddress) != Constants.INVALID_PORT_ID) {
1993                         if (getSwitchDevice() != null) {
1994                             getSwitchDevice().handleSetStreamPath(setStreamPath);
1995                         } else {
1996                             Slog.e(TAG, "Can't get the correct local device to handle routing.");
1997                         }
1998                     }
1999                     sendCecCommand(setStreamPath);
2000                 }
2001             });
2002         }
2003 
2004         @Override
setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex)2005         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
2006                 final int maxIndex) {
2007             enforceAccessPermission();
2008             runOnServiceThread(new Runnable() {
2009                 @Override
2010                 public void run() {
2011                     HdmiCecLocalDeviceTv tv = tv();
2012                     if (tv == null) {
2013                         Slog.w(TAG, "Local tv device not available");
2014                         return;
2015                     }
2016                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
2017                 }
2018             });
2019         }
2020 
2021         @Override
setSystemAudioMute(final boolean mute)2022         public void setSystemAudioMute(final boolean mute) {
2023             enforceAccessPermission();
2024             runOnServiceThread(new Runnable() {
2025                 @Override
2026                 public void run() {
2027                     HdmiCecLocalDeviceTv tv = tv();
2028                     if (tv == null) {
2029                         Slog.w(TAG, "Local tv device not available");
2030                         return;
2031                     }
2032                     tv.changeMute(mute);
2033                 }
2034             });
2035         }
2036 
2037         @Override
setArcMode(final boolean enabled)2038         public void setArcMode(final boolean enabled) {
2039             enforceAccessPermission();
2040             runOnServiceThread(new Runnable() {
2041                 @Override
2042                 public void run() {
2043                     HdmiCecLocalDeviceTv tv = tv();
2044                     if (tv == null) {
2045                         Slog.w(TAG, "Local tv device not available to change arc mode.");
2046                         return;
2047                     }
2048                 }
2049             });
2050         }
2051 
2052         @Override
setProhibitMode(final boolean enabled)2053         public void setProhibitMode(final boolean enabled) {
2054             enforceAccessPermission();
2055             if (!isTvDevice()) {
2056                 return;
2057             }
2058             HdmiControlService.this.setProhibitMode(enabled);
2059         }
2060 
2061         @Override
addVendorCommandListener(final IHdmiVendorCommandListener listener, final int deviceType)2062         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
2063                 final int deviceType) {
2064             enforceAccessPermission();
2065             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
2066         }
2067 
2068         @Override
sendVendorCommand(final int deviceType, final int targetAddress, final byte[] params, final boolean hasVendorId)2069         public void sendVendorCommand(final int deviceType, final int targetAddress,
2070                 final byte[] params, final boolean hasVendorId) {
2071             enforceAccessPermission();
2072             runOnServiceThread(new Runnable() {
2073                 @Override
2074                 public void run() {
2075                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2076                     if (device == null) {
2077                         Slog.w(TAG, "Local device not available");
2078                         return;
2079                     }
2080                     if (hasVendorId) {
2081                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
2082                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
2083                                 getVendorId(), params));
2084                     } else {
2085                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
2086                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
2087                     }
2088                 }
2089             });
2090         }
2091 
2092         @Override
sendStandby(final int deviceType, final int deviceId)2093         public void sendStandby(final int deviceType, final int deviceId) {
2094             enforceAccessPermission();
2095             runOnServiceThread(new Runnable() {
2096                 @Override
2097                 public void run() {
2098                     HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
2099                     if (mhlDevice != null) {
2100                         mhlDevice.sendStandby();
2101                         return;
2102                     }
2103                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2104                     if (device == null) {
2105                         device = audioSystem();
2106                     }
2107                     if (device == null) {
2108                         Slog.w(TAG, "Local device not available");
2109                         return;
2110                     }
2111                     device.sendStandby(deviceId);
2112                 }
2113             });
2114         }
2115 
2116         @Override
setHdmiRecordListener(IHdmiRecordListener listener)2117         public void setHdmiRecordListener(IHdmiRecordListener listener) {
2118             enforceAccessPermission();
2119             HdmiControlService.this.setHdmiRecordListener(listener);
2120         }
2121 
2122         @Override
startOneTouchRecord(final int recorderAddress, final byte[] recordSource)2123         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
2124             enforceAccessPermission();
2125             runOnServiceThread(new Runnable() {
2126                 @Override
2127                 public void run() {
2128                     if (!isTvDeviceEnabled()) {
2129                         Slog.w(TAG, "TV device is not enabled.");
2130                         return;
2131                     }
2132                     tv().startOneTouchRecord(recorderAddress, recordSource);
2133                 }
2134             });
2135         }
2136 
2137         @Override
stopOneTouchRecord(final int recorderAddress)2138         public void stopOneTouchRecord(final int recorderAddress) {
2139             enforceAccessPermission();
2140             runOnServiceThread(new Runnable() {
2141                 @Override
2142                 public void run() {
2143                     if (!isTvDeviceEnabled()) {
2144                         Slog.w(TAG, "TV device is not enabled.");
2145                         return;
2146                     }
2147                     tv().stopOneTouchRecord(recorderAddress);
2148                 }
2149             });
2150         }
2151 
2152         @Override
startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)2153         public void startTimerRecording(final int recorderAddress, final int sourceType,
2154                 final byte[] recordSource) {
2155             enforceAccessPermission();
2156             runOnServiceThread(new Runnable() {
2157                 @Override
2158                 public void run() {
2159                     if (!isTvDeviceEnabled()) {
2160                         Slog.w(TAG, "TV device is not enabled.");
2161                         return;
2162                     }
2163                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
2164                 }
2165             });
2166         }
2167 
2168         @Override
clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)2169         public void clearTimerRecording(final int recorderAddress, final int sourceType,
2170                 final byte[] recordSource) {
2171             enforceAccessPermission();
2172             runOnServiceThread(new Runnable() {
2173                 @Override
2174                 public void run() {
2175                     if (!isTvDeviceEnabled()) {
2176                         Slog.w(TAG, "TV device is not enabled.");
2177                         return;
2178                     }
2179                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
2180                 }
2181             });
2182         }
2183 
2184         @Override
sendMhlVendorCommand(final int portId, final int offset, final int length, final byte[] data)2185         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
2186                 final byte[] data) {
2187             enforceAccessPermission();
2188             runOnServiceThread(new Runnable() {
2189                 @Override
2190                 public void run() {
2191                     if (!isControlEnabled()) {
2192                         Slog.w(TAG, "Hdmi control is disabled.");
2193                         return ;
2194                     }
2195                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2196                     if (device == null) {
2197                         Slog.w(TAG, "Invalid port id:" + portId);
2198                         return;
2199                     }
2200                     mMhlController.sendVendorCommand(portId, offset, length, data);
2201                 }
2202             });
2203         }
2204 
2205         @Override
addHdmiMhlVendorCommandListener( IHdmiMhlVendorCommandListener listener)2206         public void addHdmiMhlVendorCommandListener(
2207                 IHdmiMhlVendorCommandListener listener) {
2208             enforceAccessPermission();
2209             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
2210         }
2211 
2212         @Override
setStandbyMode(final boolean isStandbyModeOn)2213         public void setStandbyMode(final boolean isStandbyModeOn) {
2214             enforceAccessPermission();
2215             runOnServiceThread(new Runnable() {
2216                 @Override
2217                 public void run() {
2218                     HdmiControlService.this.setStandbyMode(isStandbyModeOn);
2219                 }
2220             });
2221         }
2222 
2223         @Override
isHdmiCecVolumeControlEnabled()2224         public boolean isHdmiCecVolumeControlEnabled() {
2225             enforceAccessPermission();
2226             return HdmiControlService.this.isHdmiCecVolumeControlEnabled();
2227         }
2228 
2229         @Override
setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled)2230         public void setHdmiCecVolumeControlEnabled(final boolean isHdmiCecVolumeControlEnabled) {
2231             enforceAccessPermission();
2232             long token = Binder.clearCallingIdentity();
2233             try {
2234                 HdmiControlService.this.setHdmiCecVolumeControlEnabled(
2235                         isHdmiCecVolumeControlEnabled);
2236             } finally {
2237                 Binder.restoreCallingIdentity(token);
2238             }
2239         }
2240 
2241         @Override
reportAudioStatus(final int deviceType, final int volume, final int maxVolume, final boolean isMute)2242         public void reportAudioStatus(final int deviceType, final int volume, final int maxVolume,
2243                 final boolean isMute) {
2244             enforceAccessPermission();
2245             runOnServiceThread(new Runnable() {
2246                 @Override
2247                 public void run() {
2248                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
2249                     if (device == null) {
2250                         Slog.w(TAG, "Local device not available");
2251                         return;
2252                     }
2253                     if (audioSystem() == null) {
2254                         Slog.w(TAG, "audio system is not available");
2255                         return;
2256                     }
2257                     if (!audioSystem().isSystemAudioActivated()) {
2258                         Slog.w(TAG, "audio system is not in system audio mode");
2259                         return;
2260                     }
2261                     audioSystem().reportAudioStatus(Constants.ADDR_TV);
2262                 }
2263             });
2264         }
2265 
2266         @Override
setSystemAudioModeOnForAudioOnlySource()2267         public void setSystemAudioModeOnForAudioOnlySource() {
2268             enforceAccessPermission();
2269             runOnServiceThread(new Runnable() {
2270                 @Override
2271                 public void run() {
2272                     if (!isAudioSystemDevice()) {
2273                         Slog.e(TAG, "Not an audio system device. Won't set system audio mode on");
2274                         return;
2275                     }
2276                     if (audioSystem() == null) {
2277                         Slog.e(TAG, "Audio System local device is not registered");
2278                         return;
2279                     }
2280                     if (!audioSystem().checkSupportAndSetSystemAudioMode(true)) {
2281                         Slog.e(TAG, "System Audio Mode is not supported.");
2282                         return;
2283                     }
2284                     sendCecCommand(
2285                             HdmiCecMessageBuilder.buildSetSystemAudioMode(
2286                                     audioSystem().mAddress, Constants.ADDR_BROADCAST, true));
2287                 }
2288             });
2289         }
2290 
2291         @Override
dump(FileDescriptor fd, final PrintWriter writer, String[] args)2292         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
2293             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
2294             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
2295 
2296             pw.println("mProhibitMode: " + mProhibitMode);
2297             pw.println("mPowerStatus: " + mPowerStatus);
2298 
2299             // System settings
2300             pw.println("System_settings:");
2301             pw.increaseIndent();
2302             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
2303             pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
2304             pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
2305             pw.println("mHdmiCecVolumeControlEnabled " + mHdmiCecVolumeControlEnabled);
2306             pw.decreaseIndent();
2307 
2308             pw.println("mMhlController: ");
2309             pw.increaseIndent();
2310             mMhlController.dump(pw);
2311             pw.decreaseIndent();
2312 
2313             HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo);
2314             if (mCecController != null) {
2315                 pw.println("mCecController: ");
2316                 pw.increaseIndent();
2317                 mCecController.dump(pw);
2318                 pw.decreaseIndent();
2319             }
2320         }
2321     }
2322 
2323     // Get the source address to send out commands to devices connected to the current device
2324     // when other services interact with HdmiControlService.
getRemoteControlSourceAddress()2325     private int getRemoteControlSourceAddress() {
2326         if (isAudioSystemDevice()) {
2327             return audioSystem().getDeviceInfo().getLogicalAddress();
2328         } else if (isPlaybackDevice()) {
2329             return playback().getDeviceInfo().getLogicalAddress();
2330         }
2331         return ADDR_UNREGISTERED;
2332     }
2333 
2334     // Get the switch device to do CEC routing control
2335     @Nullable
getSwitchDevice()2336     private HdmiCecLocalDeviceSource getSwitchDevice() {
2337         if (isAudioSystemDevice()) {
2338             return audioSystem();
2339         }
2340         if (isPlaybackDevice()) {
2341             return playback();
2342         }
2343         return null;
2344     }
2345 
2346     @ServiceThreadOnly
2347     @VisibleForTesting
oneTouchPlay(final IHdmiControlCallback callback)2348     protected void oneTouchPlay(final IHdmiControlCallback callback) {
2349         assertRunOnServiceThread();
2350         if (!mAddressAllocated) {
2351             mOtpCallbackPendingAddressAllocation = callback;
2352             Slog.d(TAG, "Local device is under address allocation. "
2353                         + "Save OTP callback for later process.");
2354             return;
2355         }
2356 
2357         HdmiCecLocalDeviceSource source = playback();
2358         if (source == null) {
2359             source = audioSystem();
2360         }
2361 
2362         if (source == null) {
2363             Slog.w(TAG, "Local source device not available");
2364             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
2365             return;
2366         }
2367         source.oneTouchPlay(callback);
2368     }
2369 
2370     @ServiceThreadOnly
queryDisplayStatus(final IHdmiControlCallback callback)2371     private void queryDisplayStatus(final IHdmiControlCallback callback) {
2372         assertRunOnServiceThread();
2373         if (!mAddressAllocated) {
2374             mDisplayStatusCallback = callback;
2375             Slog.d(TAG, "Local device is under address allocation. "
2376                         + "Queue display callback for later process.");
2377             return;
2378         }
2379 
2380         HdmiCecLocalDevicePlayback source = playback();
2381         if (source == null) {
2382             Slog.w(TAG, "Local playback device not available");
2383             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
2384             return;
2385         }
2386         source.queryDisplayStatus(callback);
2387     }
2388 
addHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2389     private void addHdmiControlStatusChangeListener(
2390             final IHdmiControlStatusChangeListener listener) {
2391         final HdmiControlStatusChangeListenerRecord record =
2392                 new HdmiControlStatusChangeListenerRecord(listener);
2393         try {
2394             listener.asBinder().linkToDeath(record, 0);
2395         } catch (RemoteException e) {
2396             Slog.w(TAG, "Listener already died");
2397             return;
2398         }
2399         synchronized (mLock) {
2400             mHdmiControlStatusChangeListenerRecords.add(record);
2401         }
2402 
2403         // Inform the listener of the initial state of each HDMI port by generating
2404         // hotplug events.
2405         runOnServiceThread(new Runnable() {
2406             @Override
2407             public void run() {
2408                 synchronized (mLock) {
2409                     if (!mHdmiControlStatusChangeListenerRecords.contains(record)) return;
2410                 }
2411 
2412                 // Return the current status of mHdmiControlEnabled;
2413                 synchronized (mLock) {
2414                     invokeHdmiControlStatusChangeListenerLocked(listener, mHdmiControlEnabled);
2415                 }
2416             }
2417         });
2418     }
2419 
removeHdmiControlStatusChangeListener( final IHdmiControlStatusChangeListener listener)2420     private void removeHdmiControlStatusChangeListener(
2421             final IHdmiControlStatusChangeListener listener) {
2422         synchronized (mLock) {
2423             for (HdmiControlStatusChangeListenerRecord record :
2424                     mHdmiControlStatusChangeListenerRecords) {
2425                 if (record.mListener.asBinder() == listener.asBinder()) {
2426                     listener.asBinder().unlinkToDeath(record, 0);
2427                     mHdmiControlStatusChangeListenerRecords.remove(record);
2428                     break;
2429                 }
2430             }
2431         }
2432     }
2433 
2434     @VisibleForTesting
addHdmiCecVolumeControlFeatureListener( final IHdmiCecVolumeControlFeatureListener listener)2435     void addHdmiCecVolumeControlFeatureListener(
2436             final IHdmiCecVolumeControlFeatureListener listener) {
2437         mHdmiCecVolumeControlFeatureListenerRecords.register(listener);
2438 
2439         runOnServiceThread(new Runnable() {
2440             @Override
2441             public void run() {
2442                 // Return the current status of mHdmiCecVolumeControlEnabled;
2443                 synchronized (mLock) {
2444                     try {
2445                         listener.onHdmiCecVolumeControlFeature(mHdmiCecVolumeControlEnabled);
2446                     } catch (RemoteException e) {
2447                         Slog.e(TAG, "Failed to report HdmiControlVolumeControlStatusChange: "
2448                                 + mHdmiCecVolumeControlEnabled, e);
2449                     }
2450                 }
2451             }
2452         });
2453     }
2454 
2455     @VisibleForTesting
removeHdmiControlVolumeControlStatusChangeListener( final IHdmiCecVolumeControlFeatureListener listener)2456     void removeHdmiControlVolumeControlStatusChangeListener(
2457             final IHdmiCecVolumeControlFeatureListener listener) {
2458         mHdmiCecVolumeControlFeatureListenerRecords.unregister(listener);
2459     }
2460 
addHotplugEventListener(final IHdmiHotplugEventListener listener)2461     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
2462         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
2463         try {
2464             listener.asBinder().linkToDeath(record, 0);
2465         } catch (RemoteException e) {
2466             Slog.w(TAG, "Listener already died");
2467             return;
2468         }
2469         synchronized (mLock) {
2470             mHotplugEventListenerRecords.add(record);
2471         }
2472 
2473         // Inform the listener of the initial state of each HDMI port by generating
2474         // hotplug events.
2475         runOnServiceThread(new Runnable() {
2476             @Override
2477             public void run() {
2478                 synchronized (mLock) {
2479                     if (!mHotplugEventListenerRecords.contains(record)) return;
2480                 }
2481                 for (HdmiPortInfo port : getPortInfo()) {
2482                     HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
2483                             mCecController.isConnected(port.getId()));
2484                     synchronized (mLock) {
2485                         invokeHotplugEventListenerLocked(listener, event);
2486                     }
2487                 }
2488             }
2489         });
2490     }
2491 
removeHotplugEventListener(IHdmiHotplugEventListener listener)2492     private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
2493         synchronized (mLock) {
2494             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2495                 if (record.mListener.asBinder() == listener.asBinder()) {
2496                     listener.asBinder().unlinkToDeath(record, 0);
2497                     mHotplugEventListenerRecords.remove(record);
2498                     break;
2499                 }
2500             }
2501         }
2502     }
2503 
addDeviceEventListener(IHdmiDeviceEventListener listener)2504     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
2505         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
2506         try {
2507             listener.asBinder().linkToDeath(record, 0);
2508         } catch (RemoteException e) {
2509             Slog.w(TAG, "Listener already died");
2510             return;
2511         }
2512         synchronized (mLock) {
2513             mDeviceEventListenerRecords.add(record);
2514         }
2515     }
2516 
invokeDeviceEventListeners(HdmiDeviceInfo device, int status)2517     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
2518         synchronized (mLock) {
2519             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
2520                 try {
2521                     record.mListener.onStatusChanged(device, status);
2522                 } catch (RemoteException e) {
2523                     Slog.e(TAG, "Failed to report device event:" + e);
2524                 }
2525             }
2526         }
2527     }
2528 
addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener)2529     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
2530         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
2531                 listener);
2532         try {
2533             listener.asBinder().linkToDeath(record, 0);
2534         } catch (RemoteException e) {
2535             Slog.w(TAG, "Listener already died");
2536             return;
2537         }
2538         synchronized (mLock) {
2539             mSystemAudioModeChangeListenerRecords.add(record);
2540         }
2541     }
2542 
removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener)2543     private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
2544         synchronized (mLock) {
2545             for (SystemAudioModeChangeListenerRecord record :
2546                     mSystemAudioModeChangeListenerRecords) {
2547                 if (record.mListener.asBinder() == listener) {
2548                     listener.asBinder().unlinkToDeath(record, 0);
2549                     mSystemAudioModeChangeListenerRecords.remove(record);
2550                     break;
2551                 }
2552             }
2553         }
2554     }
2555 
2556     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
2557         private final IHdmiInputChangeListener mListener;
2558 
InputChangeListenerRecord(IHdmiInputChangeListener listener)2559         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
2560             mListener = listener;
2561         }
2562 
2563         @Override
binderDied()2564         public void binderDied() {
2565             synchronized (mLock) {
2566                 if (mInputChangeListenerRecord == this) {
2567                     mInputChangeListenerRecord = null;
2568                 }
2569             }
2570         }
2571     }
2572 
setInputChangeListener(IHdmiInputChangeListener listener)2573     private void setInputChangeListener(IHdmiInputChangeListener listener) {
2574         synchronized (mLock) {
2575             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
2576             try {
2577                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
2578             } catch (RemoteException e) {
2579                 Slog.w(TAG, "Listener already died");
2580                 return;
2581             }
2582         }
2583     }
2584 
invokeInputChangeListener(HdmiDeviceInfo info)2585     void invokeInputChangeListener(HdmiDeviceInfo info) {
2586         synchronized (mLock) {
2587             if (mInputChangeListenerRecord != null) {
2588                 try {
2589                     mInputChangeListenerRecord.mListener.onChanged(info);
2590                 } catch (RemoteException e) {
2591                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
2592                 }
2593             }
2594         }
2595     }
2596 
setHdmiRecordListener(IHdmiRecordListener listener)2597     private void setHdmiRecordListener(IHdmiRecordListener listener) {
2598         synchronized (mLock) {
2599             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
2600             try {
2601                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
2602             } catch (RemoteException e) {
2603                 Slog.w(TAG, "Listener already died.", e);
2604             }
2605         }
2606     }
2607 
invokeRecordRequestListener(int recorderAddress)2608     byte[] invokeRecordRequestListener(int recorderAddress) {
2609         synchronized (mLock) {
2610             if (mRecordListenerRecord != null) {
2611                 try {
2612                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
2613                 } catch (RemoteException e) {
2614                     Slog.w(TAG, "Failed to start record.", e);
2615                 }
2616             }
2617             return EmptyArray.BYTE;
2618         }
2619     }
2620 
invokeOneTouchRecordResult(int recorderAddress, int result)2621     void invokeOneTouchRecordResult(int recorderAddress, int result) {
2622         synchronized (mLock) {
2623             if (mRecordListenerRecord != null) {
2624                 try {
2625                     mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
2626                 } catch (RemoteException e) {
2627                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
2628                 }
2629             }
2630         }
2631     }
2632 
invokeTimerRecordingResult(int recorderAddress, int result)2633     void invokeTimerRecordingResult(int recorderAddress, int result) {
2634         synchronized (mLock) {
2635             if (mRecordListenerRecord != null) {
2636                 try {
2637                     mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
2638                 } catch (RemoteException e) {
2639                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
2640                 }
2641             }
2642         }
2643     }
2644 
invokeClearTimerRecordingResult(int recorderAddress, int result)2645     void invokeClearTimerRecordingResult(int recorderAddress, int result) {
2646         synchronized (mLock) {
2647             if (mRecordListenerRecord != null) {
2648                 try {
2649                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
2650                             result);
2651                 } catch (RemoteException e) {
2652                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
2653                 }
2654             }
2655         }
2656     }
2657 
invokeCallback(IHdmiControlCallback callback, int result)2658     private void invokeCallback(IHdmiControlCallback callback, int result) {
2659         try {
2660             callback.onComplete(result);
2661         } catch (RemoteException e) {
2662             Slog.e(TAG, "Invoking callback failed:" + e);
2663         }
2664     }
2665 
invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, boolean enabled)2666     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
2667             boolean enabled) {
2668         try {
2669             listener.onStatusChanged(enabled);
2670         } catch (RemoteException e) {
2671             Slog.e(TAG, "Invoking callback failed:" + e);
2672         }
2673     }
2674 
announceHotplugEvent(int portId, boolean connected)2675     private void announceHotplugEvent(int portId, boolean connected) {
2676         HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
2677         synchronized (mLock) {
2678             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
2679                 invokeHotplugEventListenerLocked(record.mListener, event);
2680             }
2681         }
2682     }
2683 
invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event)2684     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
2685             HdmiHotplugEvent event) {
2686         try {
2687             listener.onReceived(event);
2688         } catch (RemoteException e) {
2689             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
2690         }
2691     }
2692 
announceHdmiControlStatusChange(boolean isEnabled)2693     private void announceHdmiControlStatusChange(boolean isEnabled) {
2694         assertRunOnServiceThread();
2695         synchronized (mLock) {
2696             for (HdmiControlStatusChangeListenerRecord record :
2697                     mHdmiControlStatusChangeListenerRecords) {
2698                 invokeHdmiControlStatusChangeListenerLocked(record.mListener, isEnabled);
2699             }
2700         }
2701     }
2702 
invokeHdmiControlStatusChangeListenerLocked( IHdmiControlStatusChangeListener listener, boolean isEnabled)2703     private void invokeHdmiControlStatusChangeListenerLocked(
2704             IHdmiControlStatusChangeListener listener, boolean isEnabled) {
2705         if (isEnabled) {
2706             queryDisplayStatus(new IHdmiControlCallback.Stub() {
2707                 public void onComplete(int status) {
2708                     boolean isAvailable = true;
2709                     if (status == HdmiControlManager.POWER_STATUS_UNKNOWN
2710                             || status == HdmiControlManager.RESULT_EXCEPTION
2711                             || status == HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE) {
2712                         isAvailable = false;
2713                     }
2714 
2715                     try {
2716                         listener.onStatusChange(isEnabled, isAvailable);
2717                     } catch (RemoteException e) {
2718                         Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2719                                 + " isAvailable: " + isAvailable, e);
2720                     }
2721                 }
2722             });
2723             return;
2724         }
2725 
2726         try {
2727             listener.onStatusChange(isEnabled, false);
2728         } catch (RemoteException e) {
2729             Slog.e(TAG, "Failed to report HdmiControlStatusChange: " + isEnabled
2730                     + " isAvailable: " + false, e);
2731         }
2732     }
2733 
announceHdmiCecVolumeControlFeatureChange(boolean isEnabled)2734     private void announceHdmiCecVolumeControlFeatureChange(boolean isEnabled) {
2735         assertRunOnServiceThread();
2736         mHdmiCecVolumeControlFeatureListenerRecords.broadcast(listener -> {
2737             try {
2738                 listener.onHdmiCecVolumeControlFeature(isEnabled);
2739             } catch (RemoteException e) {
2740                 Slog.e(TAG,
2741                         "Failed to report HdmiControlVolumeControlStatusChange: "
2742                                 + isEnabled);
2743             }
2744         });
2745     }
2746 
tv()2747     public HdmiCecLocalDeviceTv tv() {
2748         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
2749     }
2750 
isTvDevice()2751     boolean isTvDevice() {
2752         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
2753     }
2754 
isAudioSystemDevice()2755     boolean isAudioSystemDevice() {
2756         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2757     }
2758 
isPlaybackDevice()2759     boolean isPlaybackDevice() {
2760         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_PLAYBACK);
2761     }
2762 
isSwitchDevice()2763     boolean isSwitchDevice() {
2764         return SystemProperties.getBoolean(
2765             PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
2766     }
2767 
isTvDeviceEnabled()2768     boolean isTvDeviceEnabled() {
2769         return isTvDevice() && tv() != null;
2770     }
2771 
playback()2772     protected HdmiCecLocalDevicePlayback playback() {
2773         return (HdmiCecLocalDevicePlayback)
2774                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
2775     }
2776 
audioSystem()2777     public HdmiCecLocalDeviceAudioSystem audioSystem() {
2778         return (HdmiCecLocalDeviceAudioSystem) mCecController.getLocalDevice(
2779                 HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
2780     }
2781 
getAudioManager()2782     AudioManager getAudioManager() {
2783         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
2784     }
2785 
isControlEnabled()2786     boolean isControlEnabled() {
2787         synchronized (mLock) {
2788             return mHdmiControlEnabled;
2789         }
2790     }
2791 
2792     @ServiceThreadOnly
getPowerStatus()2793     int getPowerStatus() {
2794         assertRunOnServiceThread();
2795         return mPowerStatus;
2796     }
2797 
2798     @ServiceThreadOnly
2799     @VisibleForTesting
setPowerStatus(int powerStatus)2800     void setPowerStatus(int powerStatus) {
2801         assertRunOnServiceThread();
2802         mPowerStatus = powerStatus;
2803     }
2804 
2805     @ServiceThreadOnly
isPowerOnOrTransient()2806     boolean isPowerOnOrTransient() {
2807         assertRunOnServiceThread();
2808         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2809                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2810     }
2811 
2812     @ServiceThreadOnly
isPowerStandbyOrTransient()2813     boolean isPowerStandbyOrTransient() {
2814         assertRunOnServiceThread();
2815         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2816                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2817     }
2818 
2819     @ServiceThreadOnly
isPowerStandby()2820     boolean isPowerStandby() {
2821         assertRunOnServiceThread();
2822         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
2823     }
2824 
2825     @ServiceThreadOnly
wakeUp()2826     void wakeUp() {
2827         assertRunOnServiceThread();
2828         mWakeUpMessageReceived = true;
2829         mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
2830                 "android.server.hdmi:WAKE");
2831         // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2832         // the intent, the sequence will continue at onWakeUp().
2833     }
2834 
2835     @ServiceThreadOnly
standby()2836     void standby() {
2837         assertRunOnServiceThread();
2838         if (!canGoToStandby()) {
2839             return;
2840         }
2841         mStandbyMessageReceived = true;
2842         mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2843         // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2844         // the intent, the sequence will continue at onStandby().
2845     }
2846 
isWakeUpMessageReceived()2847     boolean isWakeUpMessageReceived() {
2848         return mWakeUpMessageReceived;
2849     }
2850 
2851     @VisibleForTesting
isStandbyMessageReceived()2852     boolean isStandbyMessageReceived() {
2853         return mStandbyMessageReceived;
2854     }
2855 
2856     @ServiceThreadOnly
onWakeUp()2857     private void onWakeUp() {
2858         assertRunOnServiceThread();
2859         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2860         if (mCecController != null) {
2861             if (mHdmiControlEnabled) {
2862                 int startReason = INITIATED_BY_SCREEN_ON;
2863                 if (mWakeUpMessageReceived) {
2864                     startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2865                 }
2866                 initializeCec(startReason);
2867             }
2868         } else {
2869             Slog.i(TAG, "Device does not support HDMI-CEC.");
2870         }
2871         // TODO: Initialize MHL local devices.
2872     }
2873 
2874     @ServiceThreadOnly
2875     @VisibleForTesting
onStandby(final int standbyAction)2876     protected void onStandby(final int standbyAction) {
2877         assertRunOnServiceThread();
2878         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2879         invokeVendorCommandListenersOnControlStateChanged(false,
2880                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
2881 
2882         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2883 
2884         if (!isStandbyMessageReceived() && !canGoToStandby()) {
2885             mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2886             for (HdmiCecLocalDevice device : devices) {
2887                 device.onStandby(mStandbyMessageReceived, standbyAction);
2888             }
2889             return;
2890         }
2891 
2892         disableDevices(new PendingActionClearedCallback() {
2893             @Override
2894             public void onCleared(HdmiCecLocalDevice device) {
2895                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2896                 devices.remove(device);
2897                 if (devices.isEmpty()) {
2898                     onStandbyCompleted(standbyAction);
2899                     // We will not clear local devices here, since some OEM/SOC will keep passing
2900                     // the received packets until the application processor enters to the sleep
2901                     // actually.
2902                 }
2903             }
2904         });
2905     }
2906 
canGoToStandby()2907     private boolean canGoToStandby() {
2908         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2909             if (!device.canGoToStandby()) return false;
2910         }
2911         return true;
2912     }
2913 
2914     @ServiceThreadOnly
onLanguageChanged(String language)2915     private void onLanguageChanged(String language) {
2916         assertRunOnServiceThread();
2917         mMenuLanguage = language;
2918 
2919         if (isTvDeviceEnabled()) {
2920             tv().broadcastMenuLanguage(language);
2921             mCecController.setLanguage(language);
2922         }
2923     }
2924 
2925     /**
2926      * Gets the CEC menu language.
2927      *
2928      * <p>This is the ISO/FDIS 639-2 3 letter language code sent in the CEC message @{code <Set Menu
2929      * Language>}.
2930      * See HDMI 1.4b section CEC 13.6.2
2931      *
2932      * @see {@link Locale#getISO3Language()}
2933      */
2934     @ServiceThreadOnly
getLanguage()2935     String getLanguage() {
2936         assertRunOnServiceThread();
2937         return mMenuLanguage;
2938     }
2939 
disableDevices(PendingActionClearedCallback callback)2940     private void disableDevices(PendingActionClearedCallback callback) {
2941         if (mCecController != null) {
2942             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2943                 device.disableDevice(mStandbyMessageReceived, callback);
2944             }
2945         }
2946         mMhlController.clearAllLocalDevices();
2947     }
2948 
2949     @ServiceThreadOnly
clearLocalDevices()2950     private void clearLocalDevices() {
2951         assertRunOnServiceThread();
2952         if (mCecController == null) {
2953             return;
2954         }
2955         mCecController.clearLogicalAddress();
2956         mCecController.clearLocalDevices();
2957     }
2958 
2959     @ServiceThreadOnly
onStandbyCompleted(int standbyAction)2960     private void onStandbyCompleted(int standbyAction) {
2961         assertRunOnServiceThread();
2962         Slog.v(TAG, "onStandbyCompleted");
2963 
2964         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
2965             return;
2966         }
2967         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2968         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2969             device.onStandby(mStandbyMessageReceived, standbyAction);
2970         }
2971         mStandbyMessageReceived = false;
2972         if (!isAudioSystemDevice()) {
2973             mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2974             mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2975         }
2976     }
2977 
addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType)2978     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2979         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2980         try {
2981             listener.asBinder().linkToDeath(record, 0);
2982         } catch (RemoteException e) {
2983             Slog.w(TAG, "Listener already died");
2984             return;
2985         }
2986         synchronized (mLock) {
2987             mVendorCommandListenerRecords.add(record);
2988         }
2989     }
2990 
invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)2991     boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2992             byte[] params, boolean hasVendorId) {
2993         synchronized (mLock) {
2994             if (mVendorCommandListenerRecords.isEmpty()) {
2995                 return false;
2996             }
2997             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2998                 if (record.mDeviceType != deviceType) {
2999                     continue;
3000                 }
3001                 try {
3002                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
3003                 } catch (RemoteException e) {
3004                     Slog.e(TAG, "Failed to notify vendor command reception", e);
3005                 }
3006             }
3007             return true;
3008         }
3009     }
3010 
invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)3011     boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
3012         synchronized (mLock) {
3013             if (mVendorCommandListenerRecords.isEmpty()) {
3014                 return false;
3015             }
3016             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
3017                 try {
3018                     record.mListener.onControlStateChanged(enabled, reason);
3019                 } catch (RemoteException e) {
3020                     Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
3021                 }
3022             }
3023             return true;
3024         }
3025     }
3026 
addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener)3027     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
3028         HdmiMhlVendorCommandListenerRecord record =
3029                 new HdmiMhlVendorCommandListenerRecord(listener);
3030         try {
3031             listener.asBinder().linkToDeath(record, 0);
3032         } catch (RemoteException e) {
3033             Slog.w(TAG, "Listener already died.");
3034             return;
3035         }
3036 
3037         synchronized (mLock) {
3038             mMhlVendorCommandListenerRecords.add(record);
3039         }
3040     }
3041 
invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)3042     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
3043         synchronized (mLock) {
3044             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
3045                 try {
3046                     record.mListener.onReceived(portId, offest, length, data);
3047                 } catch (RemoteException e) {
3048                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
3049                 }
3050             }
3051         }
3052     }
3053 
setStandbyMode(boolean isStandbyModeOn)3054     void setStandbyMode(boolean isStandbyModeOn) {
3055         assertRunOnServiceThread();
3056         if (isPowerOnOrTransient() && isStandbyModeOn) {
3057             mPowerManager.goToSleep(SystemClock.uptimeMillis(),
3058                     PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
3059             if (playback() != null) {
3060                 playback().sendStandby(0 /* unused */);
3061             }
3062         } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
3063             mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_HDMI,
3064                     "android.server.hdmi:WAKE");
3065             if (playback() != null) {
3066                 oneTouchPlay(new IHdmiControlCallback.Stub() {
3067                     @Override
3068                     public void onComplete(int result) {
3069                         if (result != HdmiControlManager.RESULT_SUCCESS) {
3070                             Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
3071                         }
3072                     }
3073                 });
3074             }
3075         }
3076     }
3077 
setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled)3078     void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {
3079         synchronized (mLock) {
3080             mHdmiCecVolumeControlEnabled = isHdmiCecVolumeControlEnabled;
3081 
3082             boolean storedValue = readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3083                     true);
3084             if (storedValue != isHdmiCecVolumeControlEnabled) {
3085                 HdmiLogger.debug("Changing HDMI CEC volume control feature state: %s",
3086                         isHdmiCecVolumeControlEnabled);
3087                 writeBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED,
3088                         isHdmiCecVolumeControlEnabled);
3089             }
3090         }
3091         announceHdmiCecVolumeControlFeatureChange(isHdmiCecVolumeControlEnabled);
3092     }
3093 
isHdmiCecVolumeControlEnabled()3094     boolean isHdmiCecVolumeControlEnabled() {
3095         synchronized (mLock) {
3096             return mHdmiCecVolumeControlEnabled;
3097         }
3098     }
3099 
isProhibitMode()3100     boolean isProhibitMode() {
3101         synchronized (mLock) {
3102             return mProhibitMode;
3103         }
3104     }
3105 
setProhibitMode(boolean enabled)3106     void setProhibitMode(boolean enabled) {
3107         synchronized (mLock) {
3108             mProhibitMode = enabled;
3109         }
3110     }
3111 
isSystemAudioActivated()3112     boolean isSystemAudioActivated() {
3113         synchronized (mLock) {
3114             return mSystemAudioActivated;
3115         }
3116     }
3117 
setSystemAudioActivated(boolean on)3118     void setSystemAudioActivated(boolean on) {
3119         synchronized (mLock) {
3120             mSystemAudioActivated = on;
3121         }
3122     }
3123 
3124     @ServiceThreadOnly
setCecOption(int key, boolean value)3125     void setCecOption(int key, boolean value) {
3126         assertRunOnServiceThread();
3127         mCecController.setOption(key, value);
3128     }
3129 
3130     @ServiceThreadOnly
setControlEnabled(boolean enabled)3131     void setControlEnabled(boolean enabled) {
3132         assertRunOnServiceThread();
3133 
3134         synchronized (mLock) {
3135             mHdmiControlEnabled = enabled;
3136         }
3137 
3138         if (enabled) {
3139             enableHdmiControlService();
3140             setHdmiCecVolumeControlEnabled(
3141                     readBooleanSetting(Global.HDMI_CONTROL_VOLUME_CONTROL_ENABLED, true));
3142             return;
3143         }
3144 
3145         mHdmiCecVolumeControlEnabled = false;
3146         // Call the vendor handler before the service is disabled.
3147         invokeVendorCommandListenersOnControlStateChanged(false,
3148                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
3149         // Post the remained tasks in the service thread again to give the vendor-issued-tasks
3150         // a chance to run.
3151         runOnServiceThread(new Runnable() {
3152             @Override
3153             public void run() {
3154                 disableHdmiControlService();
3155             }
3156         });
3157         announceHdmiControlStatusChange(enabled);
3158 
3159         return;
3160     }
3161 
3162     @ServiceThreadOnly
enableHdmiControlService()3163     private void enableHdmiControlService() {
3164         mCecController.setOption(OptionKey.ENABLE_CEC, true);
3165         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
3166         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
3167 
3168         initializeCec(INITIATED_BY_ENABLE_CEC);
3169     }
3170 
3171     @ServiceThreadOnly
disableHdmiControlService()3172     private void disableHdmiControlService() {
3173         disableDevices(new PendingActionClearedCallback() {
3174             @Override
3175             public void onCleared(HdmiCecLocalDevice device) {
3176                 assertRunOnServiceThread();
3177                 mCecController.flush(new Runnable() {
3178                     @Override
3179                     public void run() {
3180                         mCecController.setOption(OptionKey.ENABLE_CEC, false);
3181                         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
3182                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
3183                         clearLocalDevices();
3184                     }
3185                 });
3186             }
3187         });
3188     }
3189 
3190     @ServiceThreadOnly
setActivePortId(int portId)3191     void setActivePortId(int portId) {
3192         assertRunOnServiceThread();
3193         mActivePortId = portId;
3194 
3195         // Resets last input for MHL, which stays valid only after the MHL device was selected,
3196         // and no further switching is done.
3197         setLastInputForMhl(Constants.INVALID_PORT_ID);
3198     }
3199 
getLocalActiveSource()3200     ActiveSource getLocalActiveSource() {
3201         synchronized (mLock) {
3202             return mActiveSource;
3203         }
3204     }
3205 
setActiveSource(int logicalAddress, int physicalAddress)3206     void setActiveSource(int logicalAddress, int physicalAddress) {
3207         synchronized (mLock) {
3208             mActiveSource.logicalAddress = logicalAddress;
3209             mActiveSource.physicalAddress = physicalAddress;
3210         }
3211         // If the current device is a source device, check if the current Active Source matches
3212         // the local device info. Set mIsActiveSource of the local device accordingly.
3213         for (HdmiCecLocalDevice device : getAllLocalDevices()) {
3214             // mIsActiveSource only exists in source device, ignore this setting if the current
3215             // device is not an HdmiCecLocalDeviceSource.
3216             if (!(device instanceof HdmiCecLocalDeviceSource)) {
3217                 continue;
3218             }
3219             if (logicalAddress == device.getDeviceInfo().getLogicalAddress()
3220                 && physicalAddress == getPhysicalAddress()) {
3221                 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(true);
3222             } else {
3223                 ((HdmiCecLocalDeviceSource) device).setIsActiveSource(false);
3224             }
3225         }
3226     }
3227 
3228     // This method should only be called when the device can be the active source
3229     // and all the device types call into this method.
3230     // For example, when receiving broadcast messages, all the device types will call this
3231     // method but only one of them will be the Active Source.
setAndBroadcastActiveSource( int physicalAddress, int deviceType, int source)3232     protected void setAndBroadcastActiveSource(
3233             int physicalAddress, int deviceType, int source) {
3234         // If the device has both playback and audio system logical addresses,
3235         // playback will claim active source. Otherwise audio system will.
3236         if (deviceType == HdmiDeviceInfo.DEVICE_PLAYBACK) {
3237             HdmiCecLocalDevicePlayback playback = playback();
3238             playback.setIsActiveSource(true);
3239             playback.wakeUpIfActiveSource();
3240             playback.maySendActiveSource(source);
3241             setActiveSource(playback.mAddress, physicalAddress);
3242         }
3243 
3244         if (deviceType == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) {
3245             HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3246             if (playback() != null) {
3247                 audioSystem.setIsActiveSource(false);
3248             } else {
3249                 audioSystem.setIsActiveSource(true);
3250                 audioSystem.wakeUpIfActiveSource();
3251                 audioSystem.maySendActiveSource(source);
3252                 setActiveSource(audioSystem.mAddress, physicalAddress);
3253             }
3254         }
3255     }
3256 
3257     // This method should only be called when the device can be the active source
3258     // and only one of the device types calls into this method.
3259     // For example, when receiving One Touch Play, only playback device handles it
3260     // and this method updates Active Source in all the device types sharing the same
3261     // Physical Address.
setAndBroadcastActiveSourceFromOneDeviceType( int sourceAddress, int physicalAddress)3262     protected void setAndBroadcastActiveSourceFromOneDeviceType(
3263             int sourceAddress, int physicalAddress) {
3264         // If the device has both playback and audio system logical addresses,
3265         // playback will claim active source. Otherwise audio system will.
3266         HdmiCecLocalDevicePlayback playback = playback();
3267         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
3268         if (playback != null) {
3269             playback.setIsActiveSource(true);
3270             playback.wakeUpIfActiveSource();
3271             playback.maySendActiveSource(sourceAddress);
3272             if (audioSystem != null) {
3273                 audioSystem.setIsActiveSource(false);
3274             }
3275             setActiveSource(playback.mAddress, physicalAddress);
3276         } else {
3277             if (audioSystem != null) {
3278                 audioSystem.setIsActiveSource(true);
3279                 audioSystem.wakeUpIfActiveSource();
3280                 audioSystem.maySendActiveSource(sourceAddress);
3281                 setActiveSource(audioSystem.mAddress, physicalAddress);
3282             }
3283         }
3284     }
3285 
3286     @ServiceThreadOnly
setLastInputForMhl(int portId)3287     void setLastInputForMhl(int portId) {
3288         assertRunOnServiceThread();
3289         mLastInputMhl = portId;
3290     }
3291 
3292     @ServiceThreadOnly
getLastInputForMhl()3293     int getLastInputForMhl() {
3294         assertRunOnServiceThread();
3295         return mLastInputMhl;
3296     }
3297 
3298     /**
3299      * Performs input change, routing control for MHL device.
3300      *
3301      * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
3302      * @param contentOn {@code true} if RAP data is content on; otherwise false
3303      */
3304     @ServiceThreadOnly
changeInputForMhl(int portId, boolean contentOn)3305     void changeInputForMhl(int portId, boolean contentOn) {
3306         assertRunOnServiceThread();
3307         if (tv() == null) return;
3308         final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
3309         if (portId != Constants.INVALID_PORT_ID) {
3310             tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
3311                 @Override
3312                 public void onComplete(int result) throws RemoteException {
3313                     // Keep the last input to switch back later when RAP[ContentOff] is received.
3314                     // This effectively sets the port to invalid one if the switching is for
3315                     // RAP[ContentOff].
3316                     setLastInputForMhl(lastInput);
3317                 }
3318             });
3319         }
3320         // MHL device is always directly connected to the port. Update the active port ID to avoid
3321         // unnecessary post-routing control task.
3322         tv().setActivePortId(portId);
3323 
3324         // The port is either the MHL-enabled port where the mobile device is connected, or
3325         // the last port to go back to when turnoff command is received. Note that the last port
3326         // may not be the MHL-enabled one. In this case the device info to be passed to
3327         // input change listener should be the one describing the corresponding HDMI port.
3328         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
3329         HdmiDeviceInfo info = (device != null) ? device.getInfo()
3330                 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
3331         invokeInputChangeListener(info);
3332     }
3333 
setMhlInputChangeEnabled(boolean enabled)3334    void setMhlInputChangeEnabled(boolean enabled) {
3335        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
3336 
3337         synchronized (mLock) {
3338             mMhlInputChangeEnabled = enabled;
3339         }
3340     }
3341 
isMhlInputChangeEnabled()3342     boolean isMhlInputChangeEnabled() {
3343         synchronized (mLock) {
3344             return mMhlInputChangeEnabled;
3345         }
3346     }
3347 
3348     @ServiceThreadOnly
displayOsd(int messageId)3349     void displayOsd(int messageId) {
3350         assertRunOnServiceThread();
3351         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3352         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
3353         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3354                 HdmiControlService.PERMISSION);
3355     }
3356 
3357     @ServiceThreadOnly
displayOsd(int messageId, int extra)3358     void displayOsd(int messageId, int extra) {
3359         assertRunOnServiceThread();
3360         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
3361         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
3362         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
3363         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
3364                 HdmiControlService.PERMISSION);
3365     }
3366 }
3367