TvInputHardwareManager.java revision fe9259e6a78ceb08efc43e8bca4981ee18a0a0ef
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.tv;
18
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
22import android.content.Context;
23import android.hardware.hdmi.HdmiCecDeviceInfo;
24import android.hardware.hdmi.HdmiHotplugEvent;
25import android.hardware.hdmi.IHdmiControlService;
26import android.hardware.hdmi.IHdmiDeviceEventListener;
27import android.hardware.hdmi.IHdmiHotplugEventListener;
28import android.hardware.hdmi.IHdmiInputChangeListener;
29import android.media.AudioDevicePort;
30import android.media.AudioManager;
31import android.media.AudioPatch;
32import android.media.AudioPort;
33import android.media.AudioPortConfig;
34import android.media.tv.ITvInputHardware;
35import android.media.tv.ITvInputHardwareCallback;
36import android.media.tv.TvInputHardwareInfo;
37import android.media.tv.TvInputInfo;
38import android.media.tv.TvStreamConfig;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Looper;
42import android.os.Message;
43import android.os.RemoteException;
44import android.os.ServiceManager;
45import android.util.ArrayMap;
46import android.util.Slog;
47import android.util.SparseArray;
48import android.util.SparseBooleanArray;
49import android.view.KeyEvent;
50import android.view.Surface;
51
52import com.android.server.SystemService;
53
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.HashSet;
57import java.util.List;
58import java.util.Map;
59import java.util.Set;
60
61/**
62 * A helper class for TvInputManagerService to handle TV input hardware.
63 *
64 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
65 * calls to tv_input HAL module.
66 *
67 * @hide
68 */
69class TvInputHardwareManager implements TvInputHal.Callback {
70    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
71
72    private final Context mContext;
73    private final Listener mListener;
74    private final TvInputHal mHal = new TvInputHal(this);
75    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
76    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
77    /* A map from a device ID to the matching TV input ID. */
78    private final SparseArray<String> mHardwareInputIdMap = new SparseArray<String>();
79    /* A map from a HDMI logical address to the matching TV input ID. */
80    private final SparseArray<String> mHdmiCecInputIdMap = new SparseArray<String>();
81    private final Map<String, TvInputInfo> mInputMap = new ArrayMap<String, TvInputInfo>();
82
83    private final AudioManager mAudioManager;
84    private IHdmiControlService mHdmiControlService;
85    private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
86            new HdmiHotplugEventListener();
87    private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
88    private final IHdmiInputChangeListener mHdmiInputChangeListener = new HdmiInputChangeListener();
89    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
90    // TODO: Should handle INACTIVE case.
91    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
92
93    // Calls to mListener should happen here.
94    private final Handler mHandler = new ListenerHandler();
95
96    private final Object mLock = new Object();
97
98    public TvInputHardwareManager(Context context, Listener listener) {
99        mContext = context;
100        mListener = listener;
101        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
102        mHal.init();
103    }
104
105    public void onBootPhase(int phase) {
106        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
107            mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService(
108                    Context.HDMI_CONTROL_SERVICE));
109            if (mHdmiControlService != null) {
110                try {
111                    mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
112                    mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
113                    mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener);
114                } catch (RemoteException e) {
115                    Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
116                }
117            }
118        }
119    }
120
121    @Override
122    public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
123        synchronized (mLock) {
124            Connection connection = new Connection(info);
125            connection.updateConfigsLocked(configs);
126            mConnections.put(info.getDeviceId(), connection);
127            buildInfoListLocked();
128            mHandler.obtainMessage(
129                    ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
130        }
131    }
132
133    private void buildInfoListLocked() {
134        mInfoList.clear();
135        for (int i = 0; i < mConnections.size(); ++i) {
136            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
137        }
138    }
139
140    @Override
141    public void onDeviceUnavailable(int deviceId) {
142        synchronized (mLock) {
143            Connection connection = mConnections.get(deviceId);
144            if (connection == null) {
145                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
146                return;
147            }
148            connection.resetLocked(null, null, null, null, null);
149            mConnections.remove(deviceId);
150            buildInfoListLocked();
151            TvInputHardwareInfo info = connection.getHardwareInfoLocked();
152            mHandler.obtainMessage(
153                    ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
154        }
155    }
156
157    @Override
158    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
159        synchronized (mLock) {
160            Connection connection = mConnections.get(deviceId);
161            if (connection == null) {
162                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
163                        + deviceId);
164                return;
165            }
166            connection.updateConfigsLocked(configs);
167            try {
168                connection.getCallbackLocked().onStreamConfigChanged(configs);
169            } catch (RemoteException e) {
170                Slog.e(TAG, "error in onStreamConfigurationChanged", e);
171            }
172        }
173    }
174
175    public List<TvInputHardwareInfo> getHardwareList() {
176        synchronized (mLock) {
177            return mInfoList;
178        }
179    }
180
181    public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() {
182        if (mHdmiControlService != null) {
183            try {
184                return mHdmiControlService.getInputDevices();
185            } catch (RemoteException e) {
186                Slog.e(TAG, "error in getHdmiCecInputDeviceList", e);
187            }
188        }
189        return Collections.emptyList();
190    }
191
192    private boolean checkUidChangedLocked(
193            Connection connection, int callingUid, int resolvedUserId) {
194        Integer connectionCallingUid = connection.getCallingUidLocked();
195        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
196        if (connectionCallingUid == null || connectionResolvedUserId == null) {
197            return true;
198        }
199        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
200            return true;
201        }
202        return false;
203    }
204
205    private int convertConnectedToState(boolean connected) {
206        if (connected) {
207            return INPUT_STATE_CONNECTED;
208        } else {
209            return INPUT_STATE_DISCONNECTED;
210        }
211    }
212
213    public void addHardwareTvInput(int deviceId, TvInputInfo info) {
214        synchronized (mLock) {
215            String oldInputId = mHardwareInputIdMap.get(deviceId);
216            if (oldInputId != null) {
217                Slog.w(TAG, "Trying to override previous registration: old = "
218                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
219                        + info + ":" + deviceId);
220            }
221            mHardwareInputIdMap.put(deviceId, info.getId());
222            mInputMap.put(info.getId(), info);
223
224            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
225                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
226                if (inputId != null && inputId.equals(info.getId())) {
227                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
228                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
229                            inputId).sendToTarget();
230                }
231            }
232        }
233    }
234
235    private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
236        for (int i = 0; i < map.size(); ++i) {
237            if (map.valueAt(i).equals(value)) {
238                return i;
239            }
240        }
241        return -1;
242    }
243
244    public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) {
245        if (info.getType() != TvInputInfo.TYPE_HDMI) {
246            throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
247        }
248        synchronized (mLock) {
249            String parentId = info.getParentId();
250            int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
251            if (parentIndex < 0) {
252                throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
253            }
254            String oldInputId = mHdmiCecInputIdMap.get(logicalAddress);
255            if (oldInputId != null) {
256                Slog.w(TAG, "Trying to override previous registration: old = "
257                        + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
258                        + info + ":" + logicalAddress);
259            }
260            mHdmiCecInputIdMap.put(logicalAddress, info.getId());
261            mInputMap.put(info.getId(), info);
262        }
263    }
264
265    public void removeTvInput(String inputId) {
266        synchronized (mLock) {
267            mInputMap.remove(inputId);
268            int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
269            if (hardwareIndex >= 0) {
270                mHardwareInputIdMap.removeAt(hardwareIndex);
271            }
272            int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId);
273            if (cecIndex >= 0) {
274                mHdmiCecInputIdMap.removeAt(cecIndex);
275            }
276        }
277    }
278
279    /**
280     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
281     * the object, and if more than one process attempts to create hardware with the same deviceId,
282     * the latest service will get the object and all the other hardware are released. The
283     * release is notified via ITvInputHardwareCallback.onReleased().
284     */
285    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
286            TvInputInfo info, int callingUid, int resolvedUserId) {
287        if (callback == null) {
288            throw new NullPointerException();
289        }
290        synchronized (mLock) {
291            Connection connection = mConnections.get(deviceId);
292            if (connection == null) {
293                Slog.e(TAG, "Invalid deviceId : " + deviceId);
294                return null;
295            }
296            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
297                TvInputHardwareImpl hardware =
298                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
299                try {
300                    callback.asBinder().linkToDeath(connection, 0);
301                } catch (RemoteException e) {
302                    hardware.release();
303                    return null;
304                }
305                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
306            }
307            return connection.getHardwareLocked();
308        }
309    }
310
311    /**
312     * Release the specified hardware.
313     */
314    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
315            int resolvedUserId) {
316        synchronized (mLock) {
317            Connection connection = mConnections.get(deviceId);
318            if (connection == null) {
319                Slog.e(TAG, "Invalid deviceId : " + deviceId);
320                return;
321            }
322            if (connection.getHardwareLocked() != hardware
323                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
324                return;
325            }
326            connection.resetLocked(null, null, null, null, null);
327        }
328    }
329
330    private String findInputIdForHdmiPortLocked(int port) {
331        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
332            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
333                    && hardwareInfo.getHdmiPortId() == port) {
334                return mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
335            }
336        }
337        return null;
338    }
339
340    private class Connection implements IBinder.DeathRecipient {
341        private final TvInputHardwareInfo mHardwareInfo;
342        private TvInputInfo mInfo;
343        private TvInputHardwareImpl mHardware = null;
344        private ITvInputHardwareCallback mCallback;
345        private TvStreamConfig[] mConfigs = null;
346        private Integer mCallingUid = null;
347        private Integer mResolvedUserId = null;
348
349        public Connection(TvInputHardwareInfo hardwareInfo) {
350            mHardwareInfo = hardwareInfo;
351        }
352
353        // *Locked methods assume TvInputHardwareManager.mLock is held.
354
355        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
356                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
357            if (mHardware != null) {
358                try {
359                    mCallback.onReleased();
360                } catch (RemoteException e) {
361                    Slog.e(TAG, "error in Connection::resetLocked", e);
362                }
363                mHardware.release();
364            }
365            mHardware = hardware;
366            mCallback = callback;
367            mInfo = info;
368            mCallingUid = callingUid;
369            mResolvedUserId = resolvedUserId;
370
371            if (mHardware != null && mCallback != null) {
372                try {
373                    mCallback.onStreamConfigChanged(getConfigsLocked());
374                } catch (RemoteException e) {
375                    Slog.e(TAG, "error in Connection::resetLocked", e);
376                }
377            }
378        }
379
380        public void updateConfigsLocked(TvStreamConfig[] configs) {
381            mConfigs = configs;
382        }
383
384        public TvInputHardwareInfo getHardwareInfoLocked() {
385            return mHardwareInfo;
386        }
387
388        public TvInputInfo getInfoLocked() {
389            return mInfo;
390        }
391
392        public ITvInputHardware getHardwareLocked() {
393            return mHardware;
394        }
395
396        public ITvInputHardwareCallback getCallbackLocked() {
397            return mCallback;
398        }
399
400        public TvStreamConfig[] getConfigsLocked() {
401            return mConfigs;
402        }
403
404        public Integer getCallingUidLocked() {
405            return mCallingUid;
406        }
407
408        public Integer getResolvedUserIdLocked() {
409            return mResolvedUserId;
410        }
411
412        @Override
413        public void binderDied() {
414            synchronized (mLock) {
415                resetLocked(null, null, null, null, null);
416            }
417        }
418    }
419
420    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
421        private final TvInputHardwareInfo mInfo;
422        private boolean mReleased = false;
423        private final Object mImplLock = new Object();
424
425        private final AudioDevicePort mAudioSource;
426        private final AudioDevicePort mAudioSink;
427        private AudioPatch mAudioPatch = null;
428
429        private TvStreamConfig mActiveConfig = null;
430
431        public TvInputHardwareImpl(TvInputHardwareInfo info) {
432            mInfo = info;
433            AudioDevicePort audioSource = null;
434            AudioDevicePort audioSink = null;
435            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
436                ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
437                if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
438                    // Find source
439                    for (AudioPort port : devicePorts) {
440                        AudioDevicePort devicePort = (AudioDevicePort) port;
441                        if (devicePort.type() == mInfo.getAudioType() &&
442                                devicePort.address().equals(mInfo.getAudioAddress())) {
443                            audioSource = devicePort;
444                            break;
445                        }
446                    }
447                    // Find sink
448                    // TODO: App may want to specify sink device?
449                    int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
450                    for (AudioPort port : devicePorts) {
451                        AudioDevicePort devicePort = (AudioDevicePort) port;
452                        if (devicePort.type() == sinkDevices) {
453                            audioSink = devicePort;
454                            break;
455                        }
456                    }
457                }
458            }
459            mAudioSource = audioSource;
460            mAudioSink = audioSink;
461        }
462
463        public void release() {
464            synchronized (mImplLock) {
465                if (mAudioPatch != null) {
466                    mAudioManager.releaseAudioPatch(mAudioPatch);
467                    mAudioPatch = null;
468                }
469                mReleased = true;
470            }
471        }
472
473        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
474        // attempts to call setSurface with different TvStreamConfig objects, the last call will
475        // prevail.
476        @Override
477        public boolean setSurface(Surface surface, TvStreamConfig config)
478                throws RemoteException {
479            synchronized (mImplLock) {
480                if (mReleased) {
481                    throw new IllegalStateException("Device already released.");
482                }
483                if (surface != null && config == null) {
484                    return false;
485                }
486                if (surface == null && mActiveConfig == null) {
487                    return false;
488                }
489                if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
490                    if (surface != null) {
491                        // Set "Active Source" for HDMI.
492                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
493                        mActiveHdmiSources.add(mInfo.getDeviceId());
494                    } else {
495                        mActiveHdmiSources.remove(mInfo.getDeviceId());
496                        if (mActiveHdmiSources.size() == 0) {
497                            // Tell HDMI that no HDMI source is active
498                            // TODO(hdmi): mHdmiClient.portSelect(null);
499                        }
500                    }
501                }
502                if (mAudioSource != null && mAudioSink != null) {
503                    if (surface != null) {
504                        AudioPortConfig sourceConfig = mAudioSource.activeConfig();
505                        AudioPortConfig sinkConfig = mAudioSink.activeConfig();
506                        AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
507                        // TODO: build config if activeConfig() == null
508                        mAudioManager.createAudioPatch(
509                                audioPatchArray,
510                                new AudioPortConfig[] { sourceConfig },
511                                new AudioPortConfig[] { sinkConfig });
512                        mAudioPatch = audioPatchArray[0];
513                    } else {
514                        mAudioManager.releaseAudioPatch(mAudioPatch);
515                        mAudioPatch = null;
516                    }
517                }
518                int result = TvInputHal.ERROR_UNKNOWN;
519                if (surface == null) {
520                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
521                    mActiveConfig = null;
522                } else {
523                    if (config != mActiveConfig && mActiveConfig != null) {
524                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
525                        if (result != TvInputHal.SUCCESS) {
526                            mActiveConfig = null;
527                            return false;
528                        }
529                    }
530                    result = mHal.addStream(mInfo.getDeviceId(), surface, config);
531                    if (result == TvInputHal.SUCCESS) {
532                        mActiveConfig = config;
533                    }
534                }
535                return result == TvInputHal.SUCCESS;
536            }
537        }
538
539        @Override
540        public void setVolume(float volume) throws RemoteException {
541            synchronized (mImplLock) {
542                if (mReleased) {
543                    throw new IllegalStateException("Device already released.");
544                }
545            }
546            // TODO: Use AudioGain?
547        }
548
549        @Override
550        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
551            synchronized (mImplLock) {
552                if (mReleased) {
553                    throw new IllegalStateException("Device already released.");
554                }
555            }
556            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
557                return false;
558            }
559            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
560            return false;
561        }
562    }
563
564    interface Listener {
565        public void onStateChanged(String inputId, int state);
566        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
567        public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
568        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
569        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
570    }
571
572    private class ListenerHandler extends Handler {
573        private static final int STATE_CHANGED = 1;
574        private static final int HARDWARE_DEVICE_ADDED = 2;
575        private static final int HARDWARE_DEVICE_REMOVED = 3;
576        private static final int HDMI_CEC_DEVICE_ADDED = 4;
577        private static final int HDMI_CEC_DEVICE_REMOVED = 5;
578
579        @Override
580        public final void handleMessage(Message msg) {
581            switch (msg.what) {
582                case STATE_CHANGED: {
583                    String inputId = (String) msg.obj;
584                    int state = msg.arg1;
585                    mListener.onStateChanged(inputId, state);
586                    break;
587                }
588                case HARDWARE_DEVICE_ADDED: {
589                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
590                    mListener.onHardwareDeviceAdded(info);
591                    break;
592                }
593                case HARDWARE_DEVICE_REMOVED: {
594                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
595                    mListener.onHardwareDeviceRemoved(info);
596                    break;
597                }
598                case HDMI_CEC_DEVICE_ADDED: {
599                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
600                    mListener.onHdmiCecDeviceAdded(info);
601                    break;
602                }
603                case HDMI_CEC_DEVICE_REMOVED: {
604                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
605                    mListener.onHdmiCecDeviceRemoved(info);
606                    break;
607                }
608                default: {
609                    Slog.w(TAG, "Unhandled message: " + msg);
610                    break;
611                }
612            }
613        }
614    }
615
616    // Listener implementations for HdmiControlService
617
618    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
619        @Override
620        public void onReceived(HdmiHotplugEvent event) {
621            synchronized (mLock) {
622                mHdmiStateMap.put(event.getPort(), event.isConnected());
623                String inputId = findInputIdForHdmiPortLocked(event.getPort());
624                if (inputId == null) {
625                    return;
626                }
627                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
628                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
629            }
630        }
631    }
632
633    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
634        @Override
635        public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
636            mHandler.obtainMessage(
637                    activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED
638                    : ListenerHandler.HDMI_CEC_DEVICE_REMOVED,
639                    0, 0, deviceInfo).sendToTarget();
640        }
641    }
642
643    private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub {
644        @Override
645        public void onChanged(HdmiCecDeviceInfo device) throws RemoteException {
646            // TODO: Build a channel Uri for the TvInputInfo associated with the logical device
647            //       and send an intent to TV app
648        }
649    }
650}
651