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