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