OSDN Git Service

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