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