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