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