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