1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.hdmi;
18 
19 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
20 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
21 import static com.android.server.hdmi.Constants.DISABLED;
22 import static com.android.server.hdmi.Constants.ENABLED;
23 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
24 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
25 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
26 import static com.android.server.hdmi.Constants.OPTION_MHL_SERVICE_CONTROL;
27 
28 import android.annotation.Nullable;
29 import android.content.BroadcastReceiver;
30 import android.content.ContentResolver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.database.ContentObserver;
35 import android.hardware.hdmi.HdmiControlManager;
36 import android.hardware.hdmi.HdmiDeviceInfo;
37 import android.hardware.hdmi.HdmiHotplugEvent;
38 import android.hardware.hdmi.HdmiPortInfo;
39 import android.hardware.hdmi.IHdmiControlCallback;
40 import android.hardware.hdmi.IHdmiControlService;
41 import android.hardware.hdmi.IHdmiDeviceEventListener;
42 import android.hardware.hdmi.IHdmiHotplugEventListener;
43 import android.hardware.hdmi.IHdmiInputChangeListener;
44 import android.hardware.hdmi.IHdmiMhlVendorCommandListener;
45 import android.hardware.hdmi.IHdmiRecordListener;
46 import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
47 import android.hardware.hdmi.IHdmiVendorCommandListener;
48 import android.hardware.tv.cec.V1_0.OptionKey;
49 import android.hardware.tv.cec.V1_0.SendMessageResult;
50 import android.media.AudioManager;
51 import android.media.tv.TvInputManager;
52 import android.media.tv.TvInputManager.TvInputCallback;
53 import android.net.Uri;
54 import android.os.Build;
55 import android.os.Handler;
56 import android.os.HandlerThread;
57 import android.os.IBinder;
58 import android.os.Looper;
59 import android.os.PowerManager;
60 import android.os.RemoteException;
61 import android.os.SystemClock;
62 import android.os.SystemProperties;
63 import android.os.UserHandle;
64 import android.provider.Settings.Global;
65 import android.text.TextUtils;
66 import android.util.ArraySet;
67 import android.util.Slog;
68 import android.util.SparseArray;
69 import android.util.SparseIntArray;
70 import com.android.internal.annotations.GuardedBy;
71 import com.android.internal.util.DumpUtils;
72 import com.android.internal.util.IndentingPrintWriter;
73 import com.android.server.SystemService;
74 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
75 import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
76 import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
77 import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
78 import java.io.FileDescriptor;
79 import java.io.PrintWriter;
80 import java.util.ArrayList;
81 import java.util.Arrays;
82 import java.util.Collections;
83 import java.util.List;
84 import java.util.Locale;
85 import libcore.util.EmptyArray;
86 
87 /**
88  * Provides a service for sending and processing HDMI control messages,
89  * HDMI-CEC and MHL control command, and providing the information on both standard.
90  */
91 public final class HdmiControlService extends SystemService {
92     private static final String TAG = "HdmiControlService";
93     private final Locale HONG_KONG = new Locale("zh", "HK");
94     private final Locale MACAU = new Locale("zh", "MO");
95 
96     static final String PERMISSION = "android.permission.HDMI_CEC";
97 
98     // The reason code to initiate intializeCec().
99     static final int INITIATED_BY_ENABLE_CEC = 0;
100     static final int INITIATED_BY_BOOT_UP = 1;
101     static final int INITIATED_BY_SCREEN_ON = 2;
102     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
103     static final int INITIATED_BY_HOTPLUG = 4;
104 
105     // The reason code representing the intent action that drives the standby
106     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
107     // Intent.ACTION_SHUTDOWN.
108     static final int STANDBY_SCREEN_OFF = 0;
109     static final int STANDBY_SHUTDOWN = 1;
110 
111     /**
112      * Interface to report send result.
113      */
114     interface SendMessageCallback {
115         /**
116          * Called when {@link HdmiControlService#sendCecCommand} is completed.
117          *
118          * @param error result of send request.
119          * <ul>
120          * <li>{@link SendMessageResult#SUCCESS}
121          * <li>{@link SendMessageResult#NACK}
122          * <li>{@link SendMessageResult#BUSY}
123          * <li>{@link SendMessageResult#FAIL}
124          * </ul>
125          */
onSendCompleted(int error)126         void onSendCompleted(int error);
127     }
128 
129     /**
130      * Interface to get a list of available logical devices.
131      */
132     interface DevicePollingCallback {
133         /**
134          * Called when device polling is finished.
135          *
136          * @param ackedAddress a list of logical addresses of available devices
137          */
onPollingFinished(List<Integer> ackedAddress)138         void onPollingFinished(List<Integer> ackedAddress);
139     }
140 
141     private class HdmiControlBroadcastReceiver extends BroadcastReceiver {
142         @ServiceThreadOnly
143         @Override
onReceive(Context context, Intent intent)144         public void onReceive(Context context, Intent intent) {
145             assertRunOnServiceThread();
146             switch (intent.getAction()) {
147                 case Intent.ACTION_SCREEN_OFF:
148                     if (isPowerOnOrTransient()) {
149                         onStandby(STANDBY_SCREEN_OFF);
150                     }
151                     break;
152                 case Intent.ACTION_SCREEN_ON:
153                     if (isPowerStandbyOrTransient()) {
154                         onWakeUp();
155                     }
156                     break;
157                 case Intent.ACTION_CONFIGURATION_CHANGED:
158                     String language = getMenuLanguage();
159                     if (!mLanguage.equals(language)) {
160                         onLanguageChanged(language);
161                     }
162                     break;
163                 case Intent.ACTION_SHUTDOWN:
164                     if (isPowerOnOrTransient()) {
165                         onStandby(STANDBY_SHUTDOWN);
166                     }
167                     break;
168             }
169         }
170 
getMenuLanguage()171         private String getMenuLanguage() {
172             Locale locale = Locale.getDefault();
173             if (locale.equals(Locale.TAIWAN) || locale.equals(HONG_KONG) || locale.equals(MACAU)) {
174                 // Android always returns "zho" for all Chinese variants.
175                 // Use "bibliographic" code defined in CEC639-2 for traditional
176                 // Chinese used in Taiwan/Hong Kong/Macau.
177                 return "chi";
178             } else {
179                 return locale.getISO3Language();
180             }
181         }
182     }
183 
184     // A thread to handle synchronous IO of CEC and MHL control service.
185     // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
186     // and sparse call it shares a thread to handle IO operations.
187     private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
188 
189     // Used to synchronize the access to the service.
190     private final Object mLock = new Object();
191 
192     // Type of logical devices hosted in the system. Stored in the unmodifiable list.
193     private final List<Integer> mLocalDevices;
194 
195     // List of records for hotplug event listener to handle the the caller killed in action.
196     @GuardedBy("mLock")
197     private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
198             new ArrayList<>();
199 
200     // List of records for device event listener to handle the caller killed in action.
201     @GuardedBy("mLock")
202     private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
203             new ArrayList<>();
204 
205     // List of records for vendor command listener to handle the caller killed in action.
206     @GuardedBy("mLock")
207     private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
208             new ArrayList<>();
209 
210     @GuardedBy("mLock")
211     private InputChangeListenerRecord mInputChangeListenerRecord;
212 
213     @GuardedBy("mLock")
214     private HdmiRecordListenerRecord mRecordListenerRecord;
215 
216     // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
217     // handling will be disabled and no request will be handled.
218     @GuardedBy("mLock")
219     private boolean mHdmiControlEnabled;
220 
221     // Set to true while the service is in normal mode. While set to false, no input change is
222     // allowed. Used for situations where input change can confuse users such as channel auto-scan,
223     // system upgrade, etc., a.k.a. "prohibit mode".
224     @GuardedBy("mLock")
225     private boolean mProhibitMode;
226 
227     // List of records for system audio mode change to handle the the caller killed in action.
228     private final ArrayList<SystemAudioModeChangeListenerRecord>
229             mSystemAudioModeChangeListenerRecords = new ArrayList<>();
230 
231     // Handler used to run a task in service thread.
232     private final Handler mHandler = new Handler();
233 
234     private final SettingsObserver mSettingsObserver;
235 
236     private final HdmiControlBroadcastReceiver
237             mHdmiControlBroadcastReceiver = new HdmiControlBroadcastReceiver();
238 
239     @Nullable
240     private HdmiCecController mCecController;
241 
242     // HDMI port information. Stored in the unmodifiable list to keep the static information
243     // from being modified.
244     private List<HdmiPortInfo> mPortInfo;
245 
246     // Map from path(physical address) to port ID.
247     private UnmodifiableSparseIntArray mPortIdMap;
248 
249     // Map from port ID to HdmiPortInfo.
250     private UnmodifiableSparseArray<HdmiPortInfo> mPortInfoMap;
251 
252     // Map from port ID to HdmiDeviceInfo.
253     private UnmodifiableSparseArray<HdmiDeviceInfo> mPortDeviceMap;
254 
255     private HdmiCecMessageValidator mMessageValidator;
256 
257     @ServiceThreadOnly
258     private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
259 
260     @ServiceThreadOnly
261     private String mLanguage = Locale.getDefault().getISO3Language();
262 
263     @ServiceThreadOnly
264     private boolean mStandbyMessageReceived = false;
265 
266     @ServiceThreadOnly
267     private boolean mWakeUpMessageReceived = false;
268 
269     @ServiceThreadOnly
270     private int mActivePortId = Constants.INVALID_PORT_ID;
271 
272     // Set to true while the input change by MHL is allowed.
273     @GuardedBy("mLock")
274     private boolean mMhlInputChangeEnabled;
275 
276     // List of records for MHL Vendor command listener to handle the caller killed in action.
277     @GuardedBy("mLock")
278     private final ArrayList<HdmiMhlVendorCommandListenerRecord>
279             mMhlVendorCommandListenerRecords = new ArrayList<>();
280 
281     @GuardedBy("mLock")
282     private List<HdmiDeviceInfo> mMhlDevices;
283 
284     @Nullable
285     private HdmiMhlControllerStub mMhlController;
286 
287     @Nullable
288     private TvInputManager mTvInputManager;
289 
290     @Nullable
291     private PowerManager mPowerManager;
292 
293     // Last input port before switching to the MHL port. Should switch back to this port
294     // when the mobile device sends the request one touch play with off.
295     // Gets invalidated if we go to other port/input.
296     @ServiceThreadOnly
297     private int mLastInputMhl = Constants.INVALID_PORT_ID;
298 
299     // Set to true if the logical address allocation is completed.
300     private boolean mAddressAllocated = false;
301 
302     // Buffer for processing the incoming cec messages while allocating logical addresses.
303     private final class CecMessageBuffer {
304         private List<HdmiCecMessage> mBuffer = new ArrayList<>();
305 
bufferMessage(HdmiCecMessage message)306         public void bufferMessage(HdmiCecMessage message) {
307             switch (message.getOpcode()) {
308                 case Constants.MESSAGE_ACTIVE_SOURCE:
309                     bufferActiveSource(message);
310                     break;
311                 case Constants.MESSAGE_IMAGE_VIEW_ON:
312                 case Constants.MESSAGE_TEXT_VIEW_ON:
313                     bufferImageOrTextViewOn(message);
314                     break;
315                     // Add here if new message that needs to buffer
316                 default:
317                     // Do not need to buffer messages other than above
318                     break;
319             }
320         }
321 
processMessages()322         public void processMessages() {
323             for (final HdmiCecMessage message : mBuffer) {
324                 runOnServiceThread(new Runnable() {
325                     @Override
326                     public void run() {
327                         handleCecCommand(message);
328                     }
329                 });
330             }
331             mBuffer.clear();
332         }
333 
bufferActiveSource(HdmiCecMessage message)334         private void bufferActiveSource(HdmiCecMessage message) {
335             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_ACTIVE_SOURCE)) {
336                 mBuffer.add(message);
337             }
338         }
339 
bufferImageOrTextViewOn(HdmiCecMessage message)340         private void bufferImageOrTextViewOn(HdmiCecMessage message) {
341             if (!replaceMessageIfBuffered(message, Constants.MESSAGE_IMAGE_VIEW_ON) &&
342                 !replaceMessageIfBuffered(message, Constants.MESSAGE_TEXT_VIEW_ON)) {
343                 mBuffer.add(message);
344             }
345         }
346 
347         // Returns true if the message is replaced
replaceMessageIfBuffered(HdmiCecMessage message, int opcode)348         private boolean replaceMessageIfBuffered(HdmiCecMessage message, int opcode) {
349             for (int i = 0; i < mBuffer.size(); i++) {
350                 HdmiCecMessage bufferedMessage = mBuffer.get(i);
351                 if (bufferedMessage.getOpcode() == opcode) {
352                     mBuffer.set(i, message);
353                     return true;
354                 }
355             }
356             return false;
357         }
358     }
359 
360     private final CecMessageBuffer mCecMessageBuffer = new CecMessageBuffer();
361 
362     private final SelectRequestBuffer mSelectRequestBuffer = new SelectRequestBuffer();
363 
HdmiControlService(Context context)364     public HdmiControlService(Context context) {
365         super(context);
366         mLocalDevices = getIntList(SystemProperties.get(Constants.PROPERTY_DEVICE_TYPE));
367         mSettingsObserver = new SettingsObserver(mHandler);
368     }
369 
getIntList(String string)370     private static List<Integer> getIntList(String string) {
371         ArrayList<Integer> list = new ArrayList<>();
372         TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
373         splitter.setString(string);
374         for (String item : splitter) {
375             try {
376                 list.add(Integer.parseInt(item));
377             } catch (NumberFormatException e) {
378                 Slog.w(TAG, "Can't parseInt: " + item);
379             }
380         }
381         return Collections.unmodifiableList(list);
382     }
383 
384     @Override
onStart()385     public void onStart() {
386         mIoThread.start();
387         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
388         mProhibitMode = false;
389         mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
390         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
391 
392         mCecController = HdmiCecController.create(this);
393         if (mCecController != null) {
394             if (mHdmiControlEnabled) {
395                 initializeCec(INITIATED_BY_BOOT_UP);
396             }
397         } else {
398             Slog.i(TAG, "Device does not support HDMI-CEC.");
399             return;
400         }
401 
402         mMhlController = HdmiMhlControllerStub.create(this);
403         if (!mMhlController.isReady()) {
404             Slog.i(TAG, "Device does not support MHL-control.");
405         }
406         mMhlDevices = Collections.emptyList();
407 
408         initPortInfo();
409         mMessageValidator = new HdmiCecMessageValidator(this);
410         publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
411 
412         if (mCecController != null) {
413             // Register broadcast receiver for power state change.
414             IntentFilter filter = new IntentFilter();
415             filter.addAction(Intent.ACTION_SCREEN_OFF);
416             filter.addAction(Intent.ACTION_SCREEN_ON);
417             filter.addAction(Intent.ACTION_SHUTDOWN);
418             filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
419             getContext().registerReceiver(mHdmiControlBroadcastReceiver, filter);
420 
421             // Register ContentObserver to monitor the settings change.
422             registerContentObserver();
423         }
424         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, ENABLED);
425     }
426 
427     @Override
onBootPhase(int phase)428     public void onBootPhase(int phase) {
429         if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
430             mTvInputManager = (TvInputManager) getContext().getSystemService(
431                     Context.TV_INPUT_SERVICE);
432             mPowerManager = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
433         }
434     }
435 
getTvInputManager()436     TvInputManager getTvInputManager() {
437         return mTvInputManager;
438     }
439 
registerTvInputCallback(TvInputCallback callback)440     void registerTvInputCallback(TvInputCallback callback) {
441         if (mTvInputManager == null) return;
442         mTvInputManager.registerCallback(callback, mHandler);
443     }
444 
unregisterTvInputCallback(TvInputCallback callback)445     void unregisterTvInputCallback(TvInputCallback callback) {
446         if (mTvInputManager == null) return;
447         mTvInputManager.unregisterCallback(callback);
448     }
449 
getPowerManager()450     PowerManager getPowerManager() {
451         return mPowerManager;
452     }
453 
454     /**
455      * Called when the initialization of local devices is complete.
456      */
onInitializeCecComplete(int initiatedBy)457     private void onInitializeCecComplete(int initiatedBy) {
458         if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
459             mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
460         }
461         mWakeUpMessageReceived = false;
462 
463         if (isTvDeviceEnabled()) {
464             mCecController.setOption(OptionKey.WAKEUP, tv().getAutoWakeup());
465         }
466         int reason = -1;
467         switch (initiatedBy) {
468             case INITIATED_BY_BOOT_UP:
469                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_START;
470                 break;
471             case INITIATED_BY_ENABLE_CEC:
472                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING;
473                 break;
474             case INITIATED_BY_SCREEN_ON:
475             case INITIATED_BY_WAKE_UP_MESSAGE:
476                 reason = HdmiControlManager.CONTROL_STATE_CHANGED_REASON_WAKEUP;
477                 break;
478         }
479         if (reason != -1) {
480             invokeVendorCommandListenersOnControlStateChanged(true, reason);
481         }
482     }
483 
registerContentObserver()484     private void registerContentObserver() {
485         ContentResolver resolver = getContext().getContentResolver();
486         String[] settings = new String[] {
487                 Global.HDMI_CONTROL_ENABLED,
488                 Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED,
489                 Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED,
490                 Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
491                 Global.MHL_INPUT_SWITCHING_ENABLED,
492                 Global.MHL_POWER_CHARGE_ENABLED
493         };
494         for (String s : settings) {
495             resolver.registerContentObserver(Global.getUriFor(s), false, mSettingsObserver,
496                     UserHandle.USER_ALL);
497         }
498     }
499 
500     private class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)501         public SettingsObserver(Handler handler) {
502             super(handler);
503         }
504 
505         // onChange is set up to run in service thread.
506         @Override
onChange(boolean selfChange, Uri uri)507         public void onChange(boolean selfChange, Uri uri) {
508             String option = uri.getLastPathSegment();
509             boolean enabled = readBooleanSetting(option, true);
510             switch (option) {
511                 case Global.HDMI_CONTROL_ENABLED:
512                     setControlEnabled(enabled);
513                     break;
514                 case Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED:
515                     if (isTvDeviceEnabled()) {
516                         tv().setAutoWakeup(enabled);
517                     }
518                     setCecOption(OptionKey.WAKEUP, enabled);
519                     break;
520                 case Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED:
521                     for (int type : mLocalDevices) {
522                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
523                         if (localDevice != null) {
524                             localDevice.setAutoDeviceOff(enabled);
525                         }
526                     }
527                     // No need to propagate to HAL.
528                     break;
529                 case Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED:
530                     if (isTvDeviceEnabled()) {
531                         tv().setSystemAudioControlFeatureEnabled(enabled);
532                     }
533                     break;
534                 case Global.MHL_INPUT_SWITCHING_ENABLED:
535                     setMhlInputChangeEnabled(enabled);
536                     break;
537                 case Global.MHL_POWER_CHARGE_ENABLED:
538                     mMhlController.setOption(OPTION_MHL_POWER_CHARGE, toInt(enabled));
539                     break;
540             }
541         }
542     }
543 
toInt(boolean enabled)544     private static int toInt(boolean enabled) {
545         return enabled ? ENABLED : DISABLED;
546     }
547 
readBooleanSetting(String key, boolean defVal)548     boolean readBooleanSetting(String key, boolean defVal) {
549         ContentResolver cr = getContext().getContentResolver();
550         return Global.getInt(cr, key, toInt(defVal)) == ENABLED;
551     }
552 
writeBooleanSetting(String key, boolean value)553     void writeBooleanSetting(String key, boolean value) {
554         ContentResolver cr = getContext().getContentResolver();
555         Global.putInt(cr, key, toInt(value));
556     }
557 
initializeCec(int initiatedBy)558     private void initializeCec(int initiatedBy) {
559         mAddressAllocated = false;
560         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
561         mCecController.setLanguage(mLanguage);
562         initializeLocalDevices(initiatedBy);
563     }
564 
565     @ServiceThreadOnly
initializeLocalDevices(final int initiatedBy)566     private void initializeLocalDevices(final int initiatedBy) {
567         assertRunOnServiceThread();
568         // A container for [Device type, Local device info].
569         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
570         for (int type : mLocalDevices) {
571             HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
572             if (localDevice == null) {
573                 localDevice = HdmiCecLocalDevice.create(this, type);
574             }
575             localDevice.init();
576             localDevices.add(localDevice);
577         }
578         // It's now safe to flush existing local devices from mCecController since they were
579         // already moved to 'localDevices'.
580         clearLocalDevices();
581         allocateLogicalAddress(localDevices, initiatedBy);
582     }
583 
584     @ServiceThreadOnly
allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices, final int initiatedBy)585     private void allocateLogicalAddress(final ArrayList<HdmiCecLocalDevice> allocatingDevices,
586             final int initiatedBy) {
587         assertRunOnServiceThread();
588         mCecController.clearLogicalAddress();
589         final ArrayList<HdmiCecLocalDevice> allocatedDevices = new ArrayList<>();
590         final int[] finished = new int[1];
591         mAddressAllocated = allocatingDevices.isEmpty();
592 
593         // For TV device, select request can be invoked while address allocation or device
594         // discovery is in progress. Initialize the request here at the start of allocation,
595         // and process the collected requests later when the allocation and device discovery
596         // is all completed.
597         mSelectRequestBuffer.clear();
598 
599         for (final HdmiCecLocalDevice localDevice : allocatingDevices) {
600             mCecController.allocateLogicalAddress(localDevice.getType(),
601                     localDevice.getPreferredAddress(), new AllocateAddressCallback() {
602                 @Override
603                 public void onAllocated(int deviceType, int logicalAddress) {
604                     if (logicalAddress == Constants.ADDR_UNREGISTERED) {
605                         Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
606                     } else {
607                         // Set POWER_STATUS_ON to all local devices because they share lifetime
608                         // with system.
609                         HdmiDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType,
610                                 HdmiControlManager.POWER_STATUS_ON);
611                         localDevice.setDeviceInfo(deviceInfo);
612                         mCecController.addLocalDevice(deviceType, localDevice);
613                         mCecController.addLogicalAddress(logicalAddress);
614                         allocatedDevices.add(localDevice);
615                     }
616 
617                     // Address allocation completed for all devices. Notify each device.
618                     if (allocatingDevices.size() == ++finished[0]) {
619                         mAddressAllocated = true;
620                         if (initiatedBy != INITIATED_BY_HOTPLUG) {
621                             // In case of the hotplug we don't call onInitializeCecComplete()
622                             // since we reallocate the logical address only.
623                             onInitializeCecComplete(initiatedBy);
624                         }
625                         notifyAddressAllocated(allocatedDevices, initiatedBy);
626                         mCecMessageBuffer.processMessages();
627                     }
628                 }
629             });
630         }
631     }
632 
633     @ServiceThreadOnly
notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy)634     private void notifyAddressAllocated(ArrayList<HdmiCecLocalDevice> devices, int initiatedBy) {
635         assertRunOnServiceThread();
636         for (HdmiCecLocalDevice device : devices) {
637             int address = device.getDeviceInfo().getLogicalAddress();
638             device.handleAddressAllocated(address, initiatedBy);
639         }
640         if (isTvDeviceEnabled()) {
641             tv().setSelectRequestBuffer(mSelectRequestBuffer);
642         }
643     }
644 
isAddressAllocated()645     boolean isAddressAllocated() {
646         return mAddressAllocated;
647     }
648 
649     // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
650     // keep them in one place.
651     @ServiceThreadOnly
initPortInfo()652     private void initPortInfo() {
653         assertRunOnServiceThread();
654         HdmiPortInfo[] cecPortInfo = null;
655 
656         // CEC HAL provides majority of the info while MHL does only MHL support flag for
657         // each port. Return empty array if CEC HAL didn't provide the info.
658         if (mCecController != null) {
659             cecPortInfo = mCecController.getPortInfos();
660         }
661         if (cecPortInfo == null) {
662             return;
663         }
664 
665         SparseArray<HdmiPortInfo> portInfoMap = new SparseArray<>();
666         SparseIntArray portIdMap = new SparseIntArray();
667         SparseArray<HdmiDeviceInfo> portDeviceMap = new SparseArray<>();
668         for (HdmiPortInfo info : cecPortInfo) {
669             portIdMap.put(info.getAddress(), info.getId());
670             portInfoMap.put(info.getId(), info);
671             portDeviceMap.put(info.getId(), new HdmiDeviceInfo(info.getAddress(), info.getId()));
672         }
673         mPortIdMap = new UnmodifiableSparseIntArray(portIdMap);
674         mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
675         mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
676 
677         HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
678         ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
679         for (HdmiPortInfo info : mhlPortInfo) {
680             if (info.isMhlSupported()) {
681                 mhlSupportedPorts.add(info.getId());
682             }
683         }
684 
685         // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use
686         // cec port info if we do not have have port that supports MHL.
687         if (mhlSupportedPorts.isEmpty()) {
688             mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
689             return;
690         }
691         ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
692         for (HdmiPortInfo info : cecPortInfo) {
693             if (mhlSupportedPorts.contains(info.getId())) {
694                 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
695                         info.isCecSupported(), true, info.isArcSupported()));
696             } else {
697                 result.add(info);
698             }
699         }
700         mPortInfo = Collections.unmodifiableList(result);
701     }
702 
getPortInfo()703     List<HdmiPortInfo> getPortInfo() {
704         return mPortInfo;
705     }
706 
707     /**
708      * Returns HDMI port information for the given port id.
709      *
710      * @param portId HDMI port id
711      * @return {@link HdmiPortInfo} for the given port
712      */
getPortInfo(int portId)713     HdmiPortInfo getPortInfo(int portId) {
714         return mPortInfoMap.get(portId, null);
715     }
716 
717     /**
718      * Returns the routing path (physical address) of the HDMI port for the given
719      * port id.
720      */
portIdToPath(int portId)721     int portIdToPath(int portId) {
722         HdmiPortInfo portInfo = getPortInfo(portId);
723         if (portInfo == null) {
724             Slog.e(TAG, "Cannot find the port info: " + portId);
725             return Constants.INVALID_PHYSICAL_ADDRESS;
726         }
727         return portInfo.getAddress();
728     }
729 
730     /**
731      * Returns the id of HDMI port located at the top of the hierarchy of
732      * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
733      * the port id to be returned is the ID associated with the port address
734      * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
735      */
pathToPortId(int path)736     int pathToPortId(int path) {
737         int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
738         return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
739     }
740 
isValidPortId(int portId)741     boolean isValidPortId(int portId) {
742         return getPortInfo(portId) != null;
743     }
744 
745     /**
746      * Returns {@link Looper} for IO operation.
747      *
748      * <p>Declared as package-private.
749      */
getIoLooper()750     Looper getIoLooper() {
751         return mIoThread.getLooper();
752     }
753 
754     /**
755      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
756      * for tasks that are running on main service thread.
757      *
758      * <p>Declared as package-private.
759      */
getServiceLooper()760     Looper getServiceLooper() {
761         return mHandler.getLooper();
762     }
763 
764     /**
765      * Returns physical address of the device.
766      */
getPhysicalAddress()767     int getPhysicalAddress() {
768         return mCecController.getPhysicalAddress();
769     }
770 
771     /**
772      * Returns vendor id of CEC service.
773      */
getVendorId()774     int getVendorId() {
775         return mCecController.getVendorId();
776     }
777 
778     @ServiceThreadOnly
getDeviceInfo(int logicalAddress)779     HdmiDeviceInfo getDeviceInfo(int logicalAddress) {
780         assertRunOnServiceThread();
781         return tv() == null ? null : tv().getCecDeviceInfo(logicalAddress);
782     }
783 
784     @ServiceThreadOnly
getDeviceInfoByPort(int port)785     HdmiDeviceInfo getDeviceInfoByPort(int port) {
786         assertRunOnServiceThread();
787         HdmiMhlLocalDeviceStub info = mMhlController.getLocalDevice(port);
788         if (info != null) {
789             return info.getInfo();
790         }
791         return null;
792     }
793 
794     /**
795      * Returns version of CEC.
796      */
getCecVersion()797     int getCecVersion() {
798         return mCecController.getVersion();
799     }
800 
801     /**
802      * Whether a device of the specified physical address is connected to ARC enabled port.
803      */
isConnectedToArcPort(int physicalAddress)804     boolean isConnectedToArcPort(int physicalAddress) {
805         int portId = pathToPortId(physicalAddress);
806         if (portId != Constants.INVALID_PORT_ID) {
807             return mPortInfoMap.get(portId).isArcSupported();
808         }
809         return false;
810     }
811 
812     @ServiceThreadOnly
isConnected(int portId)813     boolean isConnected(int portId) {
814         assertRunOnServiceThread();
815         return mCecController.isConnected(portId);
816     }
817 
runOnServiceThread(Runnable runnable)818     void runOnServiceThread(Runnable runnable) {
819         mHandler.post(runnable);
820     }
821 
runOnServiceThreadAtFrontOfQueue(Runnable runnable)822     void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
823         mHandler.postAtFrontOfQueue(runnable);
824     }
825 
assertRunOnServiceThread()826     private void assertRunOnServiceThread() {
827         if (Looper.myLooper() != mHandler.getLooper()) {
828             throw new IllegalStateException("Should run on service thread.");
829         }
830     }
831 
832     /**
833      * Transmit a CEC command to CEC bus.
834      *
835      * @param command CEC command to send out
836      * @param callback interface used to the result of send command
837      */
838     @ServiceThreadOnly
sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback)839     void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
840         assertRunOnServiceThread();
841         if (mMessageValidator.isValid(command) == HdmiCecMessageValidator.OK) {
842             mCecController.sendCommand(command, callback);
843         } else {
844             HdmiLogger.error("Invalid message type:" + command);
845             if (callback != null) {
846                 callback.onSendCompleted(SendMessageResult.FAIL);
847             }
848         }
849     }
850 
851     @ServiceThreadOnly
sendCecCommand(HdmiCecMessage command)852     void sendCecCommand(HdmiCecMessage command) {
853         assertRunOnServiceThread();
854         sendCecCommand(command, null);
855     }
856 
857     /**
858      * Send <Feature Abort> command on the given CEC message if possible.
859      * If the aborted message is invalid, then it wont send the message.
860      * @param command original command to be aborted
861      * @param reason reason of feature abort
862      */
863     @ServiceThreadOnly
maySendFeatureAbortCommand(HdmiCecMessage command, int reason)864     void maySendFeatureAbortCommand(HdmiCecMessage command, int reason) {
865         assertRunOnServiceThread();
866         mCecController.maySendFeatureAbortCommand(command, reason);
867     }
868 
869     @ServiceThreadOnly
handleCecCommand(HdmiCecMessage message)870     boolean handleCecCommand(HdmiCecMessage message) {
871         assertRunOnServiceThread();
872         if (!mAddressAllocated) {
873             mCecMessageBuffer.bufferMessage(message);
874             return true;
875         }
876         int errorCode = mMessageValidator.isValid(message);
877         if (errorCode != HdmiCecMessageValidator.OK) {
878             // We'll not response on the messages with the invalid source or destination
879             // or with parameter length shorter than specified in the standard.
880             if (errorCode == HdmiCecMessageValidator.ERROR_PARAMETER) {
881                 maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
882             }
883             return true;
884         }
885         return dispatchMessageToLocalDevice(message);
886     }
887 
enableAudioReturnChannel(int portId, boolean enabled)888     void enableAudioReturnChannel(int portId, boolean enabled) {
889         mCecController.enableAudioReturnChannel(portId, enabled);
890     }
891 
892     @ServiceThreadOnly
dispatchMessageToLocalDevice(HdmiCecMessage message)893     private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
894         assertRunOnServiceThread();
895         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
896             if (device.dispatchMessage(message)
897                     && message.getDestination() != Constants.ADDR_BROADCAST) {
898                 return true;
899             }
900         }
901 
902         if (message.getDestination() != Constants.ADDR_BROADCAST) {
903             HdmiLogger.warning("Unhandled cec command:" + message);
904         }
905         return false;
906     }
907 
908     /**
909      * Called when a new hotplug event is issued.
910      *
911      * @param portId hdmi port number where hot plug event issued.
912      * @param connected whether to be plugged in or not
913      */
914     @ServiceThreadOnly
onHotplug(int portId, boolean connected)915     void onHotplug(int portId, boolean connected) {
916         assertRunOnServiceThread();
917 
918         if (connected && !isTvDevice()) {
919             ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
920             for (int type : mLocalDevices) {
921                 HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(type);
922                 if (localDevice == null) {
923                     localDevice = HdmiCecLocalDevice.create(this, type);
924                     localDevice.init();
925                 }
926                 localDevices.add(localDevice);
927             }
928             allocateLogicalAddress(localDevices, INITIATED_BY_HOTPLUG);
929         }
930 
931         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
932             device.onHotplug(portId, connected);
933         }
934         announceHotplugEvent(portId, connected);
935     }
936 
937     /**
938      * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
939      * devices.
940      *
941      * @param callback an interface used to get a list of all remote devices' address
942      * @param sourceAddress a logical address of source device where sends polling message
943      * @param pickStrategy strategy how to pick polling candidates
944      * @param retryCount the number of retry used to send polling message to remote devices
945      * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
946      */
947     @ServiceThreadOnly
pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy, int retryCount)948     void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
949             int retryCount) {
950         assertRunOnServiceThread();
951         mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
952                 retryCount);
953     }
954 
checkPollStrategy(int pickStrategy)955     private int checkPollStrategy(int pickStrategy) {
956         int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
957         if (strategy == 0) {
958             throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
959         }
960         int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
961         if (iterationStrategy == 0) {
962             throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
963         }
964         return strategy | iterationStrategy;
965     }
966 
getAllLocalDevices()967     List<HdmiCecLocalDevice> getAllLocalDevices() {
968         assertRunOnServiceThread();
969         return mCecController.getLocalDeviceList();
970     }
971 
getServiceLock()972     Object getServiceLock() {
973         return mLock;
974     }
975 
setAudioStatus(boolean mute, int volume)976     void setAudioStatus(boolean mute, int volume) {
977         if (!isTvDeviceEnabled() || !tv().isSystemAudioActivated()) {
978             return;
979         }
980         AudioManager audioManager = getAudioManager();
981         boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
982         if (mute) {
983             if (!muted) {
984                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
985             }
986         } else {
987             if (muted) {
988                 audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
989             }
990             // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
991             // volume change notification back to hdmi control service.
992             audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
993                     AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
994         }
995     }
996 
announceSystemAudioModeChange(boolean enabled)997     void announceSystemAudioModeChange(boolean enabled) {
998         synchronized (mLock) {
999             for (SystemAudioModeChangeListenerRecord record :
1000                     mSystemAudioModeChangeListenerRecords) {
1001                 invokeSystemAudioModeChangeLocked(record.mListener, enabled);
1002             }
1003         }
1004     }
1005 
createDeviceInfo(int logicalAddress, int deviceType, int powerStatus)1006     private HdmiDeviceInfo createDeviceInfo(int logicalAddress, int deviceType, int powerStatus) {
1007         // TODO: find better name instead of model name.
1008         String displayName = Build.MODEL;
1009         return new HdmiDeviceInfo(logicalAddress,
1010                 getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
1011                 getVendorId(), displayName);
1012     }
1013 
1014     @ServiceThreadOnly
handleMhlHotplugEvent(int portId, boolean connected)1015     void handleMhlHotplugEvent(int portId, boolean connected) {
1016         assertRunOnServiceThread();
1017         // Hotplug event is used to add/remove MHL devices as TV input.
1018         if (connected) {
1019             HdmiMhlLocalDeviceStub newDevice = new HdmiMhlLocalDeviceStub(this, portId);
1020             HdmiMhlLocalDeviceStub oldDevice = mMhlController.addLocalDevice(newDevice);
1021             if (oldDevice != null) {
1022                 oldDevice.onDeviceRemoved();
1023                 Slog.i(TAG, "Old device of port " + portId + " is removed");
1024             }
1025             invokeDeviceEventListeners(newDevice.getInfo(), DEVICE_EVENT_ADD_DEVICE);
1026             updateSafeMhlInput();
1027         } else {
1028             HdmiMhlLocalDeviceStub device = mMhlController.removeLocalDevice(portId);
1029             if (device != null) {
1030                 device.onDeviceRemoved();
1031                 invokeDeviceEventListeners(device.getInfo(), DEVICE_EVENT_REMOVE_DEVICE);
1032                 updateSafeMhlInput();
1033             } else {
1034                 Slog.w(TAG, "No device to remove:[portId=" + portId);
1035             }
1036         }
1037         announceHotplugEvent(portId, connected);
1038     }
1039 
1040     @ServiceThreadOnly
handleMhlBusModeChanged(int portId, int busmode)1041     void handleMhlBusModeChanged(int portId, int busmode) {
1042         assertRunOnServiceThread();
1043         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1044         if (device != null) {
1045             device.setBusMode(busmode);
1046         } else {
1047             Slog.w(TAG, "No mhl device exists for bus mode change[portId:" + portId +
1048                     ", busmode:" + busmode + "]");
1049         }
1050     }
1051 
1052     @ServiceThreadOnly
handleMhlBusOvercurrent(int portId, boolean on)1053     void handleMhlBusOvercurrent(int portId, boolean on) {
1054         assertRunOnServiceThread();
1055         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1056         if (device != null) {
1057             device.onBusOvercurrentDetected(on);
1058         } else {
1059             Slog.w(TAG, "No mhl device exists for bus overcurrent event[portId:" + portId + "]");
1060         }
1061     }
1062 
1063     @ServiceThreadOnly
handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId)1064     void handleMhlDeviceStatusChanged(int portId, int adopterId, int deviceId) {
1065         assertRunOnServiceThread();
1066         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1067 
1068         if (device != null) {
1069             device.setDeviceStatusChange(adopterId, deviceId);
1070         } else {
1071             Slog.w(TAG, "No mhl device exists for device status event[portId:"
1072                     + portId + ", adopterId:" + adopterId + ", deviceId:" + deviceId + "]");
1073         }
1074     }
1075 
1076     @ServiceThreadOnly
updateSafeMhlInput()1077     private void updateSafeMhlInput() {
1078         assertRunOnServiceThread();
1079         List<HdmiDeviceInfo> inputs = Collections.emptyList();
1080         SparseArray<HdmiMhlLocalDeviceStub> devices = mMhlController.getAllLocalDevices();
1081         for (int i = 0; i < devices.size(); ++i) {
1082             HdmiMhlLocalDeviceStub device = devices.valueAt(i);
1083             HdmiDeviceInfo info = device.getInfo();
1084             if (info != null) {
1085                 if (inputs.isEmpty()) {
1086                     inputs = new ArrayList<>();
1087                 }
1088                 inputs.add(device.getInfo());
1089             }
1090         }
1091         synchronized (mLock) {
1092             mMhlDevices = inputs;
1093         }
1094     }
1095 
getMhlDevicesLocked()1096     private List<HdmiDeviceInfo> getMhlDevicesLocked() {
1097         return mMhlDevices;
1098     }
1099 
1100     private class HdmiMhlVendorCommandListenerRecord implements IBinder.DeathRecipient {
1101         private final IHdmiMhlVendorCommandListener mListener;
1102 
HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener)1103         public HdmiMhlVendorCommandListenerRecord(IHdmiMhlVendorCommandListener listener) {
1104             mListener = listener;
1105         }
1106 
1107         @Override
binderDied()1108         public void binderDied() {
1109             mMhlVendorCommandListenerRecords.remove(this);
1110         }
1111     }
1112 
1113     // Record class that monitors the event of the caller of being killed. Used to clean up
1114     // the listener list and record list accordingly.
1115     private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
1116         private final IHdmiHotplugEventListener mListener;
1117 
HotplugEventListenerRecord(IHdmiHotplugEventListener listener)1118         public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
1119             mListener = listener;
1120         }
1121 
1122         @Override
binderDied()1123         public void binderDied() {
1124             synchronized (mLock) {
1125                 mHotplugEventListenerRecords.remove(this);
1126             }
1127         }
1128 
1129         @Override
equals(Object obj)1130         public boolean equals(Object obj) {
1131             if (!(obj instanceof HotplugEventListenerRecord)) return false;
1132             if (obj == this) return true;
1133             HotplugEventListenerRecord other = (HotplugEventListenerRecord) obj;
1134             return other.mListener == this.mListener;
1135         }
1136 
1137         @Override
hashCode()1138         public int hashCode() {
1139             return mListener.hashCode();
1140         }
1141     }
1142 
1143     private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
1144         private final IHdmiDeviceEventListener mListener;
1145 
DeviceEventListenerRecord(IHdmiDeviceEventListener listener)1146         public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
1147             mListener = listener;
1148         }
1149 
1150         @Override
binderDied()1151         public void binderDied() {
1152             synchronized (mLock) {
1153                 mDeviceEventListenerRecords.remove(this);
1154             }
1155         }
1156     }
1157 
1158     private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
1159         private final IHdmiSystemAudioModeChangeListener mListener;
1160 
SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener)1161         public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
1162             mListener = listener;
1163         }
1164 
1165         @Override
binderDied()1166         public void binderDied() {
1167             synchronized (mLock) {
1168                 mSystemAudioModeChangeListenerRecords.remove(this);
1169             }
1170         }
1171     }
1172 
1173     class VendorCommandListenerRecord implements IBinder.DeathRecipient {
1174         private final IHdmiVendorCommandListener mListener;
1175         private final int mDeviceType;
1176 
VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType)1177         public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
1178             mListener = listener;
1179             mDeviceType = deviceType;
1180         }
1181 
1182         @Override
binderDied()1183         public void binderDied() {
1184             synchronized (mLock) {
1185                 mVendorCommandListenerRecords.remove(this);
1186             }
1187         }
1188     }
1189 
1190     private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
1191         private final IHdmiRecordListener mListener;
1192 
HdmiRecordListenerRecord(IHdmiRecordListener listener)1193         public HdmiRecordListenerRecord(IHdmiRecordListener listener) {
1194             mListener = listener;
1195         }
1196 
1197         @Override
binderDied()1198         public void binderDied() {
1199             synchronized (mLock) {
1200                 if (mRecordListenerRecord == this) {
1201                     mRecordListenerRecord = null;
1202                 }
1203             }
1204         }
1205     }
1206 
enforceAccessPermission()1207     private void enforceAccessPermission() {
1208         getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
1209     }
1210 
1211     private final class BinderService extends IHdmiControlService.Stub {
1212         @Override
getSupportedTypes()1213         public int[] getSupportedTypes() {
1214             enforceAccessPermission();
1215             // mLocalDevices is an unmodifiable list - no lock necesary.
1216             int[] localDevices = new int[mLocalDevices.size()];
1217             for (int i = 0; i < localDevices.length; ++i) {
1218                 localDevices[i] = mLocalDevices.get(i);
1219             }
1220             return localDevices;
1221         }
1222 
1223         @Override
getActiveSource()1224         public HdmiDeviceInfo getActiveSource() {
1225             enforceAccessPermission();
1226             HdmiCecLocalDeviceTv tv = tv();
1227             if (tv == null) {
1228                 Slog.w(TAG, "Local tv device not available");
1229                 return null;
1230             }
1231             ActiveSource activeSource = tv.getActiveSource();
1232             if (activeSource.isValid()) {
1233                 return new HdmiDeviceInfo(activeSource.logicalAddress,
1234                         activeSource.physicalAddress, HdmiDeviceInfo.PORT_INVALID,
1235                         HdmiDeviceInfo.DEVICE_INACTIVE, 0, "");
1236             }
1237             int activePath = tv.getActivePath();
1238             if (activePath != HdmiDeviceInfo.PATH_INVALID) {
1239                 HdmiDeviceInfo info = tv.getSafeDeviceInfoByPath(activePath);
1240                 return (info != null) ? info : new HdmiDeviceInfo(activePath, tv.getActivePortId());
1241             }
1242             return null;
1243         }
1244 
1245         @Override
deviceSelect(final int deviceId, final IHdmiControlCallback callback)1246         public void deviceSelect(final int deviceId, final IHdmiControlCallback callback) {
1247             enforceAccessPermission();
1248             runOnServiceThread(new Runnable() {
1249                 @Override
1250                 public void run() {
1251                     if (callback == null) {
1252                         Slog.e(TAG, "Callback cannot be null");
1253                         return;
1254                     }
1255                     HdmiCecLocalDeviceTv tv = tv();
1256                     if (tv == null) {
1257                         if (!mAddressAllocated) {
1258                             mSelectRequestBuffer.set(SelectRequestBuffer.newDeviceSelect(
1259                                     HdmiControlService.this, deviceId, callback));
1260                             return;
1261                         }
1262                         Slog.w(TAG, "Local tv device not available");
1263                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1264                         return;
1265                     }
1266                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDeviceById(deviceId);
1267                     if (device != null) {
1268                         if (device.getPortId() == tv.getActivePortId()) {
1269                             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
1270                             return;
1271                         }
1272                         // Upon selecting MHL device, we send RAP[Content On] to wake up
1273                         // the connected mobile device, start routing control to switch ports.
1274                         // callback is handled by MHL action.
1275                         device.turnOn(callback);
1276                         tv.doManualPortSwitching(device.getPortId(), null);
1277                         return;
1278                     }
1279                     tv.deviceSelect(deviceId, callback);
1280                 }
1281             });
1282         }
1283 
1284         @Override
portSelect(final int portId, final IHdmiControlCallback callback)1285         public void portSelect(final int portId, final IHdmiControlCallback callback) {
1286             enforceAccessPermission();
1287             runOnServiceThread(new Runnable() {
1288                 @Override
1289                 public void run() {
1290                     if (callback == null) {
1291                         Slog.e(TAG, "Callback cannot be null");
1292                         return;
1293                     }
1294                     HdmiCecLocalDeviceTv tv = tv();
1295                     if (tv == null) {
1296                         if (!mAddressAllocated) {
1297                             mSelectRequestBuffer.set(SelectRequestBuffer.newPortSelect(
1298                                     HdmiControlService.this, portId, callback));
1299                             return;
1300                         }
1301                         Slog.w(TAG, "Local tv device not available");
1302                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1303                         return;
1304                     }
1305                     tv.doManualPortSwitching(portId, callback);
1306                 }
1307             });
1308         }
1309 
1310         @Override
sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed)1311         public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
1312             enforceAccessPermission();
1313             runOnServiceThread(new Runnable() {
1314                 @Override
1315                 public void run() {
1316                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(mActivePortId);
1317                     if (device != null) {
1318                         device.sendKeyEvent(keyCode, isPressed);
1319                         return;
1320                     }
1321                     if (mCecController != null) {
1322                         HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
1323                         if (localDevice == null) {
1324                             Slog.w(TAG, "Local device not available");
1325                             return;
1326                         }
1327                         localDevice.sendKeyEvent(keyCode, isPressed);
1328                     }
1329                 }
1330             });
1331         }
1332 
1333         @Override
oneTouchPlay(final IHdmiControlCallback callback)1334         public void oneTouchPlay(final IHdmiControlCallback callback) {
1335             enforceAccessPermission();
1336             runOnServiceThread(new Runnable() {
1337                 @Override
1338                 public void run() {
1339                     HdmiControlService.this.oneTouchPlay(callback);
1340                 }
1341             });
1342         }
1343 
1344         @Override
queryDisplayStatus(final IHdmiControlCallback callback)1345         public void queryDisplayStatus(final IHdmiControlCallback callback) {
1346             enforceAccessPermission();
1347             runOnServiceThread(new Runnable() {
1348                 @Override
1349                 public void run() {
1350                     HdmiControlService.this.queryDisplayStatus(callback);
1351                 }
1352             });
1353         }
1354 
1355         @Override
addHotplugEventListener(final IHdmiHotplugEventListener listener)1356         public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1357             enforceAccessPermission();
1358             HdmiControlService.this.addHotplugEventListener(listener);
1359         }
1360 
1361         @Override
removeHotplugEventListener(final IHdmiHotplugEventListener listener)1362         public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
1363             enforceAccessPermission();
1364             HdmiControlService.this.removeHotplugEventListener(listener);
1365         }
1366 
1367         @Override
addDeviceEventListener(final IHdmiDeviceEventListener listener)1368         public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
1369             enforceAccessPermission();
1370             HdmiControlService.this.addDeviceEventListener(listener);
1371         }
1372 
1373         @Override
getPortInfo()1374         public List<HdmiPortInfo> getPortInfo() {
1375             enforceAccessPermission();
1376             return HdmiControlService.this.getPortInfo();
1377         }
1378 
1379         @Override
canChangeSystemAudioMode()1380         public boolean canChangeSystemAudioMode() {
1381             enforceAccessPermission();
1382             HdmiCecLocalDeviceTv tv = tv();
1383             if (tv == null) {
1384                 return false;
1385             }
1386             return tv.hasSystemAudioDevice();
1387         }
1388 
1389         @Override
getSystemAudioMode()1390         public boolean getSystemAudioMode() {
1391             enforceAccessPermission();
1392             HdmiCecLocalDeviceTv tv = tv();
1393             if (tv == null) {
1394                 return false;
1395             }
1396             return tv.isSystemAudioActivated();
1397         }
1398 
1399         @Override
setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback)1400         public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
1401             enforceAccessPermission();
1402             runOnServiceThread(new Runnable() {
1403                 @Override
1404                 public void run() {
1405                     HdmiCecLocalDeviceTv tv = tv();
1406                     if (tv == null) {
1407                         Slog.w(TAG, "Local tv device not available");
1408                         invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1409                         return;
1410                     }
1411                     tv.changeSystemAudioMode(enabled, callback);
1412                 }
1413             });
1414         }
1415 
1416         @Override
addSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1417         public void addSystemAudioModeChangeListener(
1418                 final IHdmiSystemAudioModeChangeListener listener) {
1419             enforceAccessPermission();
1420             HdmiControlService.this.addSystemAudioModeChangeListner(listener);
1421         }
1422 
1423         @Override
removeSystemAudioModeChangeListener( final IHdmiSystemAudioModeChangeListener listener)1424         public void removeSystemAudioModeChangeListener(
1425                 final IHdmiSystemAudioModeChangeListener listener) {
1426             enforceAccessPermission();
1427             HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
1428         }
1429 
1430         @Override
setInputChangeListener(final IHdmiInputChangeListener listener)1431         public void setInputChangeListener(final IHdmiInputChangeListener listener) {
1432             enforceAccessPermission();
1433             HdmiControlService.this.setInputChangeListener(listener);
1434         }
1435 
1436         @Override
getInputDevices()1437         public List<HdmiDeviceInfo> getInputDevices() {
1438             enforceAccessPermission();
1439             // No need to hold the lock for obtaining TV device as the local device instance
1440             // is preserved while the HDMI control is enabled.
1441             HdmiCecLocalDeviceTv tv = tv();
1442             synchronized (mLock) {
1443                 List<HdmiDeviceInfo> cecDevices = (tv == null)
1444                         ? Collections.<HdmiDeviceInfo>emptyList()
1445                         : tv.getSafeExternalInputsLocked();
1446                 return HdmiUtils.mergeToUnmodifiableList(cecDevices, getMhlDevicesLocked());
1447             }
1448         }
1449 
1450         // Returns all the CEC devices on the bus including system audio, switch,
1451         // even those of reserved type.
1452         @Override
getDeviceList()1453         public List<HdmiDeviceInfo> getDeviceList() {
1454             enforceAccessPermission();
1455             HdmiCecLocalDeviceTv tv = tv();
1456             synchronized (mLock) {
1457                 return (tv == null)
1458                         ? Collections.<HdmiDeviceInfo>emptyList()
1459                         : tv.getSafeCecDevicesLocked();
1460             }
1461         }
1462 
1463         @Override
setSystemAudioVolume(final int oldIndex, final int newIndex, final int maxIndex)1464         public void setSystemAudioVolume(final int oldIndex, final int newIndex,
1465                 final int maxIndex) {
1466             enforceAccessPermission();
1467             runOnServiceThread(new Runnable() {
1468                 @Override
1469                 public void run() {
1470                     HdmiCecLocalDeviceTv tv = tv();
1471                     if (tv == null) {
1472                         Slog.w(TAG, "Local tv device not available");
1473                         return;
1474                     }
1475                     tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
1476                 }
1477             });
1478         }
1479 
1480         @Override
setSystemAudioMute(final boolean mute)1481         public void setSystemAudioMute(final boolean mute) {
1482             enforceAccessPermission();
1483             runOnServiceThread(new Runnable() {
1484                 @Override
1485                 public void run() {
1486                     HdmiCecLocalDeviceTv tv = tv();
1487                     if (tv == null) {
1488                         Slog.w(TAG, "Local tv device not available");
1489                         return;
1490                     }
1491                     tv.changeMute(mute);
1492                 }
1493             });
1494         }
1495 
1496         @Override
setArcMode(final boolean enabled)1497         public void setArcMode(final boolean enabled) {
1498             enforceAccessPermission();
1499             runOnServiceThread(new Runnable() {
1500                 @Override
1501                 public void run() {
1502                     HdmiCecLocalDeviceTv tv = tv();
1503                     if (tv == null) {
1504                         Slog.w(TAG, "Local tv device not available to change arc mode.");
1505                         return;
1506                     }
1507                 }
1508             });
1509         }
1510 
1511         @Override
setProhibitMode(final boolean enabled)1512         public void setProhibitMode(final boolean enabled) {
1513             enforceAccessPermission();
1514             if (!isTvDevice()) {
1515                 return;
1516             }
1517             HdmiControlService.this.setProhibitMode(enabled);
1518         }
1519 
1520         @Override
addVendorCommandListener(final IHdmiVendorCommandListener listener, final int deviceType)1521         public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1522                 final int deviceType) {
1523             enforceAccessPermission();
1524             HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1525         }
1526 
1527         @Override
sendVendorCommand(final int deviceType, final int targetAddress, final byte[] params, final boolean hasVendorId)1528         public void sendVendorCommand(final int deviceType, final int targetAddress,
1529                 final byte[] params, final boolean hasVendorId) {
1530             enforceAccessPermission();
1531             runOnServiceThread(new Runnable() {
1532                 @Override
1533                 public void run() {
1534                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1535                     if (device == null) {
1536                         Slog.w(TAG, "Local device not available");
1537                         return;
1538                     }
1539                     if (hasVendorId) {
1540                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1541                                 device.getDeviceInfo().getLogicalAddress(), targetAddress,
1542                                 getVendorId(), params));
1543                     } else {
1544                         sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1545                                 device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1546                     }
1547                 }
1548             });
1549         }
1550 
1551         @Override
sendStandby(final int deviceType, final int deviceId)1552         public void sendStandby(final int deviceType, final int deviceId) {
1553             enforceAccessPermission();
1554             runOnServiceThread(new Runnable() {
1555                 @Override
1556                 public void run() {
1557                     HdmiMhlLocalDeviceStub mhlDevice = mMhlController.getLocalDeviceById(deviceId);
1558                     if (mhlDevice != null) {
1559                         mhlDevice.sendStandby();
1560                         return;
1561                     }
1562                     HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1563                     if (device == null) {
1564                         Slog.w(TAG, "Local device not available");
1565                         return;
1566                     }
1567                     device.sendStandby(deviceId);
1568                 }
1569             });
1570         }
1571 
1572         @Override
setHdmiRecordListener(IHdmiRecordListener listener)1573         public void setHdmiRecordListener(IHdmiRecordListener listener) {
1574             enforceAccessPermission();
1575             HdmiControlService.this.setHdmiRecordListener(listener);
1576         }
1577 
1578         @Override
startOneTouchRecord(final int recorderAddress, final byte[] recordSource)1579         public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1580             enforceAccessPermission();
1581             runOnServiceThread(new Runnable() {
1582                 @Override
1583                 public void run() {
1584                     if (!isTvDeviceEnabled()) {
1585                         Slog.w(TAG, "TV device is not enabled.");
1586                         return;
1587                     }
1588                     tv().startOneTouchRecord(recorderAddress, recordSource);
1589                 }
1590             });
1591         }
1592 
1593         @Override
stopOneTouchRecord(final int recorderAddress)1594         public void stopOneTouchRecord(final int recorderAddress) {
1595             enforceAccessPermission();
1596             runOnServiceThread(new Runnable() {
1597                 @Override
1598                 public void run() {
1599                     if (!isTvDeviceEnabled()) {
1600                         Slog.w(TAG, "TV device is not enabled.");
1601                         return;
1602                     }
1603                     tv().stopOneTouchRecord(recorderAddress);
1604                 }
1605             });
1606         }
1607 
1608         @Override
startTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)1609         public void startTimerRecording(final int recorderAddress, final int sourceType,
1610                 final byte[] recordSource) {
1611             enforceAccessPermission();
1612             runOnServiceThread(new Runnable() {
1613                 @Override
1614                 public void run() {
1615                     if (!isTvDeviceEnabled()) {
1616                         Slog.w(TAG, "TV device is not enabled.");
1617                         return;
1618                     }
1619                     tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1620                 }
1621             });
1622         }
1623 
1624         @Override
clearTimerRecording(final int recorderAddress, final int sourceType, final byte[] recordSource)1625         public void clearTimerRecording(final int recorderAddress, final int sourceType,
1626                 final byte[] recordSource) {
1627             enforceAccessPermission();
1628             runOnServiceThread(new Runnable() {
1629                 @Override
1630                 public void run() {
1631                     if (!isTvDeviceEnabled()) {
1632                         Slog.w(TAG, "TV device is not enabled.");
1633                         return;
1634                     }
1635                     tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1636                 }
1637             });
1638         }
1639 
1640         @Override
sendMhlVendorCommand(final int portId, final int offset, final int length, final byte[] data)1641         public void sendMhlVendorCommand(final int portId, final int offset, final int length,
1642                 final byte[] data) {
1643             enforceAccessPermission();
1644             runOnServiceThread(new Runnable() {
1645                 @Override
1646                 public void run() {
1647                     if (!isControlEnabled()) {
1648                         Slog.w(TAG, "Hdmi control is disabled.");
1649                         return ;
1650                     }
1651                     HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
1652                     if (device == null) {
1653                         Slog.w(TAG, "Invalid port id:" + portId);
1654                         return;
1655                     }
1656                     mMhlController.sendVendorCommand(portId, offset, length, data);
1657                 }
1658             });
1659         }
1660 
1661         @Override
addHdmiMhlVendorCommandListener( IHdmiMhlVendorCommandListener listener)1662         public void addHdmiMhlVendorCommandListener(
1663                 IHdmiMhlVendorCommandListener listener) {
1664             enforceAccessPermission();
1665             HdmiControlService.this.addHdmiMhlVendorCommandListener(listener);
1666         }
1667 
1668         @Override
setStandbyMode(final boolean isStandbyModeOn)1669         public void setStandbyMode(final boolean isStandbyModeOn) {
1670             enforceAccessPermission();
1671             runOnServiceThread(new Runnable() {
1672                 @Override
1673                 public void run() {
1674                     HdmiControlService.this.setStandbyMode(isStandbyModeOn);
1675                 }
1676             });
1677         }
1678 
1679         @Override
dump(FileDescriptor fd, final PrintWriter writer, String[] args)1680         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
1681             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
1682             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
1683 
1684             pw.println("mHdmiControlEnabled: " + mHdmiControlEnabled);
1685             pw.println("mProhibitMode: " + mProhibitMode);
1686             if (mCecController != null) {
1687                 pw.println("mCecController: ");
1688                 pw.increaseIndent();
1689                 mCecController.dump(pw);
1690                 pw.decreaseIndent();
1691             }
1692 
1693             pw.println("mMhlController: ");
1694             pw.increaseIndent();
1695             mMhlController.dump(pw);
1696             pw.decreaseIndent();
1697 
1698             pw.println("mPortInfo: ");
1699             pw.increaseIndent();
1700             for (HdmiPortInfo hdmiPortInfo : mPortInfo) {
1701                 pw.println("- " + hdmiPortInfo);
1702             }
1703             pw.decreaseIndent();
1704             pw.println("mPowerStatus: " + mPowerStatus);
1705         }
1706     }
1707 
1708     @ServiceThreadOnly
oneTouchPlay(final IHdmiControlCallback callback)1709     private void oneTouchPlay(final IHdmiControlCallback callback) {
1710         assertRunOnServiceThread();
1711         HdmiCecLocalDevicePlayback source = playback();
1712         if (source == null) {
1713             Slog.w(TAG, "Local playback device not available");
1714             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1715             return;
1716         }
1717         source.oneTouchPlay(callback);
1718     }
1719 
1720     @ServiceThreadOnly
queryDisplayStatus(final IHdmiControlCallback callback)1721     private void queryDisplayStatus(final IHdmiControlCallback callback) {
1722         assertRunOnServiceThread();
1723         HdmiCecLocalDevicePlayback source = playback();
1724         if (source == null) {
1725             Slog.w(TAG, "Local playback device not available");
1726             invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1727             return;
1728         }
1729         source.queryDisplayStatus(callback);
1730     }
1731 
addHotplugEventListener(final IHdmiHotplugEventListener listener)1732     private void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
1733         final HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1734         try {
1735             listener.asBinder().linkToDeath(record, 0);
1736         } catch (RemoteException e) {
1737             Slog.w(TAG, "Listener already died");
1738             return;
1739         }
1740         synchronized (mLock) {
1741             mHotplugEventListenerRecords.add(record);
1742         }
1743 
1744         // Inform the listener of the initial state of each HDMI port by generating
1745         // hotplug events.
1746         runOnServiceThread(new Runnable() {
1747             @Override
1748             public void run() {
1749                 synchronized (mLock) {
1750                     if (!mHotplugEventListenerRecords.contains(record)) return;
1751                 }
1752                 for (HdmiPortInfo port : mPortInfo) {
1753                     HdmiHotplugEvent event = new HdmiHotplugEvent(port.getId(),
1754                             mCecController.isConnected(port.getId()));
1755                     synchronized (mLock) {
1756                         invokeHotplugEventListenerLocked(listener, event);
1757                     }
1758                 }
1759             }
1760         });
1761     }
1762 
removeHotplugEventListener(IHdmiHotplugEventListener listener)1763     private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1764         synchronized (mLock) {
1765             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1766                 if (record.mListener.asBinder() == listener.asBinder()) {
1767                     listener.asBinder().unlinkToDeath(record, 0);
1768                     mHotplugEventListenerRecords.remove(record);
1769                     break;
1770                 }
1771             }
1772         }
1773     }
1774 
addDeviceEventListener(IHdmiDeviceEventListener listener)1775     private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1776         DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1777         try {
1778             listener.asBinder().linkToDeath(record, 0);
1779         } catch (RemoteException e) {
1780             Slog.w(TAG, "Listener already died");
1781             return;
1782         }
1783         synchronized (mLock) {
1784             mDeviceEventListenerRecords.add(record);
1785         }
1786     }
1787 
invokeDeviceEventListeners(HdmiDeviceInfo device, int status)1788     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
1789         synchronized (mLock) {
1790             for (DeviceEventListenerRecord record : mDeviceEventListenerRecords) {
1791                 try {
1792                     record.mListener.onStatusChanged(device, status);
1793                 } catch (RemoteException e) {
1794                     Slog.e(TAG, "Failed to report device event:" + e);
1795                 }
1796             }
1797         }
1798     }
1799 
addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener)1800     private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1801         SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1802                 listener);
1803         try {
1804             listener.asBinder().linkToDeath(record, 0);
1805         } catch (RemoteException e) {
1806             Slog.w(TAG, "Listener already died");
1807             return;
1808         }
1809         synchronized (mLock) {
1810             mSystemAudioModeChangeListenerRecords.add(record);
1811         }
1812     }
1813 
removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener)1814     private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1815         synchronized (mLock) {
1816             for (SystemAudioModeChangeListenerRecord record :
1817                     mSystemAudioModeChangeListenerRecords) {
1818                 if (record.mListener.asBinder() == listener) {
1819                     listener.asBinder().unlinkToDeath(record, 0);
1820                     mSystemAudioModeChangeListenerRecords.remove(record);
1821                     break;
1822                 }
1823             }
1824         }
1825     }
1826 
1827     private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1828         private final IHdmiInputChangeListener mListener;
1829 
InputChangeListenerRecord(IHdmiInputChangeListener listener)1830         public InputChangeListenerRecord(IHdmiInputChangeListener listener) {
1831             mListener = listener;
1832         }
1833 
1834         @Override
binderDied()1835         public void binderDied() {
1836             synchronized (mLock) {
1837                 if (mInputChangeListenerRecord == this) {
1838                     mInputChangeListenerRecord = null;
1839                 }
1840             }
1841         }
1842     }
1843 
setInputChangeListener(IHdmiInputChangeListener listener)1844     private void setInputChangeListener(IHdmiInputChangeListener listener) {
1845         synchronized (mLock) {
1846             mInputChangeListenerRecord = new InputChangeListenerRecord(listener);
1847             try {
1848                 listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1849             } catch (RemoteException e) {
1850                 Slog.w(TAG, "Listener already died");
1851                 return;
1852             }
1853         }
1854     }
1855 
invokeInputChangeListener(HdmiDeviceInfo info)1856     void invokeInputChangeListener(HdmiDeviceInfo info) {
1857         synchronized (mLock) {
1858             if (mInputChangeListenerRecord != null) {
1859                 try {
1860                     mInputChangeListenerRecord.mListener.onChanged(info);
1861                 } catch (RemoteException e) {
1862                     Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1863                 }
1864             }
1865         }
1866     }
1867 
setHdmiRecordListener(IHdmiRecordListener listener)1868     private void setHdmiRecordListener(IHdmiRecordListener listener) {
1869         synchronized (mLock) {
1870             mRecordListenerRecord = new HdmiRecordListenerRecord(listener);
1871             try {
1872                 listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1873             } catch (RemoteException e) {
1874                 Slog.w(TAG, "Listener already died.", e);
1875             }
1876         }
1877     }
1878 
invokeRecordRequestListener(int recorderAddress)1879     byte[] invokeRecordRequestListener(int recorderAddress) {
1880         synchronized (mLock) {
1881             if (mRecordListenerRecord != null) {
1882                 try {
1883                     return mRecordListenerRecord.mListener.getOneTouchRecordSource(recorderAddress);
1884                 } catch (RemoteException e) {
1885                     Slog.w(TAG, "Failed to start record.", e);
1886                 }
1887             }
1888             return EmptyArray.BYTE;
1889         }
1890     }
1891 
invokeOneTouchRecordResult(int recorderAddress, int result)1892     void invokeOneTouchRecordResult(int recorderAddress, int result) {
1893         synchronized (mLock) {
1894             if (mRecordListenerRecord != null) {
1895                 try {
1896                     mRecordListenerRecord.mListener.onOneTouchRecordResult(recorderAddress, result);
1897                 } catch (RemoteException e) {
1898                     Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1899                 }
1900             }
1901         }
1902     }
1903 
invokeTimerRecordingResult(int recorderAddress, int result)1904     void invokeTimerRecordingResult(int recorderAddress, int result) {
1905         synchronized (mLock) {
1906             if (mRecordListenerRecord != null) {
1907                 try {
1908                     mRecordListenerRecord.mListener.onTimerRecordingResult(recorderAddress, result);
1909                 } catch (RemoteException e) {
1910                     Slog.w(TAG, "Failed to call onTimerRecordingResult.", e);
1911                 }
1912             }
1913         }
1914     }
1915 
invokeClearTimerRecordingResult(int recorderAddress, int result)1916     void invokeClearTimerRecordingResult(int recorderAddress, int result) {
1917         synchronized (mLock) {
1918             if (mRecordListenerRecord != null) {
1919                 try {
1920                     mRecordListenerRecord.mListener.onClearTimerRecordingResult(recorderAddress,
1921                             result);
1922                 } catch (RemoteException e) {
1923                     Slog.w(TAG, "Failed to call onClearTimerRecordingResult.", e);
1924                 }
1925             }
1926         }
1927     }
1928 
invokeCallback(IHdmiControlCallback callback, int result)1929     private void invokeCallback(IHdmiControlCallback callback, int result) {
1930         try {
1931             callback.onComplete(result);
1932         } catch (RemoteException e) {
1933             Slog.e(TAG, "Invoking callback failed:" + e);
1934         }
1935     }
1936 
invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener, boolean enabled)1937     private void invokeSystemAudioModeChangeLocked(IHdmiSystemAudioModeChangeListener listener,
1938             boolean enabled) {
1939         try {
1940             listener.onStatusChanged(enabled);
1941         } catch (RemoteException e) {
1942             Slog.e(TAG, "Invoking callback failed:" + e);
1943         }
1944     }
1945 
announceHotplugEvent(int portId, boolean connected)1946     private void announceHotplugEvent(int portId, boolean connected) {
1947         HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1948         synchronized (mLock) {
1949             for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1950                 invokeHotplugEventListenerLocked(record.mListener, event);
1951             }
1952         }
1953     }
1954 
invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener, HdmiHotplugEvent event)1955     private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1956             HdmiHotplugEvent event) {
1957         try {
1958             listener.onReceived(event);
1959         } catch (RemoteException e) {
1960             Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1961         }
1962     }
1963 
tv()1964     public HdmiCecLocalDeviceTv tv() {
1965         return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_TV);
1966     }
1967 
isTvDevice()1968     boolean isTvDevice() {
1969         return mLocalDevices.contains(HdmiDeviceInfo.DEVICE_TV);
1970     }
1971 
isTvDeviceEnabled()1972     boolean isTvDeviceEnabled() {
1973         return isTvDevice() && tv() != null;
1974     }
1975 
playback()1976     private HdmiCecLocalDevicePlayback playback() {
1977         return (HdmiCecLocalDevicePlayback)
1978                 mCecController.getLocalDevice(HdmiDeviceInfo.DEVICE_PLAYBACK);
1979     }
1980 
getAudioManager()1981     AudioManager getAudioManager() {
1982         return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1983     }
1984 
isControlEnabled()1985     boolean isControlEnabled() {
1986         synchronized (mLock) {
1987             return mHdmiControlEnabled;
1988         }
1989     }
1990 
1991     @ServiceThreadOnly
getPowerStatus()1992     int getPowerStatus() {
1993         assertRunOnServiceThread();
1994         return mPowerStatus;
1995     }
1996 
1997     @ServiceThreadOnly
isPowerOnOrTransient()1998     boolean isPowerOnOrTransient() {
1999         assertRunOnServiceThread();
2000         return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
2001                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2002     }
2003 
2004     @ServiceThreadOnly
isPowerStandbyOrTransient()2005     boolean isPowerStandbyOrTransient() {
2006         assertRunOnServiceThread();
2007         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
2008                 || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2009     }
2010 
2011     @ServiceThreadOnly
isPowerStandby()2012     boolean isPowerStandby() {
2013         assertRunOnServiceThread();
2014         return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
2015     }
2016 
2017     @ServiceThreadOnly
wakeUp()2018     void wakeUp() {
2019         assertRunOnServiceThread();
2020         mWakeUpMessageReceived = true;
2021         mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2022         // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
2023         // the intent, the sequence will continue at onWakeUp().
2024     }
2025 
2026     @ServiceThreadOnly
standby()2027     void standby() {
2028         assertRunOnServiceThread();
2029         if (!canGoToStandby()) {
2030             return;
2031         }
2032         mStandbyMessageReceived = true;
2033         mPowerManager.goToSleep(SystemClock.uptimeMillis(), PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2034         // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
2035         // the intent, the sequence will continue at onStandby().
2036     }
2037 
isWakeUpMessageReceived()2038     boolean isWakeUpMessageReceived() {
2039         return mWakeUpMessageReceived;
2040     }
2041 
2042     @ServiceThreadOnly
onWakeUp()2043     private void onWakeUp() {
2044         assertRunOnServiceThread();
2045         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
2046         if (mCecController != null) {
2047             if (mHdmiControlEnabled) {
2048                 int startReason = INITIATED_BY_SCREEN_ON;
2049                 if (mWakeUpMessageReceived) {
2050                     startReason = INITIATED_BY_WAKE_UP_MESSAGE;
2051                 }
2052                 initializeCec(startReason);
2053             }
2054         } else {
2055             Slog.i(TAG, "Device does not support HDMI-CEC.");
2056         }
2057         // TODO: Initialize MHL local devices.
2058     }
2059 
2060     @ServiceThreadOnly
onStandby(final int standbyAction)2061     private void onStandby(final int standbyAction) {
2062         assertRunOnServiceThread();
2063         mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
2064         invokeVendorCommandListenersOnControlStateChanged(false,
2065                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_STANDBY);
2066         if (!canGoToStandby()) {
2067             mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2068             return;
2069         }
2070 
2071         final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
2072         disableDevices(new PendingActionClearedCallback() {
2073             @Override
2074             public void onCleared(HdmiCecLocalDevice device) {
2075                 Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
2076                 devices.remove(device);
2077                 if (devices.isEmpty()) {
2078                     onStandbyCompleted(standbyAction);
2079                     // We will not clear local devices here, since some OEM/SOC will keep passing
2080                     // the received packets until the application processor enters to the sleep
2081                     // actually.
2082                 }
2083             }
2084         });
2085     }
2086 
canGoToStandby()2087     private boolean canGoToStandby() {
2088         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2089             if (!device.canGoToStandby()) return false;
2090         }
2091         return true;
2092     }
2093 
2094     @ServiceThreadOnly
onLanguageChanged(String language)2095     private void onLanguageChanged(String language) {
2096         assertRunOnServiceThread();
2097         mLanguage = language;
2098 
2099         if (isTvDeviceEnabled()) {
2100             tv().broadcastMenuLanguage(language);
2101             mCecController.setLanguage(language);
2102         }
2103     }
2104 
2105     @ServiceThreadOnly
getLanguage()2106     String getLanguage() {
2107         assertRunOnServiceThread();
2108         return mLanguage;
2109     }
2110 
disableDevices(PendingActionClearedCallback callback)2111     private void disableDevices(PendingActionClearedCallback callback) {
2112         if (mCecController != null) {
2113             for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2114                 device.disableDevice(mStandbyMessageReceived, callback);
2115             }
2116         }
2117 
2118         mMhlController.clearAllLocalDevices();
2119     }
2120 
2121     @ServiceThreadOnly
clearLocalDevices()2122     private void clearLocalDevices() {
2123         assertRunOnServiceThread();
2124         if (mCecController == null) {
2125             return;
2126         }
2127         mCecController.clearLogicalAddress();
2128         mCecController.clearLocalDevices();
2129     }
2130 
2131     @ServiceThreadOnly
onStandbyCompleted(int standbyAction)2132     private void onStandbyCompleted(int standbyAction) {
2133         assertRunOnServiceThread();
2134         Slog.v(TAG, "onStandbyCompleted");
2135 
2136         if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
2137             return;
2138         }
2139         mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
2140         for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
2141             device.onStandby(mStandbyMessageReceived, standbyAction);
2142         }
2143         mStandbyMessageReceived = false;
2144         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, false);
2145         mMhlController.setOption(OPTION_MHL_SERVICE_CONTROL, DISABLED);
2146     }
2147 
addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType)2148     private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
2149         VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
2150         try {
2151             listener.asBinder().linkToDeath(record, 0);
2152         } catch (RemoteException e) {
2153             Slog.w(TAG, "Listener already died");
2154             return;
2155         }
2156         synchronized (mLock) {
2157             mVendorCommandListenerRecords.add(record);
2158         }
2159     }
2160 
invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress, byte[] params, boolean hasVendorId)2161     boolean invokeVendorCommandListenersOnReceived(int deviceType, int srcAddress, int destAddress,
2162             byte[] params, boolean hasVendorId) {
2163         synchronized (mLock) {
2164             if (mVendorCommandListenerRecords.isEmpty()) {
2165                 return false;
2166             }
2167             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2168                 if (record.mDeviceType != deviceType) {
2169                     continue;
2170                 }
2171                 try {
2172                     record.mListener.onReceived(srcAddress, destAddress, params, hasVendorId);
2173                 } catch (RemoteException e) {
2174                     Slog.e(TAG, "Failed to notify vendor command reception", e);
2175                 }
2176             }
2177             return true;
2178         }
2179     }
2180 
invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason)2181     boolean invokeVendorCommandListenersOnControlStateChanged(boolean enabled, int reason) {
2182         synchronized (mLock) {
2183             if (mVendorCommandListenerRecords.isEmpty()) {
2184                 return false;
2185             }
2186             for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
2187                 try {
2188                     record.mListener.onControlStateChanged(enabled, reason);
2189                 } catch (RemoteException e) {
2190                     Slog.e(TAG, "Failed to notify control-state-changed to vendor handler", e);
2191                 }
2192             }
2193             return true;
2194         }
2195     }
2196 
addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener)2197     private void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {
2198         HdmiMhlVendorCommandListenerRecord record =
2199                 new HdmiMhlVendorCommandListenerRecord(listener);
2200         try {
2201             listener.asBinder().linkToDeath(record, 0);
2202         } catch (RemoteException e) {
2203             Slog.w(TAG, "Listener already died.");
2204             return;
2205         }
2206 
2207         synchronized (mLock) {
2208             mMhlVendorCommandListenerRecords.add(record);
2209         }
2210     }
2211 
invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data)2212     void invokeMhlVendorCommandListeners(int portId, int offest, int length, byte[] data) {
2213         synchronized (mLock) {
2214             for (HdmiMhlVendorCommandListenerRecord record : mMhlVendorCommandListenerRecords) {
2215                 try {
2216                     record.mListener.onReceived(portId, offest, length, data);
2217                 } catch (RemoteException e) {
2218                     Slog.e(TAG, "Failed to notify MHL vendor command", e);
2219                 }
2220             }
2221         }
2222     }
2223 
setStandbyMode(boolean isStandbyModeOn)2224     void setStandbyMode(boolean isStandbyModeOn) {
2225         assertRunOnServiceThread();
2226         if (isPowerOnOrTransient() && isStandbyModeOn) {
2227             mPowerManager.goToSleep(SystemClock.uptimeMillis(),
2228                     PowerManager.GO_TO_SLEEP_REASON_HDMI, 0);
2229             if (playback() != null) {
2230                 playback().sendStandby(0 /* unused */);
2231             }
2232         } else if (isPowerStandbyOrTransient() && !isStandbyModeOn) {
2233             mPowerManager.wakeUp(SystemClock.uptimeMillis(), "android.server.hdmi:WAKE");
2234             if (playback() != null) {
2235                 oneTouchPlay(new IHdmiControlCallback.Stub() {
2236                     @Override
2237                     public void onComplete(int result) {
2238                         if (result != HdmiControlManager.RESULT_SUCCESS) {
2239                             Slog.w(TAG, "Failed to complete 'one touch play'. result=" + result);
2240                         }
2241                     }
2242                 });
2243             }
2244         }
2245     }
2246 
isProhibitMode()2247     boolean isProhibitMode() {
2248         synchronized (mLock) {
2249             return mProhibitMode;
2250         }
2251     }
2252 
setProhibitMode(boolean enabled)2253     void setProhibitMode(boolean enabled) {
2254         synchronized (mLock) {
2255             mProhibitMode = enabled;
2256         }
2257     }
2258 
2259     @ServiceThreadOnly
setCecOption(int key, boolean value)2260     void setCecOption(int key, boolean value) {
2261         assertRunOnServiceThread();
2262         mCecController.setOption(key, value);
2263     }
2264 
2265     @ServiceThreadOnly
setControlEnabled(boolean enabled)2266     void setControlEnabled(boolean enabled) {
2267         assertRunOnServiceThread();
2268 
2269         synchronized (mLock) {
2270             mHdmiControlEnabled = enabled;
2271         }
2272 
2273         if (enabled) {
2274             enableHdmiControlService();
2275             return;
2276         }
2277         // Call the vendor handler before the service is disabled.
2278         invokeVendorCommandListenersOnControlStateChanged(false,
2279                 HdmiControlManager.CONTROL_STATE_CHANGED_REASON_SETTING);
2280         // Post the remained tasks in the service thread again to give the vendor-issued-tasks
2281         // a chance to run.
2282         runOnServiceThread(new Runnable() {
2283             @Override
2284             public void run() {
2285                 disableHdmiControlService();
2286             }
2287         });
2288         return;
2289     }
2290 
2291     @ServiceThreadOnly
enableHdmiControlService()2292     private void enableHdmiControlService() {
2293         mCecController.setOption(OptionKey.SYSTEM_CEC_CONTROL, true);
2294         mMhlController.setOption(OPTION_MHL_ENABLE, ENABLED);
2295 
2296         initializeCec(INITIATED_BY_ENABLE_CEC);
2297     }
2298 
2299     @ServiceThreadOnly
disableHdmiControlService()2300     private void disableHdmiControlService() {
2301         disableDevices(new PendingActionClearedCallback() {
2302             @Override
2303             public void onCleared(HdmiCecLocalDevice device) {
2304                 assertRunOnServiceThread();
2305                 mCecController.flush(new Runnable() {
2306                     @Override
2307                     public void run() {
2308                         mCecController.setOption(OptionKey.ENABLE_CEC, false);
2309                         mMhlController.setOption(OPTION_MHL_ENABLE, DISABLED);
2310                         clearLocalDevices();
2311                     }
2312                 });
2313             }
2314         });
2315     }
2316 
2317     @ServiceThreadOnly
setActivePortId(int portId)2318     void setActivePortId(int portId) {
2319         assertRunOnServiceThread();
2320         mActivePortId = portId;
2321 
2322         // Resets last input for MHL, which stays valid only after the MHL device was selected,
2323         // and no further switching is done.
2324         setLastInputForMhl(Constants.INVALID_PORT_ID);
2325     }
2326 
2327     @ServiceThreadOnly
setLastInputForMhl(int portId)2328     void setLastInputForMhl(int portId) {
2329         assertRunOnServiceThread();
2330         mLastInputMhl = portId;
2331     }
2332 
2333     @ServiceThreadOnly
getLastInputForMhl()2334     int getLastInputForMhl() {
2335         assertRunOnServiceThread();
2336         return mLastInputMhl;
2337     }
2338 
2339     /**
2340      * Performs input change, routing control for MHL device.
2341      *
2342      * @param portId MHL port, or the last port to go back to if {@code contentOn} is false
2343      * @param contentOn {@code true} if RAP data is content on; otherwise false
2344      */
2345     @ServiceThreadOnly
changeInputForMhl(int portId, boolean contentOn)2346     void changeInputForMhl(int portId, boolean contentOn) {
2347         assertRunOnServiceThread();
2348         if (tv() == null) return;
2349         final int lastInput = contentOn ? tv().getActivePortId() : Constants.INVALID_PORT_ID;
2350         if (portId != Constants.INVALID_PORT_ID) {
2351             tv().doManualPortSwitching(portId, new IHdmiControlCallback.Stub() {
2352                 @Override
2353                 public void onComplete(int result) throws RemoteException {
2354                     // Keep the last input to switch back later when RAP[ContentOff] is received.
2355                     // This effectively sets the port to invalid one if the switching is for
2356                     // RAP[ContentOff].
2357                     setLastInputForMhl(lastInput);
2358                 }
2359             });
2360         }
2361         // MHL device is always directly connected to the port. Update the active port ID to avoid
2362         // unnecessary post-routing control task.
2363         tv().setActivePortId(portId);
2364 
2365         // The port is either the MHL-enabled port where the mobile device is connected, or
2366         // the last port to go back to when turnoff command is received. Note that the last port
2367         // may not be the MHL-enabled one. In this case the device info to be passed to
2368         // input change listener should be the one describing the corresponding HDMI port.
2369         HdmiMhlLocalDeviceStub device = mMhlController.getLocalDevice(portId);
2370         HdmiDeviceInfo info = (device != null) ? device.getInfo()
2371                 : mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE);
2372         invokeInputChangeListener(info);
2373     }
2374 
setMhlInputChangeEnabled(boolean enabled)2375    void setMhlInputChangeEnabled(boolean enabled) {
2376        mMhlController.setOption(OPTION_MHL_INPUT_SWITCHING, toInt(enabled));
2377 
2378         synchronized (mLock) {
2379             mMhlInputChangeEnabled = enabled;
2380         }
2381     }
2382 
isMhlInputChangeEnabled()2383     boolean isMhlInputChangeEnabled() {
2384         synchronized (mLock) {
2385             return mMhlInputChangeEnabled;
2386         }
2387     }
2388 
2389     @ServiceThreadOnly
displayOsd(int messageId)2390     void displayOsd(int messageId) {
2391         assertRunOnServiceThread();
2392         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2393         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2394         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2395                 HdmiControlService.PERMISSION);
2396     }
2397 
2398     @ServiceThreadOnly
displayOsd(int messageId, int extra)2399     void displayOsd(int messageId, int extra) {
2400         assertRunOnServiceThread();
2401         Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE);
2402         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId);
2403         intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_EXTRA_PARAM1, extra);
2404         getContext().sendBroadcastAsUser(intent, UserHandle.ALL,
2405                 HdmiControlService.PERMISSION);
2406     }
2407 }
2408