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