HdmiControlService.java revision ea67c183fe5511ad99aeaae1a32b5245bd020e36
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.Context;
21import android.hardware.hdmi.HdmiCec;
22import android.hardware.hdmi.HdmiCecDeviceInfo;
23import android.hardware.hdmi.HdmiCecMessage;
24import android.hardware.hdmi.HdmiHotplugEvent;
25import android.hardware.hdmi.HdmiPortInfo;
26import android.hardware.hdmi.IHdmiControlCallback;
27import android.hardware.hdmi.IHdmiControlService;
28import android.hardware.hdmi.IHdmiDeviceEventListener;
29import android.hardware.hdmi.IHdmiHotplugEventListener;
30import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
31import android.os.Build;
32import android.os.Handler;
33import android.os.HandlerThread;
34import android.os.IBinder;
35import android.os.Looper;
36import android.os.RemoteException;
37import android.util.Slog;
38import android.util.SparseArray;
39import android.util.SparseIntArray;
40
41import com.android.internal.annotations.GuardedBy;
42import com.android.server.SystemService;
43import com.android.server.hdmi.HdmiCecController.AllocateAddressCallback;
44
45import java.util.ArrayList;
46import java.util.Collections;
47import java.util.List;
48
49/**
50 * Provides a service for sending and processing HDMI control messages,
51 * HDMI-CEC and MHL control command, and providing the information on both standard.
52 */
53public final class HdmiControlService extends SystemService {
54    private static final String TAG = "HdmiControlService";
55
56    // TODO: Rename the permission to HDMI_CONTROL.
57    private static final String PERMISSION = "android.permission.HDMI_CEC";
58
59    /**
60     * Interface to report send result.
61     */
62    interface SendMessageCallback {
63        /**
64         * Called when {@link HdmiControlService#sendCecCommand} is completed.
65         *
66         * @param error result of send request.
67         * @see {@link #SEND_RESULT_SUCCESS}
68         * @see {@link #SEND_RESULT_NAK}
69         * @see {@link #SEND_RESULT_FAILURE}
70         */
71        void onSendCompleted(int error);
72    }
73
74    /**
75     * Interface to get a list of available logical devices.
76     */
77    interface DevicePollingCallback {
78        /**
79         * Called when device polling is finished.
80         *
81         * @param ackedAddress a list of logical addresses of available devices
82         */
83        void onPollingFinished(List<Integer> ackedAddress);
84    }
85
86    // A thread to handle synchronous IO of CEC and MHL control service.
87    // Since all of CEC and MHL HAL interfaces processed in short time (< 200ms)
88    // and sparse call it shares a thread to handle IO operations.
89    private final HandlerThread mIoThread = new HandlerThread("Hdmi Control Io Thread");
90
91    // Used to synchronize the access to the service.
92    private final Object mLock = new Object();
93
94    // Type of logical devices hosted in the system. Stored in the unmodifiable list.
95    private final List<Integer> mLocalDevices;
96
97    // List of listeners registered by callers that want to get notified of
98    // hotplug events.
99    @GuardedBy("mLock")
100    private final ArrayList<IHdmiHotplugEventListener> mHotplugEventListeners = new ArrayList<>();
101
102    // List of records for hotplug event listener to handle the the caller killed in action.
103    @GuardedBy("mLock")
104    private final ArrayList<HotplugEventListenerRecord> mHotplugEventListenerRecords =
105            new ArrayList<>();
106
107    // List of listeners registered by callers that want to get notified of
108    // device status events.
109    @GuardedBy("mLock")
110    private final ArrayList<IHdmiDeviceEventListener> mDeviceEventListeners = new ArrayList<>();
111
112    // List of records for device event listener to handle the the caller killed in action.
113    @GuardedBy("mLock")
114    private final ArrayList<DeviceEventListenerRecord> mDeviceEventListenerRecords =
115            new ArrayList<>();
116
117    // List of listeners registered by callers that want to get notified of
118    // system audio mode changes.
119    private final ArrayList<IHdmiSystemAudioModeChangeListener>
120            mSystemAudioModeChangeListeners = new ArrayList<>();
121    // List of records for system audio mode change to handle the the caller killed in action.
122    private final ArrayList<SystemAudioModeChangeListenerRecord>
123            mSystemAudioModeChangeListenerRecords = new ArrayList<>();
124
125    // Handler used to run a task in service thread.
126    private final Handler mHandler = new Handler();
127
128    @Nullable
129    private HdmiCecController mCecController;
130
131    @Nullable
132    private HdmiMhlController mMhlController;
133
134    // HDMI port information. Stored in the unmodifiable list to keep the static information
135    // from being modified.
136    private List<HdmiPortInfo> mPortInfo;
137
138    public HdmiControlService(Context context) {
139        super(context);
140        mLocalDevices = HdmiUtils.asImmutableList(getContext().getResources().getIntArray(
141                com.android.internal.R.array.config_hdmiCecLogicalDeviceType));
142    }
143
144    @Override
145    public void onStart() {
146        mIoThread.start();
147        mCecController = HdmiCecController.create(this);
148
149        if (mCecController != null) {
150            initializeLocalDevices(mLocalDevices);
151        } else {
152            Slog.i(TAG, "Device does not support HDMI-CEC.");
153        }
154
155        mMhlController = HdmiMhlController.create(this);
156        if (mMhlController == null) {
157            Slog.i(TAG, "Device does not support MHL-control.");
158        }
159        mPortInfo = initPortInfo();
160        publishBinderService(Context.HDMI_CONTROL_SERVICE, new BinderService());
161
162        // TODO: Read the preference for SystemAudioMode and initialize mSystemAudioMode and
163        // start to monitor the preference value and invoke SystemAudioActionFromTv if needed.
164    }
165
166    private void initializeLocalDevices(final List<Integer> deviceTypes) {
167        // A container for [Logical Address, Local device info].
168        final SparseArray<HdmiCecLocalDevice> devices = new SparseArray<>();
169        final SparseIntArray finished = new SparseIntArray();
170        mCecController.clearLogicalAddress();
171        for (int type : deviceTypes) {
172            final HdmiCecLocalDevice localDevice = HdmiCecLocalDevice.create(this, type);
173            localDevice.init();
174            mCecController.allocateLogicalAddress(type,
175                    localDevice.getPreferredAddress(), new AllocateAddressCallback() {
176                @Override
177                public void onAllocated(int deviceType, int logicalAddress) {
178                    if (logicalAddress == HdmiCec.ADDR_UNREGISTERED) {
179                        Slog.e(TAG, "Failed to allocate address:[device_type:" + deviceType + "]");
180                    } else {
181                        HdmiCecDeviceInfo deviceInfo = createDeviceInfo(logicalAddress, deviceType);
182                        localDevice.setDeviceInfo(deviceInfo);
183                        mCecController.addLocalDevice(deviceType, localDevice);
184                        mCecController.addLogicalAddress(logicalAddress);
185                        devices.append(logicalAddress, localDevice);
186                    }
187                    finished.append(deviceType, logicalAddress);
188
189                    // Address allocation completed for all devices. Notify each device.
190                    if (deviceTypes.size() == finished.size()) {
191                        notifyAddressAllocated(devices);
192                    }
193                }
194            });
195        }
196    }
197
198    private void notifyAddressAllocated(SparseArray<HdmiCecLocalDevice> devices) {
199        for (int i = 0; i < devices.size(); ++i) {
200            int address = devices.keyAt(i);
201            HdmiCecLocalDevice device = devices.valueAt(i);
202            device.handleAddressAllocated(address);
203        }
204    }
205
206    // Initialize HDMI port information. Combine the information from CEC and MHL HAL and
207    // keep them in one place.
208    private List<HdmiPortInfo> initPortInfo() {
209        HdmiPortInfo[] cecPortInfo = null;
210
211        // CEC HAL provides majority of the info while MHL does only MHL support flag for
212        // each port. Return empty array if CEC HAL didn't provide the info.
213        if (mCecController != null) {
214            cecPortInfo = mCecController.getPortInfos();
215        }
216        if (cecPortInfo == null) {
217            return Collections.emptyList();
218        }
219
220        HdmiPortInfo[] mhlPortInfo = new HdmiPortInfo[0];
221        if (mMhlController != null) {
222            // TODO: Implement plumbing logic to get MHL port information.
223            // mhlPortInfo = mMhlController.getPortInfos();
224        }
225
226        // Use the id (port number) to find the matched info between CEC and MHL to combine them
227        // into one. Leave the field `mhlSupported` to false if matched MHL entry is not found.
228        ArrayList<HdmiPortInfo> result = new ArrayList<>(cecPortInfo.length);
229        for (int i = 0; i < cecPortInfo.length; ++i) {
230            HdmiPortInfo cec = cecPortInfo[i];
231            int id = cec.getId();
232            boolean mhlInfoFound = false;
233            for (HdmiPortInfo mhl : mhlPortInfo) {
234                if (id == mhl.getId()) {
235                    result.add(new HdmiPortInfo(id, cec.getType(), cec.getAddress(),
236                            cec.isCecSupported(), mhl.isMhlSupported(), cec.isArcSupported()));
237                    mhlInfoFound = true;
238                    break;
239                }
240            }
241            if (!mhlInfoFound) {
242                result.add(cec);
243            }
244        }
245
246        return Collections.unmodifiableList(result);
247    }
248
249    /**
250     * Returns HDMI port information for the given port id.
251     *
252     * @param portId HDMI port id
253     * @return {@link HdmiPortInfo} for the given port
254     */
255    HdmiPortInfo getPortInfo(int portId) {
256        // mPortInfo is an unmodifiable list and the only reference to its inner list.
257        // No lock is necessary.
258        for (HdmiPortInfo info : mPortInfo) {
259            if (portId == info.getId()) {
260                return info;
261            }
262        }
263        return null;
264    }
265
266    /**
267     * Returns the routing path (physical address) of the HDMI port for the given
268     * port id.
269     */
270    int portIdToPath(int portId) {
271        HdmiPortInfo portInfo = getPortInfo(portId);
272        if (portInfo == null) {
273            Slog.e(TAG, "Cannot find the port info: " + portId);
274            return HdmiConstants.INVALID_PHYSICAL_ADDRESS;
275        }
276        return portInfo.getAddress();
277    }
278
279    /**
280     * Returns the id of HDMI port located at the top of the hierarchy of
281     * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
282     * the port id to be returned is the ID associated with the port address
283     * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
284     */
285    int pathToPortId(int path) {
286        int portAddress = path & HdmiConstants.ROUTING_PATH_TOP_MASK;
287        for (HdmiPortInfo info : mPortInfo) {
288            if (portAddress == info.getAddress()) {
289                return info.getId();
290            }
291        }
292        return HdmiConstants.INVALID_PORT_ID;
293    }
294
295    /**
296     * Returns {@link Looper} for IO operation.
297     *
298     * <p>Declared as package-private.
299     */
300    Looper getIoLooper() {
301        return mIoThread.getLooper();
302    }
303
304    /**
305     * Returns {@link Looper} of main thread. Use this {@link Looper} instance
306     * for tasks that are running on main service thread.
307     *
308     * <p>Declared as package-private.
309     */
310    Looper getServiceLooper() {
311        return mHandler.getLooper();
312    }
313
314    /**
315     * Returns physical address of the device.
316     */
317    int getPhysicalAddress() {
318        return mCecController.getPhysicalAddress();
319    }
320
321    /**
322     * Returns vendor id of CEC service.
323     */
324    int getVendorId() {
325        return mCecController.getVendorId();
326    }
327
328    HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) {
329        assertRunOnServiceThread();
330        HdmiCecLocalDeviceTv tv = tv();
331        if (tv == null) {
332            return null;
333        }
334        return tv.getDeviceInfo(logicalAddress);
335    }
336
337    /**
338     * Returns version of CEC.
339     */
340    int getCecVersion() {
341        return mCecController.getVersion();
342    }
343
344    /**
345     * Whether a device of the specified physical address is connected to ARC enabled port.
346     */
347    boolean isConnectedToArcPort(int physicalAddress) {
348        for (HdmiPortInfo portInfo : mPortInfo) {
349            if (hasSameTopPort(portInfo.getAddress(), physicalAddress)
350                    && portInfo.isArcSupported()) {
351                return true;
352            }
353        }
354        return false;
355    }
356
357    void runOnServiceThread(Runnable runnable) {
358        mHandler.post(runnable);
359    }
360
361    void runOnServiceThreadAtFrontOfQueue(Runnable runnable) {
362        mHandler.postAtFrontOfQueue(runnable);
363    }
364
365    private void assertRunOnServiceThread() {
366        if (Looper.myLooper() != mHandler.getLooper()) {
367            throw new IllegalStateException("Should run on service thread.");
368        }
369    }
370
371    /**
372     * Transmit a CEC command to CEC bus.
373     *
374     * @param command CEC command to send out
375     * @param callback interface used to the result of send command
376     */
377    void sendCecCommand(HdmiCecMessage command, @Nullable SendMessageCallback callback) {
378        mCecController.sendCommand(command, callback);
379    }
380
381    void sendCecCommand(HdmiCecMessage command) {
382        mCecController.sendCommand(command, null);
383    }
384
385    boolean handleCecCommand(HdmiCecMessage message) {
386        return dispatchMessageToLocalDevice(message);
387    }
388
389    void setAudioReturnChannel(boolean enabled) {
390        mCecController.setAudioReturnChannel(enabled);
391    }
392
393    private boolean dispatchMessageToLocalDevice(HdmiCecMessage message) {
394        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
395            if (device.dispatchMessage(message)
396                    && message.getDestination() != HdmiCec.ADDR_BROADCAST) {
397                return true;
398            }
399        }
400
401        Slog.w(TAG, "Unhandled cec command:" + message);
402        return false;
403    }
404
405    /**
406     * Called when a new hotplug event is issued.
407     *
408     * @param portNo hdmi port number where hot plug event issued.
409     * @param connected whether to be plugged in or not
410     */
411    void onHotplug(int portNo, boolean connected) {
412        assertRunOnServiceThread();
413
414        for (HdmiCecLocalDevice device : mCecController.getLocalDeviceList()) {
415            device.onHotplug(portNo, connected);
416        }
417
418        announceHotplugEvent(portNo, connected);
419    }
420
421    /**
422     * Poll all remote devices. It sends &lt;Polling Message&gt; to all remote
423     * devices.
424     *
425     * @param callback an interface used to get a list of all remote devices' address
426     * @param pickStrategy strategy how to pick polling candidates
427     * @param retryCount the number of retry used to send polling message to remote devices
428     * @throw IllegalArgumentException if {@code pickStrategy} is invalid value
429     */
430    void pollDevices(DevicePollingCallback callback, int pickStrategy, int retryCount) {
431        mCecController.pollDevices(callback, checkPollStrategy(pickStrategy), retryCount);
432    }
433
434    private int checkPollStrategy(int pickStrategy) {
435        int strategy = pickStrategy & HdmiConstants.POLL_STRATEGY_MASK;
436        if (strategy == 0) {
437            throw new IllegalArgumentException("Invalid poll strategy:" + pickStrategy);
438        }
439        int iterationStrategy = pickStrategy & HdmiConstants.POLL_ITERATION_STRATEGY_MASK;
440        if (iterationStrategy == 0) {
441            throw new IllegalArgumentException("Invalid iteration strategy:" + pickStrategy);
442        }
443        return strategy | iterationStrategy;
444    }
445
446    List<HdmiCecLocalDevice> getAllLocalDevices() {
447        assertRunOnServiceThread();
448        return mCecController.getLocalDeviceList();
449    }
450
451    Object getServiceLock() {
452        return mLock;
453    }
454
455    void setAudioStatus(boolean mute, int volume) {
456        // TODO: Hook up with AudioManager.
457    }
458
459    void announceSystemAudioModeChange(boolean enabled) {
460        for (IHdmiSystemAudioModeChangeListener listener : mSystemAudioModeChangeListeners) {
461            invokeSystemAudioModeChange(listener, enabled);
462        }
463    }
464
465    private HdmiCecDeviceInfo createDeviceInfo(int logicalAddress, int deviceType) {
466        // TODO: find better name instead of model name.
467        String displayName = Build.MODEL;
468        return new HdmiCecDeviceInfo(logicalAddress,
469                getPhysicalAddress(), deviceType, getVendorId(), displayName);
470    }
471
472    // Record class that monitors the event of the caller of being killed. Used to clean up
473    // the listener list and record list accordingly.
474    private final class HotplugEventListenerRecord implements IBinder.DeathRecipient {
475        private final IHdmiHotplugEventListener mListener;
476
477        public HotplugEventListenerRecord(IHdmiHotplugEventListener listener) {
478            mListener = listener;
479        }
480
481        @Override
482        public void binderDied() {
483            synchronized (mLock) {
484                mHotplugEventListenerRecords.remove(this);
485                mHotplugEventListeners.remove(mListener);
486            }
487        }
488    }
489
490    private final class DeviceEventListenerRecord implements IBinder.DeathRecipient {
491        private final IHdmiDeviceEventListener mListener;
492
493        public DeviceEventListenerRecord(IHdmiDeviceEventListener listener) {
494            mListener = listener;
495        }
496
497        @Override
498        public void binderDied() {
499            synchronized (mLock) {
500                mDeviceEventListenerRecords.remove(this);
501                mDeviceEventListeners.remove(mListener);
502            }
503        }
504    }
505
506    private final class SystemAudioModeChangeListenerRecord implements IBinder.DeathRecipient {
507        private IHdmiSystemAudioModeChangeListener mListener;
508
509        public SystemAudioModeChangeListenerRecord(IHdmiSystemAudioModeChangeListener listener) {
510            mListener = listener;
511        }
512
513        @Override
514        public void binderDied() {
515            synchronized (mLock) {
516                mSystemAudioModeChangeListenerRecords.remove(this);
517                mSystemAudioModeChangeListeners.remove(mListener);
518            }
519        }
520    }
521
522    private void enforceAccessPermission() {
523        getContext().enforceCallingOrSelfPermission(PERMISSION, TAG);
524    }
525
526    private final class BinderService extends IHdmiControlService.Stub {
527        @Override
528        public int[] getSupportedTypes() {
529            enforceAccessPermission();
530            // mLocalDevices is an unmodifiable list - no lock necesary.
531            int[] localDevices = new int[mLocalDevices.size()];
532            for (int i = 0; i < localDevices.length; ++i) {
533                localDevices[i] = mLocalDevices.get(i);
534            }
535            return localDevices;
536        }
537
538        @Override
539        public void deviceSelect(final int logicalAddress, final IHdmiControlCallback callback) {
540            enforceAccessPermission();
541            runOnServiceThread(new Runnable() {
542                @Override
543                public void run() {
544                    HdmiCecLocalDeviceTv tv = tv();
545                    if (tv == null) {
546                        Slog.w(TAG, "Local tv device not available");
547                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
548                        return;
549                    }
550                    tv.deviceSelect(logicalAddress, callback);
551                }
552            });
553        }
554
555        @Override
556        public void portSelect(final int portId, final IHdmiControlCallback callback) {
557            enforceAccessPermission();
558            runOnServiceThread(new Runnable() {
559                @Override
560                public void run() {
561                    HdmiCecLocalDeviceTv tv = tv();
562                    if (tv == null) {
563                        Slog.w(TAG, "Local tv device not available");
564                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
565                        return;
566                    }
567                    tv.portSelect(portId, callback);
568                }
569            });
570        }
571
572        @Override
573        public void sendKeyEvent(final int keyCode, final boolean isPressed) {
574            enforceAccessPermission();
575            runOnServiceThread(new Runnable() {
576                @Override
577                public void run() {
578                    // TODO: sendKeyEvent is for TV device only for now. Allow other
579                    //       local devices of different types to use this as well.
580                    HdmiCecLocalDeviceTv tv = tv();
581                    if (tv == null) {
582                        Slog.w(TAG, "Local tv device not available");
583                        return;
584                    }
585                    tv.sendKeyEvent(keyCode, isPressed);
586                }
587            });
588        }
589
590        @Override
591        public void oneTouchPlay(final IHdmiControlCallback callback) {
592            enforceAccessPermission();
593            runOnServiceThread(new Runnable() {
594                @Override
595                public void run() {
596                    HdmiControlService.this.oneTouchPlay(callback);
597                }
598            });
599        }
600
601        @Override
602        public void queryDisplayStatus(final IHdmiControlCallback callback) {
603            enforceAccessPermission();
604            runOnServiceThread(new Runnable() {
605                @Override
606                public void run() {
607                    HdmiControlService.this.queryDisplayStatus(callback);
608                }
609            });
610        }
611
612        @Override
613        public void addHotplugEventListener(final IHdmiHotplugEventListener listener) {
614            enforceAccessPermission();
615            runOnServiceThread(new Runnable() {
616                @Override
617                public void run() {
618                    HdmiControlService.this.addHotplugEventListener(listener);
619                }
620            });
621        }
622
623        @Override
624        public void removeHotplugEventListener(final IHdmiHotplugEventListener listener) {
625            enforceAccessPermission();
626            runOnServiceThread(new Runnable() {
627                @Override
628                public void run() {
629                    HdmiControlService.this.removeHotplugEventListener(listener);
630                }
631            });
632        }
633
634        @Override
635        public void addDeviceEventListener(final IHdmiDeviceEventListener listener) {
636            enforceAccessPermission();
637            runOnServiceThread(new Runnable() {
638                @Override
639                public void run() {
640                    HdmiControlService.this.addDeviceEventListener(listener);
641                }
642            });
643        }
644
645        @Override
646        public List<HdmiPortInfo> getPortInfo() {
647            enforceAccessPermission();
648            return mPortInfo;
649        }
650
651        @Override
652        public boolean canChangeSystemAudioMode() {
653            enforceAccessPermission();
654            HdmiCecLocalDeviceTv tv = tv();
655            if (tv == null) {
656                return false;
657            }
658            return tv.canChangeSystemAudioMode();
659        }
660
661        @Override
662        public boolean getSystemAudioMode() {
663            enforceAccessPermission();
664            HdmiCecLocalDeviceTv tv = tv();
665            if (tv == null) {
666                return false;
667            }
668            return tv.getSystemAudioMode();
669        }
670
671        @Override
672        public void setSystemAudioMode(final boolean enabled, final IHdmiControlCallback callback) {
673            enforceAccessPermission();
674            runOnServiceThread(new Runnable() {
675                @Override
676                public void run() {
677                    HdmiCecLocalDeviceTv tv = tv();
678                    if (tv == null) {
679                        Slog.w(TAG, "Local tv device not available");
680                        invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
681                        return;
682                    }
683                    tv.changeSystemAudioMode(enabled, callback);
684                }
685            });
686        }
687
688        @Override
689        public void addSystemAudioModeChangeListener(
690                final IHdmiSystemAudioModeChangeListener listener) {
691            enforceAccessPermission();
692            HdmiControlService.this.addSystemAudioModeChangeListner(listener);
693        }
694
695        @Override
696        public void removeSystemAudioModeChangeListener(
697                final IHdmiSystemAudioModeChangeListener listener) {
698            enforceAccessPermission();
699            HdmiControlService.this.removeSystemAudioModeChangeListener(listener);
700        }
701    }
702
703    private void oneTouchPlay(final IHdmiControlCallback callback) {
704        assertRunOnServiceThread();
705        HdmiCecLocalDevicePlayback source = playback();
706        if (source == null) {
707            Slog.w(TAG, "Local playback device not available");
708            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
709            return;
710        }
711        source.oneTouchPlay(callback);
712    }
713
714    private void queryDisplayStatus(final IHdmiControlCallback callback) {
715        assertRunOnServiceThread();
716        HdmiCecLocalDevicePlayback source = playback();
717        if (source == null) {
718            Slog.w(TAG, "Local playback device not available");
719            invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE);
720            return;
721        }
722        source.queryDisplayStatus(callback);
723    }
724
725    private void addHotplugEventListener(IHdmiHotplugEventListener listener) {
726        HotplugEventListenerRecord record = new HotplugEventListenerRecord(listener);
727        try {
728            listener.asBinder().linkToDeath(record, 0);
729        } catch (RemoteException e) {
730            Slog.w(TAG, "Listener already died");
731            return;
732        }
733        synchronized (mLock) {
734            mHotplugEventListenerRecords.add(record);
735            mHotplugEventListeners.add(listener);
736        }
737    }
738
739    private void removeHotplugEventListener(IHdmiHotplugEventListener listener) {
740        synchronized (mLock) {
741            for (HotplugEventListenerRecord record : mHotplugEventListenerRecords) {
742                if (record.mListener.asBinder() == listener.asBinder()) {
743                    listener.asBinder().unlinkToDeath(record, 0);
744                    mHotplugEventListenerRecords.remove(record);
745                    break;
746                }
747            }
748            mHotplugEventListeners.remove(listener);
749        }
750    }
751
752    private void addDeviceEventListener(IHdmiDeviceEventListener listener) {
753        DeviceEventListenerRecord record = new DeviceEventListenerRecord(listener);
754        try {
755            listener.asBinder().linkToDeath(record, 0);
756        } catch (RemoteException e) {
757            Slog.w(TAG, "Listener already died");
758            return;
759        }
760        synchronized (mLock) {
761            mDeviceEventListeners.add(listener);
762            mDeviceEventListenerRecords.add(record);
763        }
764    }
765
766    void invokeDeviceEventListeners(HdmiCecDeviceInfo device, boolean activated) {
767        synchronized (mLock) {
768            for (IHdmiDeviceEventListener listener : mDeviceEventListeners) {
769                try {
770                    listener.onStatusChanged(device, activated);
771                } catch (RemoteException e) {
772                    Slog.e(TAG, "Failed to report device event:" + e);
773                }
774            }
775        }
776    }
777
778    private void addSystemAudioModeChangeListner(IHdmiSystemAudioModeChangeListener listener) {
779        SystemAudioModeChangeListenerRecord record = new SystemAudioModeChangeListenerRecord(
780                listener);
781        try {
782            listener.asBinder().linkToDeath(record, 0);
783        } catch (RemoteException e) {
784            Slog.w(TAG, "Listener already died");
785            return;
786        }
787        synchronized (mLock) {
788            mSystemAudioModeChangeListeners.add(listener);
789            mSystemAudioModeChangeListenerRecords.add(record);
790        }
791    }
792
793    private void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {
794        synchronized (mLock) {
795            for (SystemAudioModeChangeListenerRecord record :
796                    mSystemAudioModeChangeListenerRecords) {
797                if (record.mListener.asBinder() == listener) {
798                    listener.asBinder().unlinkToDeath(record, 0);
799                    mSystemAudioModeChangeListenerRecords.remove(record);
800                    break;
801                }
802            }
803            mSystemAudioModeChangeListeners.remove(listener);
804        }
805    }
806
807    private void invokeCallback(IHdmiControlCallback callback, int result) {
808        try {
809            callback.onComplete(result);
810        } catch (RemoteException e) {
811            Slog.e(TAG, "Invoking callback failed:" + e);
812        }
813    }
814
815    private void invokeSystemAudioModeChange(IHdmiSystemAudioModeChangeListener listener,
816            boolean enabled) {
817        try {
818            listener.onStatusChanged(enabled);
819        } catch (RemoteException e) {
820            Slog.e(TAG, "Invoking callback failed:" + e);
821        }
822    }
823
824    private void announceHotplugEvent(int portId, boolean connected) {
825        HdmiHotplugEvent event = new HdmiHotplugEvent(portId, connected);
826        synchronized (mLock) {
827            for (IHdmiHotplugEventListener listener : mHotplugEventListeners) {
828                invokeHotplugEventListenerLocked(listener, event);
829            }
830        }
831    }
832
833    private void invokeHotplugEventListenerLocked(IHdmiHotplugEventListener listener,
834            HdmiHotplugEvent event) {
835        try {
836            listener.onReceived(event);
837        } catch (RemoteException e) {
838            Slog.e(TAG, "Failed to report hotplug event:" + event.toString(), e);
839        }
840    }
841
842    private static boolean hasSameTopPort(int path1, int path2) {
843        return (path1 & HdmiConstants.ROUTING_PATH_TOP_MASK)
844                == (path2 & HdmiConstants.ROUTING_PATH_TOP_MASK);
845    }
846
847    private HdmiCecLocalDeviceTv tv() {
848        return (HdmiCecLocalDeviceTv) mCecController.getLocalDevice(HdmiCec.DEVICE_TV);
849    }
850
851    private HdmiCecLocalDevicePlayback playback() {
852        return (HdmiCecLocalDevicePlayback) mCecController.getLocalDevice(HdmiCec.DEVICE_PLAYBACK);
853    }
854}
855