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