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