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