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