HdmiControlService.java revision 38db629d897e9d7c8e31ce0a7e985981e3e12996
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.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.hardware.hdmi.HdmiCec;
25import android.hardware.hdmi.HdmiCecDeviceInfo;
26import android.hardware.hdmi.HdmiCecMessage;
27import android.hardware.hdmi.HdmiHotplugEvent;
28import android.hardware.hdmi.HdmiPortInfo;
29import android.hardware.hdmi.IHdmiControlCallback;
30import android.hardware.hdmi.IHdmiControlService;
31import android.hardware.hdmi.IHdmiDeviceEventListener;
32import android.hardware.hdmi.IHdmiHotplugEventListener;
33import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
34import android.media.AudioManager;
35import android.os.Build;
36import android.os.Handler;
37import android.os.HandlerThread;
38import android.os.IBinder;
39import android.os.Looper;
40import android.os.PowerManager;
41import android.os.RemoteException;
42import android.os.SystemClock;
43import android.util.Slog;
44import android.util.SparseArray;
45import android.util.SparseIntArray;
46
47import com.android.internal.annotations.GuardedBy;
48import com.android.server.SystemService;
49import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
50import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
51
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.List;
55
56/**
57 * Provides a service for sending and processing HDMI control messages,
58 * HDMI-CEC and MHL control command, and providing the information on both standard.
59 */
60public final class HdmiControlService extends SystemService {
61    private static final String TAG = "HdmiControlService";
62
63    // TODO: Rename the permission to HDMI_CONTROL.
64    private static final String PERMISSION = "android.permission.HDMI_CEC";
65
66    /**
67     * Interface to report send result.
68     */
69    interface SendMessageCallback {
70        /**
71         * Called when {@link HdmiControlService#sendCecCommand} is completed.
72         *
73         * @param error result of send request.
74         * @see {@link #SEND_RESULT_SUCCESS}
75         * @see {@link #SEND_RESULT_NAK}
76         * @see {@link #SEND_RESULT_FAILURE}
77         */
78        void onSendCompleted(int error);
79    }
80
81    /**
82     * Interface to get a list of available logical devices.
83     */
84    interface DevicePollingCallback {
85        /**
86         * Called when device polling is finished.
87         *
88         * @param ackedAddress a list of logical addresses of available devices
89         */
90        void onPollingFinished(List<Integer> ackedAddress);
91    }
92
93    private class PowerStateReceiver extends BroadcastReceiver {
94        @Override
95        public void onReceive(Context context, Intent intent) {
96            switch (intent.getAction()) {
97                case Intent.ACTION_SCREEN_OFF:
98                    if (isPowerOnOrTransient()) {
99                        onStandby();
100                    }
101                    break;
102                case Intent.ACTION_SCREEN_ON:
103                    if (isPowerStandbyOrTransient()) {
104                        onWakeUp();
105                    }
106                    break;
107            }
108        }
109    }
110
111    // A thread to handle synchronous IO of CEC and MHL control service.
112    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
113    // and sparse call it shares a thread to handle IO operations.
114    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
115
116    // Used to synchronize the access to the service.
117    private final Object mLock = new Object();
118
119    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
120    private final List<Integer> mLocalDevices;
121
122    // List of listeners registered by callers that want to get notified of
123    // hotplug events.
124    @GuardedBy("mLock")
125    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
126
127    // List of records for hotplug event listener to handle the the caller killed in action.
128    @GuardedBy("mLock")
129    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
130            new ArrayList<>();
131
132    // List of listeners registered by callers that want to get notified of
133    // device status events.
134    @GuardedBy("mLock")
135    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
136
137    // List of records for device event listener to handle the the caller killed in action.
138    @GuardedBy("mLock")
139    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
140            new ArrayList<>();
141
142    // Set to true while HDMI control is enabled. If set to false, HDMI-CEC/MHL protocol
143    // handling will be disabled and no request will be handled.
144    @GuardedBy("mLock")
145    private boolean mHdmiControlEnabled;
146
147    // List of listeners registered by callers that want to get notified of
148    // system audio mode changes.
149    private final ArrayList<IHdmiSystemAudioModeChangeListener>
150            mSystemAudioModeChangeListeners = new ArrayList<>();
151    // List of records for system audio mode change to handle the the caller killed in action.
152    private final ArrayList<SystemAudioModeChangeListenerRecord>
153            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
154
155    // Handler used to run a task in service thread.
156    private final Handler mHandler = new Handler();
157
158    @Nullable
159    private HdmiCecController mCecController;
160
161    @Nullable
162    private HdmiMhlController mMhlController;
163
164    // HDMI port information. Stored in the unmodifiable list to keep the static information
165    // from being modified.
166    private List<HdmiPortInfo> mPortInfo;
167
168    private final PowerStateReceiver mPowerStateReceiver = new PowerStateReceiver();
169
170    @ServiceThreadOnly
171    private int mPowerStatus = HdmiCec.POWER_STATUS_STANDBY;
172
173    @ServiceThreadOnly
174    private boolean mStandbyMessageReceived = false;
175
176    public HdmiControlService(Context context) {
177        super(context);
178        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
179                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
180    }
181
182    @Override
183    public void onStart() {
184        mIoThread.start();
185        mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
186        mCecController = HdmiCecController.create(this);
187
188        if (mCecController != null) {
189            mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED);
190            initializeLocalDevices(mLocalDevices);
191        } else {
192            Slog.i(TAG, "Device does not support HDMI-CEC.");
193        }
194
195        mMhlController = HdmiMhlController.create(this);
196        if (mMhlController == null) {
197            Slog.i(TAG, "Device does not support MHL-control.");
198        }
199        mPortInfo = initPortInfo();
200        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
201
202        // Register broadcast receiver for power state change.
203        if (mCecController != null || mMhlController != null) {
204            IntentFilter filter = new IntentFilter();
205            filter.addAction(Intent.ACTION_SCREEN_OFF);
206            filter.addAction(Intent.ACTION_SCREEN_ON);
207            getContext().registerReceiver(mPowerStateReceiver, filter);
208        }
209
210        // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
211        // start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
212        mHdmiControlEnabled = true;
213    }
214
215    @ServiceThreadOnly
216    private void initializeLocalDevices(final List<Integer> deviceTypes) {
217        assertRunOnServiceThread();
218        // A container for [Logical Address, Local device info].
219        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
220        final SparseIntArray finished = new SparseIntArray();
221        mCecController.clearLogicalAddress();
222        for (int type : deviceTypes) {
223            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
224            localDevice.init();
225            mCecController.allocateLogicalAddress(type,
226                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
227                @Override
228                public void onAllocated(int deviceType, int logicalAddress) {
229                    if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) {
230                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
231                    } else {
232                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
233                        localDevice.setDeviceInfo(deviceInfo);
234                        mCecController.addLocalDevice(deviceType, localDevice);
235                        mCecController.addLogicalAddress(logicalAddress);
236                        devices.append(logicalAddress, localDevice);
237                    }
238                    finished.append(deviceType, logicalAddress);
239
240                    // Address allocation completed for all devices. Notify each device.
241                    if (deviceTypes.size() == finished.size()) {
242                        if (mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON) {
243                            mPowerStatus = HdmiCec.POWER_STATUS_ON;
244                        }
245                        notifyAddressAllocated(devices);
246                    }
247                }
248            });
249        }
250    }
251
252    @ServiceThreadOnly
253    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
254        assertRunOnServiceThread();
255        for (int i = 0; i < devices.size(); ++i) {
256            int address = devices.keyAt(i);
257            HdmiCecLocalDevice device = devices.valueAt(i);
258            device.handleAddressAllocated(address);
259        }
260    }
261
262    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
263    // keep them in one place.
264    @ServiceThreadOnly
265    private List<HdmiPortInfo> initPortInfo() {
266        assertRunOnServiceThread();
267        HdmiPortInfo[] cecPortInfo = null;
268
269        // CEC HAL provides majority of the info while MHL does only MHL support flag for
270        // each port. Return empty array if CEC HAL didn't provide the info.
271        if (mCecController != null) {
272            cecPortInfo = mCecController.getPortInfos();
273        }
274        if (cecPortInfo == null) {
275            return Collections.emptyList();
276        }
277
278        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
279        if (mMhlController != null) {
280            // TODO: Implement plumbing logic to get MHL port information.
281            // mhlPortInfo = mMhlController.getPortInfos();
282        }
283
284        // Use the id (port number) to find the matched info between CEC and MHL to combine them
285        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
286        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
287        for (int i = 0; i < cecPortInfo.length; ++i) {
288            HdmiPortInfo cec = cecPortInfo[i];
289            int id = cec.getId();
290            boolean mhlInfoFound = false;
291            for (HdmiPortInfo mhl : mhlPortInfo) {
292                if (id == mhl.getId()) {
293                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
294                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
295                    mhlInfoFound = true;
296                    break;
297                }
298            }
299            if (!mhlInfoFound) {
300                result.add(cec);
301            }
302        }
303
304        return Collections.unmodifiableList(result);
305    }
306
307    /**
308     * Returns HDMI port information for the given port id.
309     *
310     * @param portId HDMI port id
311     * @return {@link HdmiPortInfo} for the given port
312     */
313    HdmiPortInfo getPortInfo(int portId) {
314        // mPortInfo is an unmodifiable list and the only reference to its inner list.
315        // No lock is necessary.
316        for (HdmiPortInfo info : mPortInfo) {
317            if (portId == info.getId()) {
318                return info;
319            }
320        }
321        return null;
322    }
323
324    /**
325     * Returns the routing path (physical address) of the HDMI port for the given
326     * port id.
327     */
328    int portIdToPath(int portId) {
329        HdmiPortInfo portInfo = getPortInfo(portId);
330        if (portInfo == null) {
331            Slog.e(TAG, "Cannot find the port info: " + portId);
332            return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
333        }
334        return portInfo.getAddress();
335    }
336
337    /**
338     * Returns the id of HDMI port located at the top of the hierarchy of
339     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
340     * the port id to be returned is the ID associated with the port address
341     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
342     */
343    int pathToPortId(int path) {
344        int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK;
345        for (HdmiPortInfo info : mPortInfo) {
346            if (portAddress == info.getAddress()) {
347                return info.getId();
348            }
349        }
350        return HdmiConstants.INVALID_PORT_ID;
351    }
352
353    /**
354     * Returns {@link Looper} for IO operation.
355     *
356     * <p>Declared as package-private.
357     */
358    Looper getIoLooper() {
359        return mIoThread.getLooper();
360    }
361
362    /**
363     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
364     * for tasks that are running on main service thread.
365     *
366     * <p>Declared as package-private.
367     */
368    Looper getServiceLooper() {
369        return mHandler.getLooper();
370    }
371
372    /**
373     * Returns physical address of the device.
374     */
375    int getPhysicalAddress() {
376        return mCecController.getPhysicalAddress();
377    }
378
379    /**
380     * Returns vendor id of CEC service.
381     */
382    int getVendorId() {
383        return mCecController.getVendorId();
384    }
385
386    @ServiceThreadOnly
387    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
388        assertRunOnServiceThread();
389        HdmiCecLocalDeviceTv tv = tv();
390        if (tv == null) {
391            return null;
392        }
393        return tv.getDeviceInfo(logicalAddress);
394    }
395
396    /**
397     * Returns version of CEC.
398     */
399    int getCecVersion() {
400        return mCecController.getVersion();
401    }
402
403    /**
404     * Whether a device of the specified physical address is connected to ARC enabled port.
405     */
406    boolean isConnectedToArcPort(int physicalAddress) {
407        for (HdmiPortInfo portInfo : mPortInfo) {
408            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
409                    && portInfo.isArcSupported()) {
410                return true;
411            }
412        }
413        return false;
414    }
415
416    void runOnServiceThread(Runnable runnable) {
417        mHandler.post(runnable);
418    }
419
420    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
421        mHandler.postAtFrontOfQueue(runnable);
422    }
423
424    private void assertRunOnServiceThread() {
425        if (Looper.myLooper() != mHandler.getLooper()) {
426            throw new IllegalStateException("Should run on service thread.");
427        }
428    }
429
430    /**
431     * Transmit a CEC command to CEC bus.
432     *
433     * @param command CEC command to send out
434     * @param callback interface used to the result of send command
435     */
436    @ServiceThreadOnly
437    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
438        assertRunOnServiceThread();
439        mCecController.sendCommand(command, callback);
440    }
441
442    @ServiceThreadOnly
443    void sendCecCommand(HdmiCecMessage command) {
444        assertRunOnServiceThread();
445        mCecController.sendCommand(command, null);
446    }
447
448    @ServiceThreadOnly
449    boolean handleCecCommand(HdmiCecMessage message) {
450        assertRunOnServiceThread();
451        return dispatchMessageToLocalDevice(message);
452    }
453
454    void setAudioReturnChannel(boolean enabled) {
455        mCecController.setAudioReturnChannel(enabled);
456    }
457
458    @ServiceThreadOnly
459    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
460        assertRunOnServiceThread();
461        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
462            if (device.dispatchMessage(message)
463                    && message.getDestination() != HdmiCec.ADDR_BROADCAST) {
464                return true;
465            }
466        }
467
468        if (message.getDestination() != HdmiCec.ADDR_BROADCAST) {
469            Slog.w(TAG, "Unhandled cec command:" + message);
470        }
471        return false;
472    }
473
474    /**
475     * Called when a new hotplug event is issued.
476     *
477     * @param portNo hdmi port number where hot plug event issued.
478     * @param connected whether to be plugged in or not
479     */
480    @ServiceThreadOnly
481    void onHotplug(int portNo, boolean connected) {
482        assertRunOnServiceThread();
483        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
484            device.onHotplug(portNo, connected);
485        }
486        announceHotplugEvent(portNo, connected);
487    }
488
489    /**
490     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
491     * devices.
492     *
493     * @param callback an interface used to get a list of all remote devices' address
494     * @param pickStrategy strategy how to pick polling candidates
495     * @param retryCount the number of retry used to send polling message to remote devices
496     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
497     */
498    @ServiceThreadOnly
499    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
500        assertRunOnServiceThread();
501        mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
502    }
503
504    private int checkPollStrategy(int pickStrategy) {
505        int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK;
506        if (strategy == 0) {
507            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
508        }
509        int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK;
510        if (iterationStrategy == 0) {
511            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
512        }
513        return strategy | iterationStrategy;
514    }
515
516    List<HdmiCecLocalDevice> getAllLocalDevices() {
517        assertRunOnServiceThread();
518        return mCecController.getLocalDeviceList();
519    }
520
521    Object getServiceLock() {
522        return mLock;
523    }
524
525    void setAudioStatus(boolean mute, int volume) {
526        // TODO: Hook up with AudioManager.
527    }
528
529    void announceSystemAudioModeChange(boolean enabled) {
530        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
531            invokeSystemAudioModeChange(listener, enabled);
532        }
533    }
534
535    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
536        // TODO: find better name instead of model name.
537        String displayName = Build.MODEL;
538        return new HdmiCecDeviceInfo(logicalAddress,
539                getPhysicalAddress(), deviceType, getVendorId(), displayName);
540    }
541
542    // Record class that monitors the event of the caller of being killed. Used to clean up
543    // the listener list and record list accordingly.
544    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
545        private final IHdmiHotplugEventListener mListener;
546
547        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
548            mListener = listener;
549        }
550
551        @Override
552        public void binderDied() {
553            synchronized (mLock) {
554                mHotplugEventListenerRecords.remove(this);
555                mHotplugEventListeners.remove(mListener);
556            }
557        }
558    }
559
560    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
561        private final IHdmiDeviceEventListener mListener;
562
563        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
564            mListener = listener;
565        }
566
567        @Override
568        public void binderDied() {
569            synchronized (mLock) {
570                mDeviceEventListenerRecords.remove(this);
571                mDeviceEventListeners.remove(mListener);
572            }
573        }
574    }
575
576    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
577        private final IHdmiSystemAudioModeChangeListener mListener;
578
579        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
580            mListener = listener;
581        }
582
583        @Override
584        public void binderDied() {
585            synchronized (mLock) {
586                mSystemAudioModeChangeListenerRecords.remove(this);
587                mSystemAudioModeChangeListeners.remove(mListener);
588            }
589        }
590    }
591
592    private void enforceAccessPermission() {
593        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
594    }
595
596    private final class BinderService extends IHdmiControlService.Stub {
597        @Override
598        public int[] getSupportedTypes() {
599            enforceAccessPermission();
600            // mLocalDevices is an unmodifiable list - no lock necesary.
601            int[] localDevices = new int[mLocalDevices.size()];
602            for (int i = 0; i < localDevices.length; ++i) {
603                localDevices[i] = mLocalDevices.get(i);
604            }
605            return localDevices;
606        }
607
608        @Override
609        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
610            enforceAccessPermission();
611            runOnServiceThread(new Runnable() {
612                @Override
613                public void run() {
614                    HdmiCecLocalDeviceTv tv = tv();
615                    if (tv == null) {
616                        Slog.w(TAG, "Local tv device not available");
617                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
618                        return;
619                    }
620                    tv.deviceSelect(logicalAddress, callback);
621                }
622            });
623        }
624
625        @Override
626        public void portSelect(final int portId, final IHdmiControlCallback callback) {
627            enforceAccessPermission();
628            runOnServiceThread(new Runnable() {
629                @Override
630                public void run() {
631                    HdmiCecLocalDeviceTv tv = tv();
632                    if (tv == null) {
633                        Slog.w(TAG, "Local tv device not available");
634                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
635                        return;
636                    }
637                    tv.doManualPortSwitching(portId, callback);
638                }
639            });
640        }
641
642        @Override
643        public void sendKeyEvent(final int keyCode, final boolean isPressed) {
644            enforceAccessPermission();
645            runOnServiceThread(new Runnable() {
646                @Override
647                public void run() {
648                    // TODO: sendKeyEvent is for TV device only for now. Allow other
649                    //       local devices of different types to use this as well.
650                    HdmiCecLocalDeviceTv tv = tv();
651                    if (tv == null) {
652                        Slog.w(TAG, "Local tv device not available");
653                        return;
654                    }
655                    tv.sendKeyEvent(keyCode, isPressed);
656                }
657            });
658        }
659
660        @Override
661        public void oneTouchPlay(final IHdmiControlCallback callback) {
662            enforceAccessPermission();
663            runOnServiceThread(new Runnable() {
664                @Override
665                public void run() {
666                    HdmiControlService.this.oneTouchPlay(callback);
667                }
668            });
669        }
670
671        @Override
672        public void queryDisplayStatus(final IHdmiControlCallback callback) {
673            enforceAccessPermission();
674            runOnServiceThread(new Runnable() {
675                @Override
676                public void run() {
677                    HdmiControlService.this.queryDisplayStatus(callback);
678                }
679            });
680        }
681
682        @Override
683        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
684            enforceAccessPermission();
685            runOnServiceThread(new Runnable() {
686                @Override
687                public void run() {
688                    HdmiControlService.this.addHotplugEventListener(listener);
689                }
690            });
691        }
692
693        @Override
694        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
695            enforceAccessPermission();
696            runOnServiceThread(new Runnable() {
697                @Override
698                public void run() {
699                    HdmiControlService.this.removeHotplugEventListener(listener);
700                }
701            });
702        }
703
704        @Override
705        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
706            enforceAccessPermission();
707            runOnServiceThread(new Runnable() {
708                @Override
709                public void run() {
710                    HdmiControlService.this.addDeviceEventListener(listener);
711                }
712            });
713        }
714
715        @Override
716        public List<HdmiPortInfo> getPortInfo() {
717            enforceAccessPermission();
718            return mPortInfo;
719        }
720
721        @Override
722        public boolean canChangeSystemAudioMode() {
723            enforceAccessPermission();
724            HdmiCecLocalDeviceTv tv = tv();
725            if (tv == null) {
726                return false;
727            }
728            return tv.hasSystemAudioDevice();
729        }
730
731        @Override
732        public boolean getSystemAudioMode() {
733            enforceAccessPermission();
734            HdmiCecLocalDeviceTv tv = tv();
735            if (tv == null) {
736                return false;
737            }
738            return tv.getSystemAudioMode();
739        }
740
741        @Override
742        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
743            enforceAccessPermission();
744            runOnServiceThread(new Runnable() {
745                @Override
746                public void run() {
747                    HdmiCecLocalDeviceTv tv = tv();
748                    if (tv == null) {
749                        Slog.w(TAG, "Local tv device not available");
750                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
751                        return;
752                    }
753                    tv.changeSystemAudioMode(enabled, callback);
754                }
755            });
756        }
757
758        @Override
759        public void addSystemAudioModeChangeListener(
760                final IHdmiSystemAudioModeChangeListener listener) {
761            enforceAccessPermission();
762            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
763        }
764
765        @Override
766        public void removeSystemAudioModeChangeListener(
767                final IHdmiSystemAudioModeChangeListener listener) {
768            enforceAccessPermission();
769            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
770        }
771
772        @Override
773        public void setControlEnabled(final boolean enabled) {
774            enforceAccessPermission();
775            synchronized (mLock) {
776                mHdmiControlEnabled = enabled;
777            }
778            // TODO: Stop the running actions when disabled, and start
779            //       address allocation/device discovery when enabled.
780            if (!enabled) {
781                return;
782            }
783            runOnServiceThread(new Runnable() {
784                @Override
785                public void run() {
786                    HdmiCecLocalDeviceTv tv = tv();
787                    if (tv == null) {
788                        return;
789                    }
790                    int value = enabled ? HdmiCec.ENABLED : HdmiCec.DISABLED;
791                    mCecController.setOption(HdmiCec.OPTION_CEC_ENABLE, value);
792                    if (mMhlController != null) {
793                        mMhlController.setOption(HdmiCec.OPTION_MHL_ENABLE, value);
794                    }
795                    tv.routingAtEnableTime();
796                }
797            });
798        }
799
800        @Override
801        public void setSystemAudioVolume(final int oldIndex, final int newIndex,
802                final int maxIndex) {
803            enforceAccessPermission();
804            runOnServiceThread(new Runnable() {
805                @Override
806                public void run() {
807                    HdmiCecLocalDeviceTv tv = tv();
808                    if (tv == null) {
809                        Slog.w(TAG, "Local tv device not available");
810                        return;
811                    }
812                    tv.changeVolume(oldIndex, newIndex - oldIndex, maxIndex);
813                }
814            });
815        }
816
817        @Override
818        public void setSystemAudioMute(final boolean mute) {
819            enforceAccessPermission();
820            runOnServiceThread(new Runnable() {
821                @Override
822                public void run() {
823                    HdmiCecLocalDeviceTv tv = tv();
824                    if (tv == null) {
825                        Slog.w(TAG, "Local tv device not available");
826                        return;
827                    }
828                    tv.changeMute(mute);
829                }
830            });
831        }
832
833        @Override
834        public void setArcMode(final boolean enabled) {
835            enforceAccessPermission();
836            runOnServiceThread(new Runnable() {
837                @Override
838                public void run() {
839                    HdmiCecLocalDeviceTv tv = tv();
840                    if (tv == null) {
841                        Slog.w(TAG, "Local tv device not available to change arc mode.");
842                        return;
843                    }
844                }
845            });
846        }
847
848        @Override
849        public void setOption(final int key, final int value) {
850            if (!isTvDevice()) {
851                return;
852            }
853            switch (key) {
854                case HdmiCec.OPTION_CEC_AUTO_WAKEUP:
855                    mCecController.setOption(key, value);
856                    break;
857                case HdmiCec.OPTION_CEC_AUTO_DEVICE_OFF:
858                    // No need to pass this option to HAL.
859                    tv().setAutoDeviceOff(value == HdmiCec.ENABLED);
860                    break;
861                case HdmiCec.OPTION_MHL_INPUT_SWITCHING:  // Fall through
862                case HdmiCec.OPTION_MHL_POWER_CHARGE:
863                    if (mMhlController != null) {
864                        mMhlController.setOption(key, value);
865                    }
866                    break;
867            }
868        }
869
870        private boolean isTvDevice() {
871            return tv() != null;
872        }
873    }
874
875    @ServiceThreadOnly
876    private void oneTouchPlay(final IHdmiControlCallback callback) {
877        assertRunOnServiceThread();
878        HdmiCecLocalDevicePlayback source = playback();
879        if (source == null) {
880            Slog.w(TAG, "Local playback device not available");
881            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
882            return;
883        }
884        source.oneTouchPlay(callback);
885    }
886
887    @ServiceThreadOnly
888    private void queryDisplayStatus(final IHdmiControlCallback callback) {
889        assertRunOnServiceThread();
890        HdmiCecLocalDevicePlayback source = playback();
891        if (source == null) {
892            Slog.w(TAG, "Local playback device not available");
893            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
894            return;
895        }
896        source.queryDisplayStatus(callback);
897    }
898
899    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
900        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
901        try {
902            listener.asBinder().linkToDeath(record, 0);
903        } catch (RemoteException e) {
904            Slog.w(TAG, "Listener already died");
905            return;
906        }
907        synchronized (mLock) {
908            mHotplugEventListenerRecords.add(record);
909            mHotplugEventListeners.add(listener);
910        }
911    }
912
913    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
914        synchronized (mLock) {
915            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
916                if (record.mListener.asBinder() == listener.asBinder()) {
917                    listener.asBinder().unlinkToDeath(record, 0);
918                    mHotplugEventListenerRecords.remove(record);
919                    break;
920                }
921            }
922            mHotplugEventListeners.remove(listener);
923        }
924    }
925
926    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
927        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
928        try {
929            listener.asBinder().linkToDeath(record, 0);
930        } catch (RemoteException e) {
931            Slog.w(TAG, "Listener already died");
932            return;
933        }
934        synchronized (mLock) {
935            mDeviceEventListeners.add(listener);
936            mDeviceEventListenerRecords.add(record);
937        }
938    }
939
940    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
941        synchronized (mLock) {
942            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
943                try {
944                    listener.onStatusChanged(device, activated);
945                } catch (RemoteException e) {
946                    Slog.e(TAG, "Failed to report device event:" + e);
947                }
948            }
949        }
950    }
951
952    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
953        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
954                listener);
955        try {
956            listener.asBinder().linkToDeath(record, 0);
957        } catch (RemoteException e) {
958            Slog.w(TAG, "Listener already died");
959            return;
960        }
961        synchronized (mLock) {
962            mSystemAudioModeChangeListeners.add(listener);
963            mSystemAudioModeChangeListenerRecords.add(record);
964        }
965    }
966
967    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
968        synchronized (mLock) {
969            for (SystemAudioModeChangeListenerRecord record :
970                    mSystemAudioModeChangeListenerRecords) {
971                if (record.mListener.asBinder() == listener) {
972                    listener.asBinder().unlinkToDeath(record, 0);
973                    mSystemAudioModeChangeListenerRecords.remove(record);
974                    break;
975                }
976            }
977            mSystemAudioModeChangeListeners.remove(listener);
978        }
979    }
980
981    private void invokeCallback(IHdmiControlCallback callback, int result) {
982        try {
983            callback.onComplete(result);
984        } catch (RemoteException e) {
985            Slog.e(TAG, "Invoking callback failed:" + e);
986        }
987    }
988
989    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
990            boolean enabled) {
991        try {
992            listener.onStatusChanged(enabled);
993        } catch (RemoteException e) {
994            Slog.e(TAG, "Invoking callback failed:" + e);
995        }
996    }
997
998    private void announceHotplugEvent(int portId, boolean connected) {
999        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
1000        synchronized (mLock) {
1001            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
1002                invokeHotplugEventListenerLocked(listener, event);
1003            }
1004        }
1005    }
1006
1007    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
1008            HdmiHotplugEvent event) {
1009        try {
1010            listener.onReceived(event);
1011        } catch (RemoteException e) {
1012            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
1013        }
1014    }
1015
1016    private static boolean hasSameTopPort(int path1, int path2) {
1017        return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
1018                == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
1019    }
1020
1021    private HdmiCecLocalDeviceTv tv() {
1022        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV);
1023    }
1024
1025    private HdmiCecLocalDevicePlayback playback() {
1026        return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
1027    }
1028
1029    AudioManager getAudioManager() {
1030        return (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
1031    }
1032
1033    boolean isControlEnabled() {
1034        synchronized (mLock) {
1035            return mHdmiControlEnabled;
1036        }
1037    }
1038
1039    int getPowerStatus() {
1040        return mPowerStatus;
1041    }
1042
1043    boolean isPowerOnOrTransient() {
1044        return mPowerStatus == HdmiCec.POWER_STATUS_ON
1045                || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
1046    }
1047
1048    boolean isPowerStandbyOrTransient() {
1049        return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY
1050                || mPowerStatus == HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY;
1051    }
1052
1053    boolean isPowerStandby() {
1054        return mPowerStatus == HdmiCec.POWER_STATUS_STANDBY;
1055    }
1056
1057    @ServiceThreadOnly
1058    void wakeUp() {
1059        assertRunOnServiceThread();
1060        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1061        pm.wakeUp(SystemClock.uptimeMillis());
1062        // PowerManger will send the broadcast Intent.ACTION_SCREEN_ON and after this gets
1063        // the intent, the sequence will continue at onWakeUp().
1064    }
1065
1066    @ServiceThreadOnly
1067    void standby() {
1068        assertRunOnServiceThread();
1069        mStandbyMessageReceived = true;
1070        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1071        pm.goToSleep(SystemClock.uptimeMillis());
1072        // PowerManger will send the broadcast Intent.ACTION_SCREEN_OFF and after this gets
1073        // the intent, the sequence will continue at onStandby().
1074    }
1075
1076    @ServiceThreadOnly
1077    private void onWakeUp() {
1078        assertRunOnServiceThread();
1079        mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_ON;
1080        if (mCecController != null) {
1081            mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.ENABLED);
1082            initializeLocalDevices(mLocalDevices);
1083        } else {
1084            Slog.i(TAG, "Device does not support HDMI-CEC.");
1085        }
1086        // TODO: Initialize MHL local devices.
1087    }
1088
1089    @ServiceThreadOnly
1090    private void onStandby() {
1091        assertRunOnServiceThread();
1092        mPowerStatus = HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY;
1093        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1094            device.onTransitionToStandby(mStandbyMessageReceived);
1095        }
1096    }
1097
1098    /**
1099     * Called when there are the outstanding actions in the local devices.
1100     * This callback is used to wait for when the action queue is empty
1101     * during the power state transition to standby.
1102     */
1103    @ServiceThreadOnly
1104    void onPendingActionsCleared() {
1105        assertRunOnServiceThread();
1106        if (mPowerStatus != HdmiCec.POWER_STATUS_TRANSIENT_TO_STANDBY) {
1107            return;
1108        }
1109        mPowerStatus = HdmiCec.POWER_STATUS_STANDBY;
1110        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
1111            device.onStandBy(mStandbyMessageReceived);
1112        }
1113        mStandbyMessageReceived = false;
1114        mCecController.setOption(HdmiCec.OPTION_CEC_SERVICE_CONTROL, HdmiCec.DISABLED);
1115    }
1116}
1117