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