HdmiControlService.java revision 7e74206693f4ee93afb902d5b3446e2384f2a13d
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 android.annotation.Nullable;
20import android.content.BroadcastReceiver;
21import android.content.ContentResolver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.hardware.hdmi.HdmiCecDeviceInfo;
26import android.hardware.hdmi.HdmiControlManager;
27import android.hardware.hdmi.HdmiHotplugEvent;
28import android.hardware.hdmi.HdmiPortInfo;
29import android.hardware.hdmi.HdmiTvClient;
30import android.hardware.hdmi.IHdmiControlCallback;
31import android.hardware.hdmi.IHdmiControlService;
32import android.hardware.hdmi.IHdmiDeviceEventListener;
33import android.hardware.hdmi.IHdmiHotplugEventListener;
34import android.hardware.hdmi.IHdmiInputChangeListener;
35import android.hardware.hdmi.IHdmiRecordListener;
36import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
37import android.hardware.hdmi.IHdmiVendorCommandListener;
38import android.media.AudioManager;
39import android.os.Build;
40import android.os.Handler;
41import android.os.HandlerThread;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.PowerManager;
45import android.os.RemoteException;
46import android.os.SystemClock;
47import android.provider.Settings.Global;
48import android.util.ArraySet;
49import android.util.Slog;
50import android.util.SparseArray;
51import android.util.SparseIntArray;
52
53import com.android.internal.annotations.GuardedBy;
54import com.android.server.SystemService;
55import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
56import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
57import com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
58import com.android.server.hdmi.HdmiCecLocalDevice.PendingActionClearedCallback;
59
60import libcore.util.EmptyArray;
61
62import java.util.ArrayList;
63import java.util.Arrays;
64import java.util.Collections;
65import java.util.List;
66
67/**
68 * Provides a service for sending and processing HDMI control messages,
69 * HDMI-CEC and MHL control command, and providing the information on both standard.
70 */
71public final class HdmiControlService extends SystemService {
72    private static final String TAG = "HdmiControlService";
73
74    static final String PERMISSION = "android.permission.HDMI_CEC";
75
76    /**
77     * Interface to report send result.
78     */
79    interface SendMessageCallback {
80        /**
81         * Called when {@link HdmiControlService#sendCecCommand} is completed.
82         *
83         * @param error result of send request.
84         * <ul>
85         * <li>{@link Constants#SEND_RESULT_SUCCESS}
86         * <li>{@link Constants#SEND_RESULT_NAK}
87         * <li>{@link Constants#SEND_RESULT_FAILURE}
88         * </ul>
89         */
90        void onSendCompleted(int error);
91    }
92
93    /**
94     * Interface to get a list of available logical devices.
95     */
96    interface DevicePollingCallback {
97        /**
98         * Called when device polling is finished.
99         *
100         * @param ackedAddress a list of logical addresses of available devices
101         */
102        void onPollingFinished(List<Integer> ackedAddress);
103    }
104
105    private class PowerStateReceiver extends BroadcastReceiver {
106        @Override
107        public void onReceive(Context context, Intent intent) {
108            switch (intent.getAction()) {
109                case Intent.ACTION_SCREEN_OFF:
110                    if (isPowerOnOrTransient()) {
111                        onStandby();
112                    }
113                    break;
114                case Intent.ACTION_SCREEN_ON:
115                    if (isPowerStandbyOrTransient()) {
116                        onWakeUp();
117                    }
118                    break;
119            }
120        }
121    }
122
123    // A thread to handle synchronous IO of CEC and MHL control service.
124    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
125    // and sparse call it shares a thread to handle IO operations.
126    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
127
128    // Used to synchronize the access to the service.
129    private final Object mLock = new Object();
130
131    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
132    private final List<Integer> mLocalDevices;
133
134    // List of listeners registered by callers that want to get notified of
135    // hotplug events.
136    @GuardedBy("mLock")
137    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
138
139    // List of records for hotplug event listener to handle the the caller killed in action.
140    @GuardedBy("mLock")
141    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
142            new ArrayList<>();
143
144    // List of listeners registered by callers that want to get notified of
145    // device status events.
146    @GuardedBy("mLock")
147    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
148
149    // List of records for device event listener to handle the the caller killed in action.
150    @GuardedBy("mLock")
151    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
152            new ArrayList<>();
153
154    // List of records for vendor command listener to handle the the caller killed in action.
155    @GuardedBy("mLock")
156    private final ArrayList<VendorCommandListenerRecord> mVendorCommandListenerRecords =
157            new ArrayList<>();
158
159    @GuardedBy("mLock")
160    private IHdmiInputChangeListener mInputChangeListener;
161
162    @GuardedBy("mLock")
163    private InputChangeListenerRecord mInputChangeListenerRecord;
164
165    @GuardedBy("mLock")
166    private IHdmiRecordListener mRecordListener;
167
168    @GuardedBy("mLock")
169    private HdmiRecordListenerRecord mRecordListenerRecord;
170
171    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
172    // handling will be disabled and no request will be handled.
173    @GuardedBy("mLock")
174    private boolean mHdmiControlEnabled;
175
176    // Set to true while the service is in normal mode. While set to false, no input change is
177    // allowed. Used for situations where input change can confuse users such as channel auto-scan,
178    // system upgrade, etc., a.k.a. "prohibit mode".
179    @GuardedBy("mLock")
180    private boolean mProhibitMode;
181
182    // List of listeners registered by callers that want to get notified of
183    // system audio mode changes.
184    private final ArrayList<IHdmiSystemAudioModeChangeListener>
185            mSystemAudioModeChangeListeners = new ArrayList<>();
186    // List of records for system audio mode change to handle the the caller killed in action.
187    private final ArrayList<SystemAudioModeChangeListenerRecord>
188            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
189
190    // Handler used to run a task in service thread.
191    private final Handler mHandler = new Handler();
192
193    @Nullable
194    private HdmiCecController mCecController;
195
196    @Nullable
197    private HdmiMhlController mMhlController;
198
199    // HDMI port information. Stored in the unmodifiable list to keep the static information
200    // from being modified.
201    private List<HdmiPortInfo> mPortInfo;
202
203    // Map from path(physical address) to port ID.
204    private final SparseIntArray mPortIdMap = new SparseIntArray();
205
206    // Map from port ID to HdmiPortInfo.
207    private final SparseArray<HdmiPortInfo> mPortInfoMap = new SparseArray<>();
208
209    private HdmiCecMessageValidator mMessageValidator;
210
211    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
212
213    @ServiceThreadOnly
214    private int mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
215
216    @ServiceThreadOnly
217    private boolean mStandbyMessageReceived = false;
218
219    public HdmiControlService(Context context) {
220        super(context);
221        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
222                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
223    }
224
225    @Override
226    public void onStart() {
227        mIoThread.start();
228        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
229        mProhibitMode = false;
230        mHdmiControlEnabled = readBooleanSetting(Global.HDMI_CONTROL_ENABLED, true);
231
232        mCecController = HdmiCecController.create(this);
233        if (mCecController != null) {
234            // TODO: Remove this as soon as OEM's HAL implementation is corrected.
235            mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE,
236                    HdmiTvClient.ENABLED);
237
238            // TODO: load value for mHdmiControlEnabled from preference.
239            if (mHdmiControlEnabled) {
240                initializeCec(true);
241            }
242        } else {
243            Slog.i(TAG, "Device does not support HDMI-CEC.");
244        }
245
246        mMhlController = HdmiMhlController.create(this);
247        if (mMhlController == null) {
248            Slog.i(TAG, "Device does not support MHL-control.");
249        }
250        initPortInfo();
251        mMessageValidator = new HdmiCecMessageValidator(this);
252        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
253
254        // Register broadcast receiver for power state change.
255        if (mCecController != null || mMhlController != null) {
256            IntentFilter filter = new IntentFilter();
257            filter.addAction(Intent.ACTION_SCREEN_OFF);
258            filter.addAction(Intent.ACTION_SCREEN_ON);
259            getContext().registerReceiver(mPowerStateReceiver, filter);
260        }
261    }
262
263    boolean readBooleanSetting(String key, boolean defVal) {
264        ContentResolver cr = getContext().getContentResolver();
265        return Global.getInt(cr, key, defVal ? Constants.TRUE : Constants.FALSE) == Constants.TRUE;
266    }
267
268    void writeBooleanSetting(String key, boolean value) {
269        ContentResolver cr = getContext().getContentResolver();
270        Global.putInt(cr, key, value ? Constants.TRUE : Constants.FALSE);
271    }
272
273    private void initializeCec(boolean fromBootup) {
274        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL,
275                HdmiTvClient.ENABLED);
276        initializeLocalDevices(mLocalDevices, fromBootup);
277    }
278
279    @ServiceThreadOnly
280    private void initializeLocalDevices(final List<Integer> deviceTypes, final boolean fromBootup) {
281        assertRunOnServiceThread();
282        // A container for [Logical Address, Local device info].
283        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
284        final SparseIntArray finished = new SparseIntArray();
285        clearLocalDevices();
286        for (int type : deviceTypes) {
287            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
288            localDevice.init();
289            mCecController.allocateLogicalAddress(type,
290                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
291                @Override
292                public void onAllocated(int deviceType, int logicalAddress) {
293                    if (logicalAddress == Constants.ADDR_UNREGISTERED) {
294                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
295                    } else {
296                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
297                        localDevice.setDeviceInfo(deviceInfo);
298                        mCecController.addLocalDevice(deviceType, localDevice);
299                        mCecController.addLogicalAddress(logicalAddress);
300                        devices.append(logicalAddress, localDevice);
301                    }
302                    finished.append(deviceType, logicalAddress);
303
304                    // Address allocation completed for all devices. Notify each device.
305                    if (deviceTypes.size() == finished.size()) {
306                        if (mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON) {
307                            mPowerStatus = HdmiControlManager.POWER_STATUS_ON;
308                        }
309                        notifyAddressAllocated(devices, fromBootup);
310                    }
311                }
312            });
313        }
314    }
315
316    @ServiceThreadOnly
317    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices,
318            boolean fromBootup) {
319        assertRunOnServiceThread();
320        for (int i = 0; i < devices.size(); ++i) {
321            int address = devices.keyAt(i);
322            HdmiCecLocalDevice device = devices.valueAt(i);
323            device.handleAddressAllocated(address, fromBootup);
324        }
325    }
326
327    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
328    // keep them in one place.
329    @ServiceThreadOnly
330    private void initPortInfo() {
331        assertRunOnServiceThread();
332        HdmiPortInfo[] cecPortInfo = null;
333
334        // CEC HAL provides majority of the info while MHL does only MHL support flag for
335        // each port. Return empty array if CEC HAL didn't provide the info.
336        if (mCecController != null) {
337            cecPortInfo = mCecController.getPortInfos();
338        }
339        if (cecPortInfo == null) {
340            return;
341        }
342
343        for (HdmiPortInfo info : cecPortInfo) {
344            mPortIdMap.put(info.getAddress(), info.getId());
345            mPortInfoMap.put(info.getId(), info);
346        }
347
348        if (mMhlController == null) {
349            mPortInfo = Collections.unmodifiableList(Arrays.asList(cecPortInfo));
350            return;
351        } else {
352            HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
353            ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
354            for (HdmiPortInfo info : mhlPortInfo) {
355                if (info.isMhlSupported()) {
356                    mhlSupportedPorts.add(info.getId());
357                }
358            }
359
360            // Build HDMI port info list with CEC port info plus MHL supported flag.
361            ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
362            for (HdmiPortInfo info : cecPortInfo) {
363                if (mhlSupportedPorts.contains(info.getId())) {
364                    result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
365                            info.isCecSupported(), true, info.isArcSupported()));
366                } else {
367                    result.add(info);
368                }
369            }
370            mPortInfo = Collections.unmodifiableList(result);
371        }
372    }
373
374    /**
375     * Returns HDMI port information for the given port id.
376     *
377     * @param portId HDMI port id
378     * @return {@link HdmiPortInfo} for the given port
379     */
380    @ServiceThreadOnly
381    HdmiPortInfo getPortInfo(int portId) {
382        assertRunOnServiceThread();
383        return mPortInfoMap.get(portId, null);
384    }
385
386    /**
387     * Returns the routing path (physical address) of the HDMI port for the given
388     * port id.
389     */
390    @ServiceThreadOnly
391    int portIdToPath(int portId) {
392        assertRunOnServiceThread();
393        HdmiPortInfo portInfo = getPortInfo(portId);
394        if (portInfo == null) {
395            Slog.e(TAG, "Cannot find the port info: " + portId);
396            return Constants.INVALID_PHYSICAL_ADDRESS;
397        }
398        return portInfo.getAddress();
399    }
400
401    /**
402     * Returns the id of HDMI port located at the top of the hierarchy of
403     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
404     * the port id to be returned is the ID associated with the port address
405     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
406     */
407    @ServiceThreadOnly
408    int pathToPortId(int path) {
409        assertRunOnServiceThread();
410        int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
411        return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
412    }
413
414    @ServiceThreadOnly
415    boolean isValidPortId(int portId) {
416        assertRunOnServiceThread();
417        return getPortInfo(portId) != null;
418    }
419
420    /**
421     * Returns {@link Looper} for IO operation.
422     *
423     * <p>Declared as package-private.
424     */
425    Looper getIoLooper() {
426        return mIoThread.getLooper();
427    }
428
429    /**
430     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
431     * for tasks that are running on main service thread.
432     *
433     * <p>Declared as package-private.
434     */
435    Looper getServiceLooper() {
436        return mHandler.getLooper();
437    }
438
439    /**
440     * Returns physical address of the device.
441     */
442    int getPhysicalAddress() {
443        return mCecController.getPhysicalAddress();
444    }
445
446    /**
447     * Returns vendor id of CEC service.
448     */
449    int getVendorId() {
450        return mCecController.getVendorId();
451    }
452
453    @ServiceThreadOnly
454    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
455        assertRunOnServiceThread();
456        HdmiCecLocalDeviceTv tv = tv();
457        if (tv == null) {
458            return null;
459        }
460        return tv.getDeviceInfo(logicalAddress);
461    }
462
463    /**
464     * Returns version of CEC.
465     */
466    int getCecVersion() {
467        return mCecController.getVersion();
468    }
469
470    /**
471     * Whether a device of the specified physical address is connected to ARC enabled port.
472     */
473    @ServiceThreadOnly
474    boolean isConnectedToArcPort(int physicalAddress) {
475        assertRunOnServiceThread();
476        int portId = mPortIdMap.get(physicalAddress);
477        if (portId != Constants.INVALID_PORT_ID) {
478            return mPortInfoMap.get(portId).isArcSupported();
479        }
480        return false;
481    }
482
483    void runOnServiceThread(Runnable runnable) {
484        mHandler.post(runnable);
485    }
486
487    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
488        mHandler.postAtFrontOfQueue(runnable);
489    }
490
491    private void assertRunOnServiceThread() {
492        if (Looper.myLooper() != mHandler.getLooper()) {
493            throw new IllegalStateException("Should run on service thread.");
494        }
495    }
496
497    /**
498     * Transmit a CEC command to CEC bus.
499     *
500     * @param command CEC command to send out
501     * @param callback interface used to the result of send command
502     */
503    @ServiceThreadOnly
504    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
505        assertRunOnServiceThread();
506        mCecController.sendCommand(command, callback);
507    }
508
509    @ServiceThreadOnly
510    void sendCecCommand(HdmiCecMessage command) {
511        assertRunOnServiceThread();
512        mCecController.sendCommand(command, null);
513    }
514
515    @ServiceThreadOnly
516    boolean handleCecCommand(HdmiCecMessage message) {
517        assertRunOnServiceThread();
518        if (!mMessageValidator.isValid(message)) {
519            return false;
520        }
521        return dispatchMessageToLocalDevice(message);
522    }
523
524    void setAudioReturnChannel(boolean enabled) {
525        mCecController.setAudioReturnChannel(enabled);
526    }
527
528    @ServiceThreadOnly
529    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
530        assertRunOnServiceThread();
531        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
532            if (device.dispatchMessage(message)
533                    && message.getDestination() != Constants.ADDR_BROADCAST) {
534                return true;
535            }
536        }
537
538        if (message.getDestination() != Constants.ADDR_BROADCAST) {
539            Slog.w(TAG, "Unhandled cec command:" + message);
540        }
541        return false;
542    }
543
544    /**
545     * Called when a new hotplug event is issued.
546     *
547     * @param portNo hdmi port number where hot plug event issued.
548     * @param connected whether to be plugged in or not
549     */
550    @ServiceThreadOnly
551    void onHotplug(int portNo, boolean connected) {
552        assertRunOnServiceThread();
553        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
554            device.onHotplug(portNo, connected);
555        }
556        announceHotplugEvent(portNo, connected);
557    }
558
559    /**
560     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
561     * devices.
562     *
563     * @param callback an interface used to get a list of all remote devices' address
564     * @param sourceAddress a logical address of source device where sends polling message
565     * @param pickStrategy strategy how to pick polling candidates
566     * @param retryCount the number of retry used to send polling message to remote devices
567     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
568     */
569    @ServiceThreadOnly
570    void pollDevices(DevicePollingCallback callback, int sourceAddress, int pickStrategy,
571            int retryCount) {
572        assertRunOnServiceThread();
573        mCecController.pollDevices(callback, sourceAddress, checkPollStrategy(pickStrategy),
574                retryCount);
575    }
576
577    private int checkPollStrategy(int pickStrategy) {
578        int strategy = pickStrategy & Constants.POLL_STRATEGY_MASK;
579        if (strategy == 0) {
580            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
581        }
582        int iterationStrategy = pickStrategy & Constants.POLL_ITERATION_STRATEGY_MASK;
583        if (iterationStrategy == 0) {
584            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
585        }
586        return strategy | iterationStrategy;
587    }
588
589    List<HdmiCecLocalDevice> getAllLocalDevices() {
590        assertRunOnServiceThread();
591        return mCecController.getLocalDeviceList();
592    }
593
594    Object getServiceLock() {
595        return mLock;
596    }
597
598    void setAudioStatus(boolean mute, int volume) {
599        AudioManager audioManager = getAudioManager();
600        boolean muted = audioManager.isStreamMute(AudioManager.STREAM_MUSIC);
601        if (mute) {
602            if (!muted) {
603                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, true);
604            }
605        } else {
606            if (muted) {
607                audioManager.setStreamMute(AudioManager.STREAM_MUSIC, false);
608            }
609            // FLAG_HDMI_SYSTEM_AUDIO_VOLUME prevents audio manager from announcing
610            // volume change notification back to hdmi control service.
611            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume,
612                    AudioManager.FLAG_SHOW_UI |
613                    AudioManager.FLAG_HDMI_SYSTEM_AUDIO_VOLUME);
614        }
615    }
616
617    void announceSystemAudioModeChange(boolean enabled) {
618        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
619            invokeSystemAudioModeChange(listener, enabled);
620        }
621    }
622
623    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
624        // TODO: find better name instead of model name.
625        String displayName = Build.MODEL;
626        return new HdmiCecDeviceInfo(logicalAddress,
627                getPhysicalAddress(), pathToPortId(getPhysicalAddress()), deviceType,
628                getVendorId(), displayName);
629    }
630
631    // Record class that monitors the event of the caller of being killed. Used to clean up
632    // the listener list and record list accordingly.
633    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
634        private final IHdmiHotplugEventListener mListener;
635
636        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
637            mListener = listener;
638        }
639
640        @Override
641        public void binderDied() {
642            synchronized (mLock) {
643                mHotplugEventListenerRecords.remove(this);
644                mHotplugEventListeners.remove(mListener);
645            }
646        }
647    }
648
649    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
650        private final IHdmiDeviceEventListener mListener;
651
652        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
653            mListener = listener;
654        }
655
656        @Override
657        public void binderDied() {
658            synchronized (mLock) {
659                mDeviceEventListenerRecords.remove(this);
660                mDeviceEventListeners.remove(mListener);
661            }
662        }
663    }
664
665    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
666        private final IHdmiSystemAudioModeChangeListener mListener;
667
668        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
669            mListener = listener;
670        }
671
672        @Override
673        public void binderDied() {
674            synchronized (mLock) {
675                mSystemAudioModeChangeListenerRecords.remove(this);
676                mSystemAudioModeChangeListeners.remove(mListener);
677            }
678        }
679    }
680
681    class VendorCommandListenerRecord implements IBinder.DeathRecipient {
682        private final IHdmiVendorCommandListener mListener;
683        private final int mDeviceType;
684
685        public VendorCommandListenerRecord(IHdmiVendorCommandListener listener, int deviceType) {
686            mListener = listener;
687            mDeviceType = deviceType;
688        }
689
690        @Override
691        public void binderDied() {
692            synchronized (mLock) {
693                mVendorCommandListenerRecords.remove(this);
694            }
695        }
696    }
697
698    private class HdmiRecordListenerRecord implements IBinder.DeathRecipient {
699        @Override
700        public void binderDied() {
701            synchronized (mLock) {
702                mRecordListener = null;
703            }
704        }
705    }
706
707    private void enforceAccessPermission() {
708        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
709    }
710
711    private final class BinderService extends IHdmiControlService.Stub {
712        @Override
713        public int[] getSupportedTypes() {
714            enforceAccessPermission();
715            // mLocalDevices is an unmodifiable list - no lock necesary.
716            int[] localDevices = new int[mLocalDevices.size()];
717            for (int i = 0; i < localDevices.length; ++i) {
718                localDevices[i] = mLocalDevices.get(i);
719            }
720            return localDevices;
721        }
722
723        @Override
724        public HdmiCecDeviceInfo getActiveSource() {
725            HdmiCecLocalDeviceTv tv = tv();
726            if (tv == null) {
727                Slog.w(TAG, "Local tv device not available");
728                return null;
729            }
730            ActiveSource activeSource = tv.getActiveSource();
731            if (activeSource.isValid()) {
732                return new HdmiCecDeviceInfo(activeSource.logicalAddress,
733                        activeSource.physicalAddress, HdmiCecDeviceInfo.PORT_INVALID,
734                        HdmiCecDeviceInfo.DEVICE_INACTIVE, 0, "");
735            }
736            int activePath = tv.getActivePath();
737            if (activePath != HdmiCecDeviceInfo.PATH_INVALID) {
738                return new HdmiCecDeviceInfo(activePath, tv.getActivePortId());
739            }
740            return null;
741        }
742
743        @Override
744        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
745            enforceAccessPermission();
746            runOnServiceThread(new Runnable() {
747                @Override
748                public void run() {
749                    if (callback == null) {
750                        Slog.e(TAG, "Callback cannot be null");
751                        return;
752                    }
753                    HdmiCecLocalDeviceTv tv = tv();
754                    if (tv == null) {
755                        Slog.w(TAG, "Local tv device not available");
756                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
757                        return;
758                    }
759                    tv.deviceSelect(logicalAddress, callback);
760                }
761            });
762        }
763
764        @Override
765        public void portSelect(final int portId, final IHdmiControlCallback callback) {
766            enforceAccessPermission();
767            runOnServiceThread(new Runnable() {
768                @Override
769                public void run() {
770                    if (callback == null) {
771                        Slog.e(TAG, "Callback cannot be null");
772                        return;
773                    }
774                    HdmiCecLocalDeviceTv tv = tv();
775                    if (tv == null) {
776                        Slog.w(TAG, "Local tv device not available");
777                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
778                        return;
779                    }
780                    tv.doManualPortSwitching(portId, callback);
781                }
782            });
783        }
784
785        @Override
786        public void sendKeyEvent(final int deviceType, final int keyCode, final boolean isPressed) {
787            enforceAccessPermission();
788            runOnServiceThread(new Runnable() {
789                @Override
790                public void run() {
791                    HdmiCecLocalDevice localDevice = mCecController.getLocalDevice(deviceType);
792                    if (localDevice == null) {
793                        Slog.w(TAG, "Local device not available");
794                        return;
795                    }
796                    localDevice.sendKeyEvent(keyCode, isPressed);
797                }
798            });
799        }
800
801        @Override
802        public void oneTouchPlay(final IHdmiControlCallback callback) {
803            enforceAccessPermission();
804            runOnServiceThread(new Runnable() {
805                @Override
806                public void run() {
807                    HdmiControlService.this.oneTouchPlay(callback);
808                }
809            });
810        }
811
812        @Override
813        public void queryDisplayStatus(final IHdmiControlCallback callback) {
814            enforceAccessPermission();
815            runOnServiceThread(new Runnable() {
816                @Override
817                public void run() {
818                    HdmiControlService.this.queryDisplayStatus(callback);
819                }
820            });
821        }
822
823        @Override
824        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
825            enforceAccessPermission();
826            runOnServiceThread(new Runnable() {
827                @Override
828                public void run() {
829                    HdmiControlService.this.addHotplugEventListener(listener);
830                }
831            });
832        }
833
834        @Override
835        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
836            enforceAccessPermission();
837            runOnServiceThread(new Runnable() {
838                @Override
839                public void run() {
840                    HdmiControlService.this.removeHotplugEventListener(listener);
841                }
842            });
843        }
844
845        @Override
846        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
847            enforceAccessPermission();
848            runOnServiceThread(new Runnable() {
849                @Override
850                public void run() {
851                    HdmiControlService.this.addDeviceEventListener(listener);
852                }
853            });
854        }
855
856        @Override
857        public List<HdmiPortInfo> getPortInfo() {
858            enforceAccessPermission();
859            return mPortInfo;
860        }
861
862        @Override
863        public boolean canChangeSystemAudioMode() {
864            enforceAccessPermission();
865            HdmiCecLocalDeviceTv tv = tv();
866            if (tv == null) {
867                return false;
868            }
869            return tv.hasSystemAudioDevice();
870        }
871
872        @Override
873        public boolean getSystemAudioMode() {
874            enforceAccessPermission();
875            HdmiCecLocalDeviceTv tv = tv();
876            if (tv == null) {
877                return false;
878            }
879            return tv.isSystemAudioActivated();
880        }
881
882        @Override
883        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
884            enforceAccessPermission();
885            runOnServiceThread(new Runnable() {
886                @Override
887                public void run() {
888                    HdmiCecLocalDeviceTv tv = tv();
889                    if (tv == null) {
890                        Slog.w(TAG, "Local tv device not available");
891                        invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
892                        return;
893                    }
894                    tv.changeSystemAudioMode(enabled, callback);
895                }
896            });
897        }
898
899        @Override
900        public void addSystemAudioModeChangeListener(
901                final IHdmiSystemAudioModeChangeListener listener) {
902            enforceAccessPermission();
903            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
904        }
905
906        @Override
907        public void removeSystemAudioModeChangeListener(
908                final IHdmiSystemAudioModeChangeListener listener) {
909            enforceAccessPermission();
910            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
911        }
912
913        @Override
914        public void setInputChangeListener(final IHdmiInputChangeListener listener) {
915            enforceAccessPermission();
916            HdmiControlService.this.setInputChangeListener(listener);
917        }
918
919        @Override
920        public List<HdmiCecDeviceInfo> getInputDevices() {
921            enforceAccessPermission();
922            // No need to hold the lock for obtaining TV device as the local device instance
923            // is preserved while the HDMI control is enabled.
924            HdmiCecLocalDeviceTv tv = tv();
925            if (tv == null) {
926                return Collections.emptyList();
927            }
928            return tv.getSafeExternalInputs();
929        }
930
931        @Override
932        public void setControlEnabled(final boolean enabled) {
933            enforceAccessPermission();
934            runOnServiceThread(new Runnable() {
935                @Override
936                public void run() {
937                    handleHdmiControlStatusChanged(enabled);
938
939                }
940            });
941        }
942
943        @Override
944        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
945                final int maxIndex) {
946            enforceAccessPermission();
947            runOnServiceThread(new Runnable() {
948                @Override
949                public void run() {
950                    HdmiCecLocalDeviceTv tv = tv();
951                    if (tv == null) {
952                        Slog.w(TAG, "Local tv device not available");
953                        return;
954                    }
955                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
956                }
957            });
958        }
959
960        @Override
961        public void setSystemAudioMute(final boolean mute) {
962            enforceAccessPermission();
963            runOnServiceThread(new Runnable() {
964                @Override
965                public void run() {
966                    HdmiCecLocalDeviceTv tv = tv();
967                    if (tv == null) {
968                        Slog.w(TAG, "Local tv device not available");
969                        return;
970                    }
971                    tv.changeMute(mute);
972                }
973            });
974        }
975
976        @Override
977        public void setArcMode(final boolean enabled) {
978            enforceAccessPermission();
979            runOnServiceThread(new Runnable() {
980                @Override
981                public void run() {
982                    HdmiCecLocalDeviceTv tv = tv();
983                    if (tv == null) {
984                        Slog.w(TAG, "Local tv device not available to change arc mode.");
985                        return;
986                    }
987                }
988            });
989        }
990
991        @Override
992        public void setOption(final int key, final int value) {
993            enforceAccessPermission();
994            if (!isTvDevice()) {
995                return;
996            }
997            switch (key) {
998                case HdmiTvClient.OPTION_CEC_AUTO_WAKEUP:
999                    mCecController.setOption(key, value);
1000                    break;
1001                case HdmiTvClient.OPTION_CEC_AUTO_DEVICE_OFF:
1002                    // No need to pass this option to HAL.
1003                    tv().setAutoDeviceOff(value == HdmiTvClient.ENABLED);
1004                    break;
1005                case HdmiTvClient.OPTION_MHL_INPUT_SWITCHING:  // Fall through
1006                case HdmiTvClient.OPTION_MHL_POWER_CHARGE:
1007                    if (mMhlController != null) {
1008                        mMhlController.setOption(key, value);
1009                    }
1010                    break;
1011            }
1012        }
1013
1014        @Override
1015        public void setProhibitMode(final boolean enabled) {
1016            enforceAccessPermission();
1017            if (!isTvDevice()) {
1018                return;
1019            }
1020            HdmiControlService.this.setProhibitMode(enabled);
1021        }
1022
1023        @Override
1024        public void addVendorCommandListener(final IHdmiVendorCommandListener listener,
1025                final int deviceType) {
1026            enforceAccessPermission();
1027            runOnServiceThread(new Runnable() {
1028                @Override
1029                public void run() {
1030                    HdmiControlService.this.addVendorCommandListener(listener, deviceType);
1031                }
1032            });
1033        }
1034
1035        @Override
1036        public void sendVendorCommand(final int deviceType, final int targetAddress,
1037                final byte[] params, final boolean hasVendorId) {
1038            enforceAccessPermission();
1039            runOnServiceThread(new Runnable() {
1040                @Override
1041                public void run() {
1042                    HdmiCecLocalDevice device = mCecController.getLocalDevice(deviceType);
1043                    if (device == null) {
1044                        Slog.w(TAG, "Local device not available");
1045                        return;
1046                    }
1047                    if (hasVendorId) {
1048                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommandWithId(
1049                                device.getDeviceInfo().getLogicalAddress(), targetAddress,
1050                                getVendorId(), params));
1051                    } else {
1052                        sendCecCommand(HdmiCecMessageBuilder.buildVendorCommand(
1053                                device.getDeviceInfo().getLogicalAddress(), targetAddress, params));
1054                    }
1055                }
1056            });
1057        }
1058
1059        @Override
1060        public void setHdmiRecordListener(IHdmiRecordListener listener) {
1061            HdmiControlService.this.setHdmiRecordListener(listener);
1062        }
1063
1064        @Override
1065        public void startOneTouchRecord(final int recorderAddress, final byte[] recordSource) {
1066            runOnServiceThread(new Runnable() {
1067                @Override
1068                public void run() {
1069                    if (!isTvDevice()) {
1070                        Slog.w(TAG, "No TV is available.");
1071                        return;
1072                    }
1073                    tv().startOneTouchRecord(recorderAddress, recordSource);
1074                }
1075            });
1076        }
1077
1078        @Override
1079        public void stopOneTouchRecord(final int recorderAddress) {
1080            runOnServiceThread(new Runnable() {
1081                @Override
1082                public void run() {
1083                    if (!isTvDevice()) {
1084                        Slog.w(TAG, "No TV is available.");
1085                        return;
1086                    }
1087                    tv().stopOneTouchRecord(recorderAddress);
1088                }
1089            });
1090        }
1091
1092        @Override
1093        public void startTimerRecording(final int recorderAddress, final int sourceType,
1094                final byte[] recordSource) {
1095            runOnServiceThread(new Runnable() {
1096                @Override
1097                public void run() {
1098                    if (!isTvDevice()) {
1099                        Slog.w(TAG, "No TV is available.");
1100                        return;
1101                    }
1102                    tv().startTimerRecording(recorderAddress, sourceType, recordSource);
1103                }
1104            });
1105        }
1106
1107        @Override
1108        public void clearTimerRecording(final int recorderAddress, final int sourceType,
1109                final byte[] recordSource) {
1110            runOnServiceThread(new Runnable() {
1111                @Override
1112                public void run() {
1113                    if (!isTvDevice()) {
1114                        Slog.w(TAG, "No TV is available.");
1115                        return;
1116                    }
1117                    tv().clearTimerRecording(recorderAddress, sourceType, recordSource);
1118                }
1119            });
1120        }
1121    }
1122
1123    @ServiceThreadOnly
1124    private void oneTouchPlay(final IHdmiControlCallback callback) {
1125        assertRunOnServiceThread();
1126        HdmiCecLocalDevicePlayback source = playback();
1127        if (source == null) {
1128            Slog.w(TAG, "Local playback device not available");
1129            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1130            return;
1131        }
1132        source.oneTouchPlay(callback);
1133    }
1134
1135    @ServiceThreadOnly
1136    private void queryDisplayStatus(final IHdmiControlCallback callback) {
1137        assertRunOnServiceThread();
1138        HdmiCecLocalDevicePlayback source = playback();
1139        if (source == null) {
1140            Slog.w(TAG, "Local playback device not available");
1141            invokeCallback(callback, HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE);
1142            return;
1143        }
1144        source.queryDisplayStatus(callback);
1145    }
1146
1147    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
1148        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
1149        try {
1150            listener.asBinder().linkToDeath(record, 0);
1151        } catch (RemoteException e) {
1152            Slog.w(TAG, "Listener already died");
1153            return;
1154        }
1155        synchronized (mLock) {
1156            mHotplugEventListenerRecords.add(record);
1157            mHotplugEventListeners.add(listener);
1158        }
1159    }
1160
1161    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
1162        synchronized (mLock) {
1163            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
1164                if (record.mListener.asBinder() == listener.asBinder()) {
1165                    listener.asBinder().unlinkToDeath(record, 0);
1166                    mHotplugEventListenerRecords.remove(record);
1167                    break;
1168                }
1169            }
1170            mHotplugEventListeners.remove(listener);
1171        }
1172    }
1173
1174    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
1175        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
1176        try {
1177            listener.asBinder().linkToDeath(record, 0);
1178        } catch (RemoteException e) {
1179            Slog.w(TAG, "Listener already died");
1180            return;
1181        }
1182        synchronized (mLock) {
1183            mDeviceEventListeners.add(listener);
1184            mDeviceEventListenerRecords.add(record);
1185        }
1186    }
1187
1188    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
1189        synchronized (mLock) {
1190            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
1191                try {
1192                    listener.onStatusChanged(device, activated);
1193                } catch (RemoteException e) {
1194                    Slog.e(TAG, "Failed to report device event:" + e);
1195                }
1196            }
1197        }
1198    }
1199
1200    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
1201        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
1202                listener);
1203        try {
1204            listener.asBinder().linkToDeath(record, 0);
1205        } catch (RemoteException e) {
1206            Slog.w(TAG, "Listener already died");
1207            return;
1208        }
1209        synchronized (mLock) {
1210            mSystemAudioModeChangeListeners.add(listener);
1211            mSystemAudioModeChangeListenerRecords.add(record);
1212        }
1213    }
1214
1215    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
1216        synchronized (mLock) {
1217            for (SystemAudioModeChangeListenerRecord record :
1218                    mSystemAudioModeChangeListenerRecords) {
1219                if (record.mListener.asBinder() == listener) {
1220                    listener.asBinder().unlinkToDeath(record, 0);
1221                    mSystemAudioModeChangeListenerRecords.remove(record);
1222                    break;
1223                }
1224            }
1225            mSystemAudioModeChangeListeners.remove(listener);
1226        }
1227    }
1228
1229    private final class InputChangeListenerRecord implements IBinder.DeathRecipient {
1230        @Override
1231        public void binderDied() {
1232            synchronized (mLock) {
1233                mInputChangeListener = null;
1234            }
1235        }
1236    }
1237
1238    private void setInputChangeListener(IHdmiInputChangeListener listener) {
1239        synchronized (mLock) {
1240            mInputChangeListenerRecord = new InputChangeListenerRecord();
1241            try {
1242                listener.asBinder().linkToDeath(mInputChangeListenerRecord, 0);
1243            } catch (RemoteException e) {
1244                Slog.w(TAG, "Listener already died");
1245                return;
1246            }
1247            mInputChangeListener = listener;
1248        }
1249    }
1250
1251    void invokeInputChangeListener(HdmiCecDeviceInfo info) {
1252        synchronized (mLock) {
1253            if (mInputChangeListener != null) {
1254                try {
1255                    mInputChangeListener.onChanged(info);
1256                } catch (RemoteException e) {
1257                    Slog.w(TAG, "Exception thrown by IHdmiInputChangeListener: " + e);
1258                }
1259            }
1260        }
1261    }
1262
1263    private void setHdmiRecordListener(IHdmiRecordListener listener) {
1264        synchronized (mLock) {
1265            mRecordListenerRecord = new HdmiRecordListenerRecord();
1266            try {
1267                listener.asBinder().linkToDeath(mRecordListenerRecord, 0);
1268            } catch (RemoteException e) {
1269                Slog.w(TAG, "Listener already died.", e);
1270            }
1271            mRecordListener = listener;
1272        }
1273    }
1274
1275    byte[] invokeRecordRequestListener(int recorderAddress) {
1276        synchronized (mLock) {
1277            if (mRecordListener != null) {
1278                try {
1279                    return mRecordListener.getOneTouchRecordSource(recorderAddress);
1280                } catch (RemoteException e) {
1281                    Slog.w(TAG, "Failed to start record.", e);
1282                }
1283            }
1284            return EmptyArray.BYTE;
1285        }
1286    }
1287
1288    void invokeOneTouchRecordResult(int result) {
1289        synchronized (mLock) {
1290            if (mRecordListener != null) {
1291                try {
1292                    mRecordListener.onOneTouchRecordResult(result);
1293                } catch (RemoteException e) {
1294                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1295                }
1296            }
1297        }
1298    }
1299
1300    void invokeTimerRecordingResult(int result) {
1301        synchronized (mLock) {
1302            if (mRecordListener != null) {
1303                try {
1304                    mRecordListener.onTimerRecordingResult(result);
1305                } catch (RemoteException e) {
1306                    Slog.w(TAG, "Failed to call onOneTouchRecordResult.", e);
1307                }
1308            }
1309        }
1310    }
1311
1312    private void invokeCallback(IHdmiControlCallback callback, int result) {
1313        try {
1314            callback.onComplete(result);
1315        } catch (RemoteException e) {
1316            Slog.e(TAG, "Invoking callback failed:" + e);
1317        }
1318    }
1319
1320    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
1321            boolean enabled) {
1322        try {
1323            listener.onStatusChanged(enabled);
1324        } catch (RemoteException e) {
1325            Slog.e(TAG, "Invoking callback failed:" + e);
1326        }
1327    }
1328
1329    private void announceHotplugEvent(int portId, boolean connected) {
1330        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1331        synchronized (mLock) {
1332            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1333                invokeHotplugEventListenerLocked(listener, event);
1334            }
1335        }
1336    }
1337
1338    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1339            HdmiHotplugEvent event) {
1340        try {
1341            listener.onReceived(event);
1342        } catch (RemoteException e) {
1343            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1344        }
1345    }
1346
1347    private static boolean hasSameTopPort(int path1, int path2) {
1348        return (path1 & Constants.ROUTING_PATH_TOP_MASK)
1349                == (path2 & Constants.ROUTING_PATH_TOP_MASK);
1350    }
1351
1352    private HdmiCecLocalDeviceTv tv() {
1353        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_TV);
1354    }
1355
1356    boolean isTvDevice() {
1357        return tv() != null;
1358    }
1359
1360    private HdmiCecLocalDevicePlayback playback() {
1361        return (HdmiCecLocalDevicePlayback)
1362                mCecController.getLocalDevice(HdmiCecDeviceInfo.DEVICE_PLAYBACK);
1363    }
1364
1365    AudioManager getAudioManager() {
1366        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1367    }
1368
1369    boolean isControlEnabled() {
1370        synchronized (mLock) {
1371            return mHdmiControlEnabled;
1372        }
1373    }
1374
1375    int getPowerStatus() {
1376        return mPowerStatus;
1377    }
1378
1379    boolean isPowerOnOrTransient() {
1380        return mPowerStatus == HdmiControlManager.POWER_STATUS_ON
1381                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1382    }
1383
1384    boolean isPowerStandbyOrTransient() {
1385        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY
1386                || mPowerStatus == HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1387    }
1388
1389    boolean isPowerStandby() {
1390        return mPowerStatus == HdmiControlManager.POWER_STATUS_STANDBY;
1391    }
1392
1393    @ServiceThreadOnly
1394    void wakeUp() {
1395        assertRunOnServiceThread();
1396        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1397        pm.wakeUp(SystemClock.uptimeMillis());
1398        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1399        // the intent, the sequence will continue at onWakeUp().
1400    }
1401
1402    @ServiceThreadOnly
1403    void standby() {
1404        assertRunOnServiceThread();
1405        mStandbyMessageReceived = true;
1406        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1407        pm.goToSleep(SystemClock.uptimeMillis());
1408        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1409        // the intent, the sequence will continue at onStandby().
1410    }
1411
1412    @ServiceThreadOnly
1413    private void onWakeUp() {
1414        assertRunOnServiceThread();
1415        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON;
1416        if (mCecController != null) {
1417            if (mHdmiControlEnabled) {
1418                initializeCec(true);
1419            }
1420        } else {
1421            Slog.i(TAG, "Device does not support HDMI-CEC.");
1422        }
1423        // TODO: Initialize MHL local devices.
1424    }
1425
1426    @ServiceThreadOnly
1427    private void onStandby() {
1428        assertRunOnServiceThread();
1429        mPowerStatus = HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY;
1430
1431        final List<HdmiCecLocalDevice> devices = getAllLocalDevices();
1432        disableDevices(new PendingActionClearedCallback() {
1433            @Override
1434            public void onCleared(HdmiCecLocalDevice device) {
1435                Slog.v(TAG, "On standby-action cleared:" + device.mDeviceType);
1436                devices.remove(device);
1437                if (devices.isEmpty()) {
1438                    onStandbyCompleted();
1439                    // We will not clear local devices here, since some OEM/SOC will keep passing
1440                    // the received packets until the application processor enters to the sleep
1441                    // actually.
1442                }
1443            }
1444        });
1445    }
1446
1447    private void disableDevices(PendingActionClearedCallback callback) {
1448        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1449            device.disableDevice(mStandbyMessageReceived, callback);
1450        }
1451    }
1452
1453    @ServiceThreadOnly
1454    private void clearLocalDevices() {
1455        assertRunOnServiceThread();
1456        if (mCecController == null) {
1457            return;
1458        }
1459        mCecController.clearLogicalAddress();
1460        mCecController.clearLocalDevices();
1461    }
1462
1463    @ServiceThreadOnly
1464    private void onStandbyCompleted() {
1465        assertRunOnServiceThread();
1466        Slog.v(TAG, "onStandbyCompleted");
1467
1468        if (mPowerStatus != HdmiControlManager.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1469            return;
1470        }
1471        mPowerStatus = HdmiControlManager.POWER_STATUS_STANDBY;
1472        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1473            device.onStandby(mStandbyMessageReceived);
1474        }
1475        mStandbyMessageReceived = false;
1476        mCecController.setOption(HdmiTvClient.OPTION_CEC_SERVICE_CONTROL, HdmiTvClient.DISABLED);
1477    }
1478
1479    private void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {
1480        VendorCommandListenerRecord record = new VendorCommandListenerRecord(listener, deviceType);
1481        try {
1482            listener.asBinder().linkToDeath(record, 0);
1483        } catch (RemoteException e) {
1484            Slog.w(TAG, "Listener already died");
1485            return;
1486        }
1487        synchronized (mLock) {
1488            mVendorCommandListenerRecords.add(record);
1489        }
1490    }
1491
1492    void invokeVendorCommandListeners(int deviceType, int srcAddress, byte[] params,
1493            boolean hasVendorId) {
1494        synchronized (mLock) {
1495            for (VendorCommandListenerRecord record : mVendorCommandListenerRecords) {
1496                if (record.mDeviceType != deviceType) {
1497                    continue;
1498                }
1499                try {
1500                    record.mListener.onReceived(srcAddress, params, hasVendorId);
1501                } catch (RemoteException e) {
1502                    Slog.e(TAG, "Failed to notify vendor command reception", e);
1503                }
1504            }
1505        }
1506    }
1507
1508    boolean isProhibitMode() {
1509        synchronized (mLock) {
1510            return mProhibitMode;
1511        }
1512    }
1513
1514    void setProhibitMode(boolean enabled) {
1515        synchronized (mLock) {
1516            mProhibitMode = enabled;
1517        }
1518    }
1519
1520    @ServiceThreadOnly
1521    private void handleHdmiControlStatusChanged(boolean enabled) {
1522        assertRunOnServiceThread();
1523
1524        int value = enabled ? HdmiTvClient.ENABLED : HdmiTvClient.DISABLED;
1525        mCecController.setOption(HdmiTvClient.OPTION_CEC_ENABLE, value);
1526        if (mMhlController != null) {
1527            mMhlController.setOption(HdmiTvClient.OPTION_MHL_ENABLE, value);
1528        }
1529
1530        synchronized (mLock) {
1531            mHdmiControlEnabled = enabled;
1532        }
1533
1534        if (enabled) {
1535            initializeCec(false);
1536        } else {
1537            disableDevices(new PendingActionClearedCallback() {
1538                @Override
1539                public void onCleared(HdmiCecLocalDevice device) {
1540                    assertRunOnServiceThread();
1541                    clearLocalDevices();
1542                }
1543            });
1544        }
1545    }
1546}
1547