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.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.pm.PackageManager;
27import android.hardware.hdmi.HdmiControlManager;
28import android.hardware.hdmi.HdmiDeviceInfo;
29import android.hardware.hdmi.HdmiHotplugEvent;
30import android.hardware.hdmi.IHdmiControlService;
31import android.hardware.hdmi.IHdmiDeviceEventListener;
32import android.hardware.hdmi.IHdmiHotplugEventListener;
33import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
34import android.media.AudioDevicePort;
35import android.media.AudioFormat;
36import android.media.AudioGain;
37import android.media.AudioGainConfig;
38import android.media.AudioManager;
39import android.media.AudioPatch;
40import android.media.AudioPort;
41import android.media.AudioPortConfig;
42import android.media.AudioSystem;
43import android.media.tv.ITvInputHardware;
44import android.media.tv.ITvInputHardwareCallback;
45import android.media.tv.TvInputHardwareInfo;
46import android.media.tv.TvInputInfo;
47import android.media.tv.TvStreamConfig;
48import android.os.Binder;
49import android.os.Handler;
50import android.os.IBinder;
51import android.os.Message;
52import android.os.RemoteException;
53import android.os.ServiceManager;
54import android.util.ArrayMap;
55import android.util.Slog;
56import android.util.SparseArray;
57import android.util.SparseBooleanArray;
58import android.view.KeyEvent;
59import android.view.Surface;
60
61import com.android.internal.util.IndentingPrintWriter;
62import com.android.server.SystemService;
63
64import java.io.FileDescriptor;
65import java.io.PrintWriter;
66import java.util.ArrayList;
67import java.util.Arrays;
68import java.util.Collections;
69import java.util.Iterator;
70import java.util.LinkedList;
71import java.util.List;
72import java.util.Map;
73
74/**
75 * A helper class for TvInputManagerService to handle TV input hardware.
76 *
77 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
78 * calls to tv_input HAL module.
79 *
80 * @hide
81 */
82class TvInputHardwareManager implements TvInputHal.Callback {
83    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
84
85    private final Context mContext;
86    private final Listener mListener;
87    private final TvInputHal mHal = new TvInputHal(this);
88    private final SparseArray<Connection> mConnections = new SparseArray<>();
89    private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
90    private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>();
91    /* A map from a device ID to the matching TV input ID. */
92    private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
93    /* A map from a HDMI logical address to the matching TV input ID. */
94    private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
95    private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
96
97    private final AudioManager mAudioManager;
98    private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
99            new HdmiHotplugEventListener();
100    private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
101    private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
102            new HdmiSystemAudioModeChangeListener();
103    private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() {
104        @Override
105        public void onReceive(Context context, Intent intent) {
106            handleVolumeChange(context, intent);
107        }
108    };
109    private int mCurrentIndex = 0;
110    private int mCurrentMaxIndex = 0;
111
112    // TODO: Should handle STANDBY case.
113    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
114    private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
115
116    // Calls to mListener should happen here.
117    private final Handler mHandler = new ListenerHandler();
118
119    private final Object mLock = new Object();
120
121    public TvInputHardwareManager(Context context, Listener listener) {
122        mContext = context;
123        mListener = listener;
124        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
125        mHal.init();
126    }
127
128    public void onBootPhase(int phase) {
129        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
130            IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
131                    ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
132            if (hdmiControlService != null) {
133                try {
134                    hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
135                    hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
136                    hdmiControlService.addSystemAudioModeChangeListener(
137                            mHdmiSystemAudioModeChangeListener);
138                    mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
139                } catch (RemoteException e) {
140                    Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
141                }
142            } else {
143                Slog.w(TAG, "HdmiControlService is not available");
144            }
145            final IntentFilter filter = new IntentFilter();
146            filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
147            filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
148            mContext.registerReceiver(mVolumeReceiver, filter);
149            updateVolume();
150        }
151    }
152
153    @Override
154    public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
155        synchronized (mLock) {
156            Connection connection = new Connection(info);
157            connection.updateConfigsLocked(configs);
158            mConnections.put(info.getDeviceId(), connection);
159            buildHardwareListLocked();
160            mHandler.obtainMessage(
161                    ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
162            if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
163                processPendingHdmiDeviceEventsLocked();
164            }
165        }
166    }
167
168    private void buildHardwareListLocked() {
169        mHardwareList.clear();
170        for (int i = 0; i < mConnections.size(); ++i) {
171            mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
172        }
173    }
174
175    @Override
176    public void onDeviceUnavailable(int deviceId) {
177        synchronized (mLock) {
178            Connection connection = mConnections.get(deviceId);
179            if (connection == null) {
180                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
181                return;
182            }
183            connection.resetLocked(null, null, null, null, null);
184            mConnections.remove(deviceId);
185            buildHardwareListLocked();
186            TvInputHardwareInfo info = connection.getHardwareInfoLocked();
187            if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
188                // Remove HDMI devices linked with this hardware.
189                for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
190                    HdmiDeviceInfo deviceInfo = it.next();
191                    if (deviceInfo.getPortId() == info.getHdmiPortId()) {
192                        mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
193                                deviceInfo).sendToTarget();
194                        it.remove();
195                    }
196                }
197            }
198            mHandler.obtainMessage(
199                    ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
200        }
201    }
202
203    @Override
204    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
205        synchronized (mLock) {
206            Connection connection = mConnections.get(deviceId);
207            if (connection == null) {
208                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
209                        + deviceId);
210                return;
211            }
212            connection.updateConfigsLocked(configs);
213            String inputId = mHardwareInputIdMap.get(deviceId);
214            if (inputId != null) {
215                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
216                        convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
217            }
218            ITvInputHardwareCallback callback = connection.getCallbackLocked();
219            if (callback != null) {
220                try {
221                    callback.onStreamConfigChanged(configs);
222                } catch (RemoteException e) {
223                    Slog.e(TAG, "error in onStreamConfigurationChanged", e);
224                }
225            }
226        }
227    }
228
229    @Override
230    public void onFirstFrameCaptured(int deviceId, int streamId) {
231        synchronized (mLock) {
232            Connection connection = mConnections.get(deviceId);
233            if (connection == null) {
234                Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
235                        + deviceId);
236                return;
237            }
238            Runnable runnable = connection.getOnFirstFrameCapturedLocked();
239            if (runnable != null) {
240                runnable.run();
241                connection.setOnFirstFrameCapturedLocked(null);
242            }
243        }
244    }
245
246    public List<TvInputHardwareInfo> getHardwareList() {
247        synchronized (mLock) {
248            return Collections.unmodifiableList(mHardwareList);
249        }
250    }
251
252    public List<HdmiDeviceInfo> getHdmiDeviceList() {
253        synchronized (mLock) {
254            return Collections.unmodifiableList(mHdmiDeviceList);
255        }
256    }
257
258    private boolean checkUidChangedLocked(
259            Connection connection, int callingUid, int resolvedUserId) {
260        Integer connectionCallingUid = connection.getCallingUidLocked();
261        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
262        return connectionCallingUid == null || connectionResolvedUserId == null
263                || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId;
264    }
265
266    private int convertConnectedToState(boolean connected) {
267        if (connected) {
268            return INPUT_STATE_CONNECTED;
269        } else {
270            return INPUT_STATE_DISCONNECTED;
271        }
272    }
273
274    public void addHardwareInput(int deviceId, TvInputInfo info) {
275        synchronized (mLock) {
276            String oldInputId = mHardwareInputIdMap.get(deviceId);
277            if (oldInputId != null) {
278                Slog.w(TAG, "Trying to override previous registration: old = "
279                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
280                        + info + ":" + deviceId);
281            }
282            mHardwareInputIdMap.put(deviceId, info.getId());
283            mInputMap.put(info.getId(), info);
284
285            // Process pending state changes
286
287            // For logical HDMI devices, they have information from HDMI CEC signals.
288            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
289                TvInputHardwareInfo hardwareInfo =
290                        findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
291                if (hardwareInfo == null) {
292                    continue;
293                }
294                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
295                if (inputId != null && inputId.equals(info.getId())) {
296                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
297                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
298                            inputId).sendToTarget();
299                    return;
300                }
301            }
302            // For the rest of the devices, we can tell by the number of available streams.
303            Connection connection = mConnections.get(deviceId);
304            if (connection != null) {
305                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
306                        convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
307                        info.getId()).sendToTarget();
308            }
309        }
310    }
311
312    private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
313        for (int i = 0; i < map.size(); ++i) {
314            if (map.valueAt(i).equals(value)) {
315                return i;
316            }
317        }
318        return -1;
319    }
320
321    private static boolean intArrayContains(int[] array, int value) {
322        for (int element : array) {
323            if (element == value) return true;
324        }
325        return false;
326    }
327
328    public void addHdmiInput(int id, TvInputInfo info) {
329        if (info.getType() != TvInputInfo.TYPE_HDMI) {
330            throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
331        }
332        synchronized (mLock) {
333            String parentId = info.getParentId();
334            int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
335            if (parentIndex < 0) {
336                throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
337            }
338            String oldInputId = mHdmiInputIdMap.get(id);
339            if (oldInputId != null) {
340                Slog.w(TAG, "Trying to override previous registration: old = "
341                        + mInputMap.get(oldInputId) + ":" + id + ", new = "
342                        + info + ":" + id);
343            }
344            mHdmiInputIdMap.put(id, info.getId());
345            mInputMap.put(info.getId(), info);
346        }
347    }
348
349    public void removeHardwareInput(String inputId) {
350        synchronized (mLock) {
351            mInputMap.remove(inputId);
352            int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
353            if (hardwareIndex >= 0) {
354                mHardwareInputIdMap.removeAt(hardwareIndex);
355            }
356            int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
357            if (deviceIndex >= 0) {
358                mHdmiInputIdMap.removeAt(deviceIndex);
359            }
360        }
361    }
362
363    /**
364     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
365     * the object, and if more than one process attempts to create hardware with the same deviceId,
366     * the latest service will get the object and all the other hardware are released. The
367     * release is notified via ITvInputHardwareCallback.onReleased().
368     */
369    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
370            TvInputInfo info, int callingUid, int resolvedUserId) {
371        if (callback == null) {
372            throw new NullPointerException();
373        }
374        synchronized (mLock) {
375            Connection connection = mConnections.get(deviceId);
376            if (connection == null) {
377                Slog.e(TAG, "Invalid deviceId : " + deviceId);
378                return null;
379            }
380            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
381                TvInputHardwareImpl hardware =
382                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
383                try {
384                    callback.asBinder().linkToDeath(connection, 0);
385                } catch (RemoteException e) {
386                    hardware.release();
387                    return null;
388                }
389                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
390            }
391            return connection.getHardwareLocked();
392        }
393    }
394
395    /**
396     * Release the specified hardware.
397     */
398    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
399            int resolvedUserId) {
400        synchronized (mLock) {
401            Connection connection = mConnections.get(deviceId);
402            if (connection == null) {
403                Slog.e(TAG, "Invalid deviceId : " + deviceId);
404                return;
405            }
406            if (connection.getHardwareLocked() != hardware
407                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
408                return;
409            }
410            connection.resetLocked(null, null, null, null, null);
411        }
412    }
413
414    private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
415        for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
416            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
417                    && hardwareInfo.getHdmiPortId() == port) {
418                return hardwareInfo;
419            }
420        }
421        return null;
422    }
423
424    private int findDeviceIdForInputIdLocked(String inputId) {
425        for (int i = 0; i < mConnections.size(); ++i) {
426            Connection connection = mConnections.get(i);
427            if (connection.getInfoLocked().getId().equals(inputId)) {
428                return i;
429            }
430        }
431        return -1;
432    }
433
434    /**
435     * Get the list of TvStreamConfig which is buffered mode.
436     */
437    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
438            int resolvedUserId) {
439        List<TvStreamConfig> configsList = new ArrayList<>();
440        synchronized (mLock) {
441            int deviceId = findDeviceIdForInputIdLocked(inputId);
442            if (deviceId < 0) {
443                Slog.e(TAG, "Invalid inputId : " + inputId);
444                return configsList;
445            }
446            Connection connection = mConnections.get(deviceId);
447            for (TvStreamConfig config : connection.getConfigsLocked()) {
448                if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
449                    configsList.add(config);
450                }
451            }
452        }
453        return configsList;
454    }
455
456    /**
457     * Take a snapshot of the given TV input into the provided Surface.
458     */
459    public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
460            int callingUid, int resolvedUserId) {
461        synchronized (mLock) {
462            int deviceId = findDeviceIdForInputIdLocked(inputId);
463            if (deviceId < 0) {
464                Slog.e(TAG, "Invalid inputId : " + inputId);
465                return false;
466            }
467            Connection connection = mConnections.get(deviceId);
468            final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
469            if (hardwareImpl != null) {
470                // Stop previous capture.
471                Runnable runnable = connection.getOnFirstFrameCapturedLocked();
472                if (runnable != null) {
473                    runnable.run();
474                    connection.setOnFirstFrameCapturedLocked(null);
475                }
476
477                boolean result = hardwareImpl.startCapture(surface, config);
478                if (result) {
479                    connection.setOnFirstFrameCapturedLocked(new Runnable() {
480                        @Override
481                        public void run() {
482                            hardwareImpl.stopCapture(config);
483                        }
484                    });
485                }
486                return result;
487            }
488        }
489        return false;
490    }
491
492    private void processPendingHdmiDeviceEventsLocked() {
493        for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
494            Message msg = it.next();
495            HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
496            TvInputHardwareInfo hardwareInfo =
497                    findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
498            if (hardwareInfo != null) {
499                msg.sendToTarget();
500                it.remove();
501            }
502        }
503    }
504
505    private void updateVolume() {
506        mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
507        mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
508    }
509
510    private void handleVolumeChange(Context context, Intent intent) {
511        String action = intent.getAction();
512        switch (action) {
513            case AudioManager.VOLUME_CHANGED_ACTION: {
514                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
515                if (streamType != AudioManager.STREAM_MUSIC) {
516                    return;
517                }
518                int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
519                if (index == mCurrentIndex) {
520                    return;
521                }
522                mCurrentIndex = index;
523                break;
524            }
525            case AudioManager.STREAM_MUTE_CHANGED_ACTION: {
526                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
527                if (streamType != AudioManager.STREAM_MUSIC) {
528                    return;
529                }
530                // volume index will be updated at onMediaStreamVolumeChanged() through
531                // updateVolume().
532                break;
533            }
534            default:
535                Slog.w(TAG, "Unrecognized intent: " + intent);
536                return;
537        }
538        synchronized (mLock) {
539            for (int i = 0; i < mConnections.size(); ++i) {
540                TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
541                if (hardwareImpl != null) {
542                    hardwareImpl.onMediaStreamVolumeChanged();
543                }
544            }
545        }
546    }
547
548    private float getMediaStreamVolume() {
549        return (float) mCurrentIndex / (float) mCurrentMaxIndex;
550    }
551
552    public void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
553        final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
554        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
555                != PackageManager.PERMISSION_GRANTED) {
556            pw.println("Permission Denial: can't dump TvInputHardwareManager from pid="
557                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
558            return;
559        }
560
561        synchronized (mLock) {
562            pw.println("TvInputHardwareManager Info:");
563            pw.increaseIndent();
564            pw.println("mConnections: deviceId -> Connection");
565            pw.increaseIndent();
566            for (int i = 0; i < mConnections.size(); i++) {
567                int deviceId = mConnections.keyAt(i);
568                Connection mConnection = mConnections.valueAt(i);
569                pw.println(deviceId + ": " + mConnection);
570
571            }
572            pw.decreaseIndent();
573
574            pw.println("mHardwareList:");
575            pw.increaseIndent();
576            for (TvInputHardwareInfo tvInputHardwareInfo : mHardwareList) {
577                pw.println(tvInputHardwareInfo);
578            }
579            pw.decreaseIndent();
580
581            pw.println("mHdmiDeviceList:");
582            pw.increaseIndent();
583            for (HdmiDeviceInfo hdmiDeviceInfo : mHdmiDeviceList) {
584                pw.println(hdmiDeviceInfo);
585            }
586            pw.decreaseIndent();
587
588            pw.println("mHardwareInputIdMap: deviceId -> inputId");
589            pw.increaseIndent();
590            for (int i = 0 ; i < mHardwareInputIdMap.size(); i++) {
591                int deviceId = mHardwareInputIdMap.keyAt(i);
592                String inputId = mHardwareInputIdMap.valueAt(i);
593                pw.println(deviceId + ": " + inputId);
594            }
595            pw.decreaseIndent();
596
597            pw.println("mHdmiInputIdMap: id -> inputId");
598            pw.increaseIndent();
599            for (int i = 0; i < mHdmiInputIdMap.size(); i++) {
600                int id = mHdmiInputIdMap.keyAt(i);
601                String inputId = mHdmiInputIdMap.valueAt(i);
602                pw.println(id + ": " + inputId);
603            }
604            pw.decreaseIndent();
605
606            pw.println("mInputMap: inputId -> inputInfo");
607            pw.increaseIndent();
608            for(Map.Entry<String, TvInputInfo> entry : mInputMap.entrySet()) {
609                pw.println(entry.getKey() + ": " + entry.getValue());
610            }
611            pw.decreaseIndent();
612            pw.decreaseIndent();
613        }
614    }
615
616    private class Connection implements IBinder.DeathRecipient {
617        private final TvInputHardwareInfo mHardwareInfo;
618        private TvInputInfo mInfo;
619        private TvInputHardwareImpl mHardware = null;
620        private ITvInputHardwareCallback mCallback;
621        private TvStreamConfig[] mConfigs = null;
622        private Integer mCallingUid = null;
623        private Integer mResolvedUserId = null;
624        private Runnable mOnFirstFrameCaptured;
625
626        public Connection(TvInputHardwareInfo hardwareInfo) {
627            mHardwareInfo = hardwareInfo;
628        }
629
630        // *Locked methods assume TvInputHardwareManager.mLock is held.
631
632        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
633                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
634            if (mHardware != null) {
635                try {
636                    mCallback.onReleased();
637                } catch (RemoteException e) {
638                    Slog.e(TAG, "error in Connection::resetLocked", e);
639                }
640                mHardware.release();
641            }
642            mHardware = hardware;
643            mCallback = callback;
644            mInfo = info;
645            mCallingUid = callingUid;
646            mResolvedUserId = resolvedUserId;
647            mOnFirstFrameCaptured = null;
648
649            if (mHardware != null && mCallback != null) {
650                try {
651                    mCallback.onStreamConfigChanged(getConfigsLocked());
652                } catch (RemoteException e) {
653                    Slog.e(TAG, "error in Connection::resetLocked", e);
654                }
655            }
656        }
657
658        public void updateConfigsLocked(TvStreamConfig[] configs) {
659            mConfigs = configs;
660        }
661
662        public TvInputHardwareInfo getHardwareInfoLocked() {
663            return mHardwareInfo;
664        }
665
666        public TvInputInfo getInfoLocked() {
667            return mInfo;
668        }
669
670        public ITvInputHardware getHardwareLocked() {
671            return mHardware;
672        }
673
674        public TvInputHardwareImpl getHardwareImplLocked() {
675            return mHardware;
676        }
677
678        public ITvInputHardwareCallback getCallbackLocked() {
679            return mCallback;
680        }
681
682        public TvStreamConfig[] getConfigsLocked() {
683            return mConfigs;
684        }
685
686        public Integer getCallingUidLocked() {
687            return mCallingUid;
688        }
689
690        public Integer getResolvedUserIdLocked() {
691            return mResolvedUserId;
692        }
693
694        public void setOnFirstFrameCapturedLocked(Runnable runnable) {
695            mOnFirstFrameCaptured = runnable;
696        }
697
698        public Runnable getOnFirstFrameCapturedLocked() {
699            return mOnFirstFrameCaptured;
700        }
701
702        @Override
703        public void binderDied() {
704            synchronized (mLock) {
705                resetLocked(null, null, null, null, null);
706            }
707        }
708
709        public String toString() {
710            return "Connection{"
711                    + " mHardwareInfo: " + mHardwareInfo
712                    + ", mInfo: " + mInfo
713                    + ", mCallback: " + mCallback
714                    + ", mConfigs: " + Arrays.toString(mConfigs)
715                    + ", mCallingUid: " + mCallingUid
716                    + ", mResolvedUserId: " + mResolvedUserId
717                    + " }";
718        }
719    }
720
721    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
722        private final TvInputHardwareInfo mInfo;
723        private boolean mReleased = false;
724        private final Object mImplLock = new Object();
725
726        private final AudioManager.OnAudioPortUpdateListener mAudioListener =
727                new AudioManager.OnAudioPortUpdateListener() {
728            @Override
729            public void onAudioPortListUpdate(AudioPort[] portList) {
730                synchronized (mImplLock) {
731                    updateAudioConfigLocked();
732                }
733            }
734
735            @Override
736            public void onAudioPatchListUpdate(AudioPatch[] patchList) {
737                // No-op
738            }
739
740            @Override
741            public void onServiceDied() {
742                synchronized (mImplLock) {
743                    mAudioSource = null;
744                    mAudioSink.clear();
745                    if (mAudioPatch != null) {
746                        mAudioManager.releaseAudioPatch(mAudioPatch);
747                        mAudioPatch = null;
748                    }
749                }
750            }
751        };
752        private int mOverrideAudioType = AudioManager.DEVICE_NONE;
753        private String mOverrideAudioAddress = "";
754        private AudioDevicePort mAudioSource;
755        private List<AudioDevicePort> mAudioSink = new ArrayList<>();
756        private AudioPatch mAudioPatch = null;
757        // Set to an invalid value for a volume, so that current volume can be applied at the
758        // first call to updateAudioConfigLocked().
759        private float mCommittedVolume = -1f;
760        private float mSourceVolume = 0.0f;
761
762        private TvStreamConfig mActiveConfig = null;
763
764        private int mDesiredSamplingRate = 0;
765        private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
766        private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
767
768        public TvInputHardwareImpl(TvInputHardwareInfo info) {
769            mInfo = info;
770            mAudioManager.registerAudioPortUpdateListener(mAudioListener);
771            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
772                mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
773                findAudioSinkFromAudioPolicy(mAudioSink);
774            }
775        }
776
777        private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
778            sinks.clear();
779            ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
780            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
781                return;
782            }
783            int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
784            for (AudioDevicePort port : devicePorts) {
785                if ((port.type() & sinkDevice) != 0 &&
786                    (port.type() & AudioSystem.DEVICE_BIT_IN) == 0) {
787                    sinks.add(port);
788                }
789            }
790        }
791
792        private AudioDevicePort findAudioDevicePort(int type, String address) {
793            if (type == AudioManager.DEVICE_NONE) {
794                return null;
795            }
796            ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
797            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
798                return null;
799            }
800            for (AudioDevicePort port : devicePorts) {
801                if (port.type() == type && port.address().equals(address)) {
802                    return port;
803                }
804            }
805            return null;
806        }
807
808        public void release() {
809            synchronized (mImplLock) {
810                mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
811                if (mAudioPatch != null) {
812                    mAudioManager.releaseAudioPatch(mAudioPatch);
813                    mAudioPatch = null;
814                }
815                mReleased = true;
816            }
817        }
818
819        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
820        // attempts to call setSurface with different TvStreamConfig objects, the last call will
821        // prevail.
822        @Override
823        public boolean setSurface(Surface surface, TvStreamConfig config)
824                throws RemoteException {
825            synchronized (mImplLock) {
826                if (mReleased) {
827                    throw new IllegalStateException("Device already released.");
828                }
829
830                int result = TvInputHal.SUCCESS;
831                if (surface == null) {
832                    // The value of config is ignored when surface == null.
833                    if (mActiveConfig != null) {
834                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
835                        mActiveConfig = null;
836                    } else {
837                        // We already have no active stream.
838                        return true;
839                    }
840                } else {
841                    // It's impossible to set a non-null surface with a null config.
842                    if (config == null) {
843                        return false;
844                    }
845                    // Remove stream only if we have an existing active configuration.
846                    if (mActiveConfig != null && !config.equals(mActiveConfig)) {
847                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
848                        if (result != TvInputHal.SUCCESS) {
849                            mActiveConfig = null;
850                        }
851                    }
852                    // Proceed only if all previous operations succeeded.
853                    if (result == TvInputHal.SUCCESS) {
854                        result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
855                        if (result == TvInputHal.SUCCESS) {
856                            mActiveConfig = config;
857                        }
858                    }
859                }
860                updateAudioConfigLocked();
861                return result == TvInputHal.SUCCESS;
862            }
863        }
864
865        /**
866         * Update audio configuration (source, sink, patch) all up to current state.
867         */
868        private void updateAudioConfigLocked() {
869            boolean sinkUpdated = updateAudioSinkLocked();
870            boolean sourceUpdated = updateAudioSourceLocked();
871            // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
872            // because Java won't evaluate the latter if the former is true.
873
874            if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
875                if (mAudioPatch != null) {
876                    mAudioManager.releaseAudioPatch(mAudioPatch);
877                    mAudioPatch = null;
878                }
879                return;
880            }
881
882            updateVolume();
883            float volume = mSourceVolume * getMediaStreamVolume();
884            AudioGainConfig sourceGainConfig = null;
885            if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
886                AudioGain sourceGain = null;
887                for (AudioGain gain : mAudioSource.gains()) {
888                    if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
889                        sourceGain = gain;
890                        break;
891                    }
892                }
893                // NOTE: we only change the source gain in MODE_JOINT here.
894                if (sourceGain != null) {
895                    int steps = (sourceGain.maxValue() - sourceGain.minValue())
896                            / sourceGain.stepValue();
897                    int gainValue = sourceGain.minValue();
898                    if (volume < 1.0f) {
899                        gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
900                    } else {
901                        gainValue = sourceGain.maxValue();
902                    }
903                    // size of gain values is 1 in MODE_JOINT
904                    int[] gainValues = new int[] { gainValue };
905                    sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
906                            sourceGain.channelMask(), gainValues, 0);
907                } else {
908                    Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
909                }
910            }
911
912            AudioPortConfig sourceConfig = mAudioSource.activeConfig();
913            List<AudioPortConfig> sinkConfigs = new ArrayList<>();
914            AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
915            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
916
917            for (AudioDevicePort audioSink : mAudioSink) {
918                AudioPortConfig sinkConfig = audioSink.activeConfig();
919                int sinkSamplingRate = mDesiredSamplingRate;
920                int sinkChannelMask = mDesiredChannelMask;
921                int sinkFormat = mDesiredFormat;
922                // If sinkConfig != null and values are set to default,
923                // fill in the sinkConfig values.
924                if (sinkConfig != null) {
925                    if (sinkSamplingRate == 0) {
926                        sinkSamplingRate = sinkConfig.samplingRate();
927                    }
928                    if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
929                        sinkChannelMask = sinkConfig.channelMask();
930                    }
931                    if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
932                        sinkChannelMask = sinkConfig.format();
933                    }
934                }
935
936                if (sinkConfig == null
937                        || sinkConfig.samplingRate() != sinkSamplingRate
938                        || sinkConfig.channelMask() != sinkChannelMask
939                        || sinkConfig.format() != sinkFormat) {
940                    // Check for compatibility and reset to default if necessary.
941                    if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
942                            && audioSink.samplingRates().length > 0) {
943                        sinkSamplingRate = audioSink.samplingRates()[0];
944                    }
945                    if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
946                        sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
947                    }
948                    if (!intArrayContains(audioSink.formats(), sinkFormat)) {
949                        sinkFormat = AudioFormat.ENCODING_DEFAULT;
950                    }
951                    sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
952                            sinkFormat, null);
953                    shouldRecreateAudioPatch = true;
954                }
955                sinkConfigs.add(sinkConfig);
956            }
957            // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
958            // non-empty at the beginning of this method.
959            AudioPortConfig sinkConfig = sinkConfigs.get(0);
960            if (sourceConfig == null || sourceGainConfig != null) {
961                int sourceSamplingRate = 0;
962                if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
963                    sourceSamplingRate = sinkConfig.samplingRate();
964                } else if (mAudioSource.samplingRates().length > 0) {
965                    // Use any sampling rate and hope audio patch can handle resampling...
966                    sourceSamplingRate = mAudioSource.samplingRates()[0];
967                }
968                int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
969                for (int inChannelMask : mAudioSource.channelMasks()) {
970                    if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
971                            == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
972                        sourceChannelMask = inChannelMask;
973                        break;
974                    }
975                }
976                int sourceFormat = AudioFormat.ENCODING_DEFAULT;
977                if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
978                    sourceFormat = sinkConfig.format();
979                }
980                sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
981                        sourceFormat, sourceGainConfig);
982                shouldRecreateAudioPatch = true;
983            }
984            if (shouldRecreateAudioPatch) {
985                mCommittedVolume = volume;
986                if (mAudioPatch != null) {
987                    mAudioManager.releaseAudioPatch(mAudioPatch);
988                }
989                mAudioManager.createAudioPatch(
990                        audioPatchArray,
991                        new AudioPortConfig[] { sourceConfig },
992                        sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
993                mAudioPatch = audioPatchArray[0];
994                if (sourceGainConfig != null) {
995                    mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
996                }
997            }
998        }
999
1000        @Override
1001        public void setStreamVolume(float volume) throws RemoteException {
1002            synchronized (mImplLock) {
1003                if (mReleased) {
1004                    throw new IllegalStateException("Device already released.");
1005                }
1006                mSourceVolume = volume;
1007                updateAudioConfigLocked();
1008            }
1009        }
1010
1011        @Override
1012        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
1013            synchronized (mImplLock) {
1014                if (mReleased) {
1015                    throw new IllegalStateException("Device already released.");
1016                }
1017            }
1018            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
1019                return false;
1020            }
1021            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
1022            return false;
1023        }
1024
1025        private boolean startCapture(Surface surface, TvStreamConfig config) {
1026            synchronized (mImplLock) {
1027                if (mReleased) {
1028                    return false;
1029                }
1030                if (surface == null || config == null) {
1031                    return false;
1032                }
1033                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
1034                    return false;
1035                }
1036
1037                int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
1038                return result == TvInputHal.SUCCESS;
1039            }
1040        }
1041
1042        private boolean stopCapture(TvStreamConfig config) {
1043            synchronized (mImplLock) {
1044                if (mReleased) {
1045                    return false;
1046                }
1047                if (config == null) {
1048                    return false;
1049                }
1050
1051                int result = mHal.removeStream(mInfo.getDeviceId(), config);
1052                return result == TvInputHal.SUCCESS;
1053            }
1054        }
1055
1056        private boolean updateAudioSourceLocked() {
1057            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
1058                return false;
1059            }
1060            AudioDevicePort previousSource = mAudioSource;
1061            mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
1062            return mAudioSource == null ? (previousSource != null)
1063                    : !mAudioSource.equals(previousSource);
1064        }
1065
1066        private boolean updateAudioSinkLocked() {
1067            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
1068                return false;
1069            }
1070            List<AudioDevicePort> previousSink = mAudioSink;
1071            mAudioSink = new ArrayList<>();
1072            if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
1073                findAudioSinkFromAudioPolicy(mAudioSink);
1074            } else {
1075                AudioDevicePort audioSink =
1076                        findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
1077                if (audioSink != null) {
1078                    mAudioSink.add(audioSink);
1079                }
1080            }
1081
1082            // Returns true if mAudioSink and previousSink differs.
1083            if (mAudioSink.size() != previousSink.size()) {
1084                return true;
1085            }
1086            previousSink.removeAll(mAudioSink);
1087            return !previousSink.isEmpty();
1088        }
1089
1090        private void handleAudioSinkUpdated() {
1091            synchronized (mImplLock) {
1092                updateAudioConfigLocked();
1093            }
1094        }
1095
1096        @Override
1097        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1098                int channelMask, int format) {
1099            synchronized (mImplLock) {
1100                mOverrideAudioType = audioType;
1101                mOverrideAudioAddress = audioAddress;
1102
1103                mDesiredSamplingRate = samplingRate;
1104                mDesiredChannelMask = channelMask;
1105                mDesiredFormat = format;
1106
1107                updateAudioConfigLocked();
1108            }
1109        }
1110
1111        public void onMediaStreamVolumeChanged() {
1112            synchronized (mImplLock) {
1113                updateAudioConfigLocked();
1114            }
1115        }
1116    }
1117
1118    interface Listener {
1119        void onStateChanged(String inputId, int state);
1120        void onHardwareDeviceAdded(TvInputHardwareInfo info);
1121        void onHardwareDeviceRemoved(TvInputHardwareInfo info);
1122        void onHdmiDeviceAdded(HdmiDeviceInfo device);
1123        void onHdmiDeviceRemoved(HdmiDeviceInfo device);
1124        void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
1125    }
1126
1127    private class ListenerHandler extends Handler {
1128        private static final int STATE_CHANGED = 1;
1129        private static final int HARDWARE_DEVICE_ADDED = 2;
1130        private static final int HARDWARE_DEVICE_REMOVED = 3;
1131        private static final int HDMI_DEVICE_ADDED = 4;
1132        private static final int HDMI_DEVICE_REMOVED = 5;
1133        private static final int HDMI_DEVICE_UPDATED = 6;
1134
1135        @Override
1136        public final void handleMessage(Message msg) {
1137            switch (msg.what) {
1138                case STATE_CHANGED: {
1139                    String inputId = (String) msg.obj;
1140                    int state = msg.arg1;
1141                    mListener.onStateChanged(inputId, state);
1142                    break;
1143                }
1144                case HARDWARE_DEVICE_ADDED: {
1145                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1146                    mListener.onHardwareDeviceAdded(info);
1147                    break;
1148                }
1149                case HARDWARE_DEVICE_REMOVED: {
1150                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1151                    mListener.onHardwareDeviceRemoved(info);
1152                    break;
1153                }
1154                case HDMI_DEVICE_ADDED: {
1155                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1156                    mListener.onHdmiDeviceAdded(info);
1157                    break;
1158                }
1159                case HDMI_DEVICE_REMOVED: {
1160                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1161                    mListener.onHdmiDeviceRemoved(info);
1162                    break;
1163                }
1164                case HDMI_DEVICE_UPDATED: {
1165                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1166                    String inputId;
1167                    synchronized (mLock) {
1168                        inputId = mHdmiInputIdMap.get(info.getId());
1169                    }
1170                    if (inputId != null) {
1171                        mListener.onHdmiDeviceUpdated(inputId, info);
1172                    } else {
1173                        Slog.w(TAG, "Could not resolve input ID matching the device info; "
1174                                + "ignoring.");
1175                    }
1176                    break;
1177                }
1178                default: {
1179                    Slog.w(TAG, "Unhandled message: " + msg);
1180                    break;
1181                }
1182            }
1183        }
1184    }
1185
1186    // Listener implementations for HdmiControlService
1187
1188    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1189        @Override
1190        public void onReceived(HdmiHotplugEvent event) {
1191            synchronized (mLock) {
1192                mHdmiStateMap.put(event.getPort(), event.isConnected());
1193                TvInputHardwareInfo hardwareInfo =
1194                        findHardwareInfoForHdmiPortLocked(event.getPort());
1195                if (hardwareInfo == null) {
1196                    return;
1197                }
1198                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
1199                if (inputId == null) {
1200                    return;
1201                }
1202                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1203                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1204            }
1205        }
1206    }
1207
1208    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1209        @Override
1210        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
1211            if (!deviceInfo.isSourceType()) return;
1212            synchronized (mLock) {
1213                int messageType = 0;
1214                Object obj = null;
1215                switch (status) {
1216                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
1217                        if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
1218                            mHdmiDeviceList.add(deviceInfo);
1219                        } else {
1220                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1221                            return;
1222                        }
1223                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
1224                        obj = deviceInfo;
1225                        break;
1226                    }
1227                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
1228                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1229                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1230                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1231                            return;
1232                        }
1233                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
1234                        obj = deviceInfo;
1235                        break;
1236                    }
1237                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
1238                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1239                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1240                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1241                            return;
1242                        }
1243                        mHdmiDeviceList.add(deviceInfo);
1244                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
1245                        obj = deviceInfo;
1246                        break;
1247                    }
1248                }
1249
1250                Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
1251                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1252                    msg.sendToTarget();
1253                } else {
1254                    mPendingHdmiDeviceEvents.add(msg);
1255                }
1256            }
1257        }
1258
1259        private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1260            for (HdmiDeviceInfo info : mHdmiDeviceList) {
1261                if (info.getId() == id) {
1262                    return info;
1263                }
1264            }
1265            return null;
1266        }
1267    }
1268
1269    private final class HdmiSystemAudioModeChangeListener extends
1270        IHdmiSystemAudioModeChangeListener.Stub {
1271        @Override
1272        public void onStatusChanged(boolean enabled) throws RemoteException {
1273            synchronized (mLock) {
1274                for (int i = 0; i < mConnections.size(); ++i) {
1275                    TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
1276                    if (impl != null) {
1277                        impl.handleAudioSinkUpdated();
1278                    }
1279                }
1280            }
1281        }
1282    }
1283}
1284