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