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