blob: 9bf7ae4465ad4430e37dcbe7580104dd0cefb8c9 [file] [log] [blame]
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001/*
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
17package com.android.server.tv;
18
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
Wonsik Kim102d0b72015-11-26 18:51:09 +090020import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
Wonsik Kim969167d2014-06-24 16:33:17 +090021import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
22
Wonsik Kim49e55932014-12-04 17:22:09 +090023import android.content.BroadcastReceiver;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000024import android.content.Context;
Wonsik Kim49e55932014-12-04 17:22:09 +090025import android.content.Intent;
26import android.content.IntentFilter;
Jungshik Jang61daf6b2014-08-08 11:38:28 +090027import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090028import android.hardware.hdmi.HdmiDeviceInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090029import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090030import android.hardware.hdmi.IHdmiControlService;
Wonsik Kim187423c2014-06-25 14:12:48 +090031import android.hardware.hdmi.IHdmiDeviceEventListener;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090032import android.hardware.hdmi.IHdmiHotplugEventListener;
Jungshik Jang1f819bb2014-08-08 10:46:54 +090033import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Wonsik Kimd7c29182014-05-27 10:38:21 +090034import android.media.AudioDevicePort;
Wonsik Kimca17a902014-07-31 10:09:46 +090035import android.media.AudioFormat;
Wonsik Kim8e45a332014-08-04 10:17:18 +090036import android.media.AudioGain;
37import android.media.AudioGainConfig;
Wonsik Kimd7c29182014-05-27 10:38:21 +090038import android.media.AudioManager;
39import android.media.AudioPatch;
40import android.media.AudioPort;
41import android.media.AudioPortConfig;
Jae Seod5cc4a22014-05-30 16:57:43 -070042import android.media.tv.ITvInputHardware;
43import android.media.tv.ITvInputHardwareCallback;
Jae Seo546c6352014-08-07 11:57:01 -070044import android.media.tv.TvInputHardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090045import android.media.tv.TvInputInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070046import android.media.tv.TvStreamConfig;
Wonsik Kim969167d2014-06-24 16:33:17 +090047import android.os.Handler;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000048import android.os.IBinder;
Wonsik Kim969167d2014-06-24 16:33:17 +090049import android.os.Message;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000050import android.os.RemoteException;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090051import android.os.ServiceManager;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090052import android.util.ArrayMap;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000053import android.util.Slog;
54import android.util.SparseArray;
Wonsik Kim969167d2014-06-24 16:33:17 +090055import android.util.SparseBooleanArray;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000056import android.view.KeyEvent;
57import android.view.Surface;
58
Wonsik Kim969167d2014-06-24 16:33:17 +090059import com.android.server.SystemService;
60
Wonsik Kimc22dbb62014-05-26 02:26:04 +000061import java.util.ArrayList;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090062import java.util.Collections;
Wonsik Kimd922a542014-07-24 18:25:29 +090063import java.util.Iterator;
64import java.util.LinkedList;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000065import java.util.List;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090066import java.util.Map;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000067
68/**
69 * A helper class for TvInputManagerService to handle TV input hardware.
70 *
71 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
72 * calls to tv_input HAL module.
73 *
74 * @hide
75 */
Jinsuk Kim7474fac2014-07-18 17:22:26 +090076class TvInputHardwareManager implements TvInputHal.Callback {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000077 private static final String TAG = TvInputHardwareManager.class.getSimpleName();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090078
Wonsik Kim49e55932014-12-04 17:22:09 +090079 private final Context mContext;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090080 private final Listener mListener;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000081 private final TvInputHal mHal = new TvInputHal(this);
Wonsik Kimd922a542014-07-24 18:25:29 +090082 private final SparseArray<Connection> mConnections = new SparseArray<>();
83 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
Jae Seo546c6352014-08-07 11:57:01 -070084 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090085 /* A map from a device ID to the matching TV input ID. */
Wonsik Kimd922a542014-07-24 18:25:29 +090086 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090087 /* A map from a HDMI logical address to the matching TV input ID. */
Jae Seo546c6352014-08-07 11:57:01 -070088 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
Wonsik Kimd922a542014-07-24 18:25:29 +090089 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090090
Wonsik Kimd7c29182014-05-27 10:38:21 +090091 private final AudioManager mAudioManager;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090092 private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
93 new HdmiHotplugEventListener();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090094 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
Jungshik Jang1f819bb2014-08-08 10:46:54 +090095 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
96 new HdmiSystemAudioModeChangeListener();
Wonsik Kim49e55932014-12-04 17:22:09 +090097 private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() {
98 @Override
99 public void onReceive(Context context, Intent intent) {
100 handleVolumeChange(context, intent);
101 }
102 };
103 private int mCurrentIndex = 0;
104 private int mCurrentMaxIndex = 0;
Jinsuk Kimd38bf472014-08-11 11:29:18 +0900105
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900106 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
Wonsik Kimd922a542014-07-24 18:25:29 +0900107 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900108
Wonsik Kim187423c2014-06-25 14:12:48 +0900109 // Calls to mListener should happen here.
110 private final Handler mHandler = new ListenerHandler();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000111
112 private final Object mLock = new Object();
113
Wonsik Kim187423c2014-06-25 14:12:48 +0900114 public TvInputHardwareManager(Context context, Listener listener) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900115 mContext = context;
Wonsik Kim187423c2014-06-25 14:12:48 +0900116 mListener = listener;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900117 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000118 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +0900119 }
120
121 public void onBootPhase(int phase) {
122 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Jae Seo38b32572015-06-05 14:43:11 -0700123 IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
124 ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
125 if (hdmiControlService != null) {
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900126 try {
Jae Seo38b32572015-06-05 14:43:11 -0700127 hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
128 hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
129 hdmiControlService.addSystemAudioModeChangeListener(
Jungshik Jang1f819bb2014-08-08 10:46:54 +0900130 mHdmiSystemAudioModeChangeListener);
Jae Seo38b32572015-06-05 14:43:11 -0700131 mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900132 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900133 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900134 }
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900135 } else {
136 Slog.w(TAG, "HdmiControlService is not available");
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900137 }
John Spurlockee5ad722015-03-03 16:17:21 -0500138 final IntentFilter filter = new IntentFilter();
139 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
140 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
141 mContext.registerReceiver(mVolumeReceiver, filter);
Wonsik Kim49e55932014-12-04 17:22:09 +0900142 updateVolume();
Wonsik Kim969167d2014-06-24 16:33:17 +0900143 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000144 }
145
146 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900147 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000148 synchronized (mLock) {
149 Connection connection = new Connection(info);
150 connection.updateConfigsLocked(configs);
151 mConnections.put(info.getDeviceId(), connection);
Wonsik Kimd922a542014-07-24 18:25:29 +0900152 buildHardwareListLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900153 mHandler.obtainMessage(
154 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
Wonsik Kimd922a542014-07-24 18:25:29 +0900155 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
156 processPendingHdmiDeviceEventsLocked();
157 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000158 }
159 }
160
Wonsik Kimd922a542014-07-24 18:25:29 +0900161 private void buildHardwareListLocked() {
162 mHardwareList.clear();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000163 for (int i = 0; i < mConnections.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900164 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000165 }
166 }
167
168 @Override
169 public void onDeviceUnavailable(int deviceId) {
170 synchronized (mLock) {
171 Connection connection = mConnections.get(deviceId);
172 if (connection == null) {
173 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
174 return;
175 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900176 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000177 mConnections.remove(deviceId);
Wonsik Kimd922a542014-07-24 18:25:29 +0900178 buildHardwareListLocked();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900179 TvInputHardwareInfo info = connection.getHardwareInfoLocked();
Wonsik Kimd922a542014-07-24 18:25:29 +0900180 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Jae Seo546c6352014-08-07 11:57:01 -0700181 // Remove HDMI devices linked with this hardware.
182 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900183 HdmiDeviceInfo deviceInfo = it.next();
Wonsik Kimd922a542014-07-24 18:25:29 +0900184 if (deviceInfo.getPortId() == info.getHdmiPortId()) {
Jae Seo546c6352014-08-07 11:57:01 -0700185 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
Wonsik Kimd922a542014-07-24 18:25:29 +0900186 deviceInfo).sendToTarget();
187 it.remove();
188 }
189 }
190 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900191 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900192 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000193 }
194 }
195
196 @Override
197 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
198 synchronized (mLock) {
199 Connection connection = mConnections.get(deviceId);
200 if (connection == null) {
201 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
202 + deviceId);
203 return;
204 }
205 connection.updateConfigsLocked(configs);
Wonsik Kim9a103652014-10-21 17:46:31 +0900206 String inputId = mHardwareInputIdMap.get(deviceId);
207 if (inputId != null) {
208 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim102d0b72015-11-26 18:51:09 +0900209 obtainStateFromConfigs(configs), 0, inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900210 }
Dongwon Kang017bb852014-12-26 14:53:14 +0900211 ITvInputHardwareCallback callback = connection.getCallbackLocked();
212 if (callback != null) {
213 try {
214 callback.onStreamConfigChanged(configs);
215 } catch (RemoteException e) {
216 Slog.e(TAG, "error in onStreamConfigurationChanged", e);
217 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000218 }
219 }
220 }
221
Terry Heoc086a3d2014-06-18 14:26:44 +0900222 @Override
223 public void onFirstFrameCaptured(int deviceId, int streamId) {
224 synchronized (mLock) {
225 Connection connection = mConnections.get(deviceId);
226 if (connection == null) {
227 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
228 + deviceId);
229 return;
230 }
231 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
232 if (runnable != null) {
233 runnable.run();
234 connection.setOnFirstFrameCapturedLocked(null);
235 }
236 }
237 }
238
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000239 public List<TvInputHardwareInfo> getHardwareList() {
240 synchronized (mLock) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900241 return Collections.unmodifiableList(mHardwareList);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000242 }
243 }
244
Jae Seo546c6352014-08-07 11:57:01 -0700245 public List<HdmiDeviceInfo> getHdmiDeviceList() {
Wonsik Kimd922a542014-07-24 18:25:29 +0900246 synchronized (mLock) {
Jae Seo546c6352014-08-07 11:57:01 -0700247 return Collections.unmodifiableList(mHdmiDeviceList);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900248 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900249 }
250
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900251 private boolean checkUidChangedLocked(
252 Connection connection, int callingUid, int resolvedUserId) {
253 Integer connectionCallingUid = connection.getCallingUidLocked();
254 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700255 return connectionCallingUid == null || connectionResolvedUserId == null
256 || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId;
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900257 }
258
Wonsik Kim102d0b72015-11-26 18:51:09 +0900259 private int obtainStateFromConfigs(TvStreamConfig[] configs) {
260 for (TvStreamConfig config : configs) {
261 if ((config.getFlags() & TvStreamConfig.FLAG_MASK_SIGNAL_DETECTION) != 0) {
262 return INPUT_STATE_CONNECTED;
263 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900264 }
Wonsik Kim102d0b72015-11-26 18:51:09 +0900265 return (configs.length > 0) ? INPUT_STATE_CONNECTED_STANDBY : INPUT_STATE_DISCONNECTED;
Wonsik Kim969167d2014-06-24 16:33:17 +0900266 }
267
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900268 public void addHardwareTvInput(int deviceId, TvInputInfo info) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900269 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900270 String oldInputId = mHardwareInputIdMap.get(deviceId);
271 if (oldInputId != null) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900272 Slog.w(TAG, "Trying to override previous registration: old = "
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900273 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
Wonsik Kim969167d2014-06-24 16:33:17 +0900274 + info + ":" + deviceId);
275 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900276 mHardwareInputIdMap.put(deviceId, info.getId());
277 mInputMap.put(info.getId(), info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900278
Wonsik Kim9a103652014-10-21 17:46:31 +0900279 // Process pending state changes
280
281 // For logical HDMI devices, they have information from HDMI CEC signals.
Wonsik Kim969167d2014-06-24 16:33:17 +0900282 for (int i = 0; i < mHdmiStateMap.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900283 TvInputHardwareInfo hardwareInfo =
284 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
285 if (hardwareInfo == null) {
286 continue;
287 }
288 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900289 if (inputId != null && inputId.equals(info.getId())) {
Wonsik Kim102d0b72015-11-26 18:51:09 +0900290 // No HDMI hotplug does not necessarily mean disconnected, as old devices may
291 // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to
292 // denote unknown state.
293 int state = mHdmiStateMap.valueAt(i)
294 ? INPUT_STATE_CONNECTED
295 : INPUT_STATE_CONNECTED_STANDBY;
296 mHandler.obtainMessage(
297 ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900298 return;
Wonsik Kim969167d2014-06-24 16:33:17 +0900299 }
300 }
Wonsik Kim9a103652014-10-21 17:46:31 +0900301 // For the rest of the devices, we can tell by the number of available streams.
302 Connection connection = mConnections.get(deviceId);
303 if (connection != null) {
304 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim102d0b72015-11-26 18:51:09 +0900305 obtainStateFromConfigs(connection.getConfigsLocked()), 0,
Wonsik Kim9a103652014-10-21 17:46:31 +0900306 info.getId()).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900307 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900308 }
309 }
310
Wonsik Kim38feae92014-07-21 21:35:50 +0900311 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
312 for (int i = 0; i < map.size(); ++i) {
313 if (map.valueAt(i).equals(value)) {
314 return i;
315 }
316 }
317 return -1;
318 }
319
Wonsik Kim71dfa962014-11-15 14:18:49 +0900320 private static boolean intArrayContains(int[] array, int value) {
321 for (int element : array) {
322 if (element == value) return true;
323 }
324 return false;
325 }
326
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900327 public void addHdmiTvInput(int id, TvInputInfo info) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900328 if (info.getType() != TvInputInfo.TYPE_HDMI) {
329 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
330 }
331 synchronized (mLock) {
332 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900333 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
334 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900335 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
336 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900337 String oldInputId = mHdmiInputIdMap.get(id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900338 if (oldInputId != null) {
339 Slog.w(TAG, "Trying to override previous registration: old = "
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900340 + mInputMap.get(oldInputId) + ":" + id + ", new = "
341 + info + ":" + id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900342 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900343 mHdmiInputIdMap.put(id, info.getId());
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900344 mInputMap.put(info.getId(), info);
345 }
346 }
347
348 public void removeTvInput(String inputId) {
349 synchronized (mLock) {
350 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900351 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900352 if (hardwareIndex >= 0) {
353 mHardwareInputIdMap.removeAt(hardwareIndex);
354 }
Jae Seo546c6352014-08-07 11:57:01 -0700355 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
356 if (deviceIndex >= 0) {
357 mHdmiInputIdMap.removeAt(deviceIndex);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900358 }
359 }
360 }
361
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000362 /**
363 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
364 * the object, and if more than one process attempts to create hardware with the same deviceId,
365 * the latest service will get the object and all the other hardware are released. The
366 * release is notified via ITvInputHardwareCallback.onReleased().
367 */
368 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900369 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000370 if (callback == null) {
371 throw new NullPointerException();
372 }
373 synchronized (mLock) {
374 Connection connection = mConnections.get(deviceId);
375 if (connection == null) {
376 Slog.e(TAG, "Invalid deviceId : " + deviceId);
377 return null;
378 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900379 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900380 TvInputHardwareImpl hardware =
381 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000382 try {
383 callback.asBinder().linkToDeath(connection, 0);
384 } catch (RemoteException e) {
385 hardware.release();
386 return null;
387 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900388 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000389 }
390 return connection.getHardwareLocked();
391 }
392 }
393
394 /**
395 * Release the specified hardware.
396 */
397 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
398 int resolvedUserId) {
399 synchronized (mLock) {
400 Connection connection = mConnections.get(deviceId);
401 if (connection == null) {
402 Slog.e(TAG, "Invalid deviceId : " + deviceId);
403 return;
404 }
405 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900406 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000407 return;
408 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900409 connection.resetLocked(null, null, null, null, null);
410 }
411 }
412
Wonsik Kimd922a542014-07-24 18:25:29 +0900413 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
414 for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900415 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
416 && hardwareInfo.getHdmiPortId() == port) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900417 return hardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +0900418 }
419 }
420 return null;
421 }
422
Terry Heoc086a3d2014-06-18 14:26:44 +0900423 private int findDeviceIdForInputIdLocked(String inputId) {
424 for (int i = 0; i < mConnections.size(); ++i) {
425 Connection connection = mConnections.get(i);
426 if (connection.getInfoLocked().getId().equals(inputId)) {
427 return i;
428 }
429 }
430 return -1;
431 }
432
433 /**
434 * Get the list of TvStreamConfig which is buffered mode.
435 */
436 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
437 int resolvedUserId) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700438 List<TvStreamConfig> configsList = new ArrayList<>();
Terry Heoc086a3d2014-06-18 14:26:44 +0900439 synchronized (mLock) {
440 int deviceId = findDeviceIdForInputIdLocked(inputId);
441 if (deviceId < 0) {
442 Slog.e(TAG, "Invalid inputId : " + inputId);
443 return configsList;
444 }
445 Connection connection = mConnections.get(deviceId);
446 for (TvStreamConfig config : connection.getConfigsLocked()) {
447 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
448 configsList.add(config);
449 }
450 }
451 }
452 return configsList;
453 }
454
455 /**
456 * Take a snapshot of the given TV input into the provided Surface.
457 */
458 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
459 int callingUid, int resolvedUserId) {
460 synchronized (mLock) {
461 int deviceId = findDeviceIdForInputIdLocked(inputId);
462 if (deviceId < 0) {
463 Slog.e(TAG, "Invalid inputId : " + inputId);
464 return false;
465 }
466 Connection connection = mConnections.get(deviceId);
467 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
468 if (hardwareImpl != null) {
469 // Stop previous capture.
470 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
471 if (runnable != null) {
472 runnable.run();
473 connection.setOnFirstFrameCapturedLocked(null);
474 }
475
476 boolean result = hardwareImpl.startCapture(surface, config);
477 if (result) {
478 connection.setOnFirstFrameCapturedLocked(new Runnable() {
479 @Override
480 public void run() {
481 hardwareImpl.stopCapture(config);
482 }
483 });
484 }
485 return result;
486 }
487 }
488 return false;
489 }
490
Wonsik Kimd922a542014-07-24 18:25:29 +0900491 private void processPendingHdmiDeviceEventsLocked() {
492 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
493 Message msg = it.next();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900494 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
Wonsik Kimd922a542014-07-24 18:25:29 +0900495 TvInputHardwareInfo hardwareInfo =
496 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
497 if (hardwareInfo != null) {
498 msg.sendToTarget();
499 it.remove();
500 }
501 }
502 }
503
Wonsik Kim49e55932014-12-04 17:22:09 +0900504 private void updateVolume() {
505 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
506 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
507 }
508
509 private void handleVolumeChange(Context context, Intent intent) {
510 String action = intent.getAction();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700511 switch (action) {
512 case AudioManager.VOLUME_CHANGED_ACTION: {
513 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
514 if (streamType != AudioManager.STREAM_MUSIC) {
515 return;
516 }
517 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
518 if (index == mCurrentIndex) {
519 return;
520 }
521 mCurrentIndex = index;
522 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900523 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700524 case AudioManager.STREAM_MUTE_CHANGED_ACTION: {
525 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
526 if (streamType != AudioManager.STREAM_MUSIC) {
527 return;
528 }
529 // volume index will be updated at onMediaStreamVolumeChanged() through
530 // updateVolume().
531 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900532 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700533 default:
534 Slog.w(TAG, "Unrecognized intent: " + intent);
Wonsik Kim49e55932014-12-04 17:22:09 +0900535 return;
Wonsik Kim49e55932014-12-04 17:22:09 +0900536 }
537 synchronized (mLock) {
538 for (int i = 0; i < mConnections.size(); ++i) {
539 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
540 if (hardwareImpl != null) {
541 hardwareImpl.onMediaStreamVolumeChanged();
542 }
543 }
544 }
545 }
546
547 private float getMediaStreamVolume() {
John Spurlockee5ad722015-03-03 16:17:21 -0500548 return (float) mCurrentIndex / (float) mCurrentMaxIndex;
Wonsik Kim49e55932014-12-04 17:22:09 +0900549 }
550
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000551 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900552 private final TvInputHardwareInfo mHardwareInfo;
553 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000554 private TvInputHardwareImpl mHardware = null;
555 private ITvInputHardwareCallback mCallback;
556 private TvStreamConfig[] mConfigs = null;
557 private Integer mCallingUid = null;
558 private Integer mResolvedUserId = null;
Terry Heoc086a3d2014-06-18 14:26:44 +0900559 private Runnable mOnFirstFrameCaptured;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000560
Wonsik Kim969167d2014-06-24 16:33:17 +0900561 public Connection(TvInputHardwareInfo hardwareInfo) {
562 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000563 }
564
565 // *Locked methods assume TvInputHardwareManager.mLock is held.
566
Wonsik Kim969167d2014-06-24 16:33:17 +0900567 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
568 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000569 if (mHardware != null) {
570 try {
571 mCallback.onReleased();
572 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900573 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000574 }
575 mHardware.release();
576 }
577 mHardware = hardware;
578 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900579 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000580 mCallingUid = callingUid;
581 mResolvedUserId = resolvedUserId;
Terry Heoc086a3d2014-06-18 14:26:44 +0900582 mOnFirstFrameCaptured = null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000583
584 if (mHardware != null && mCallback != null) {
585 try {
586 mCallback.onStreamConfigChanged(getConfigsLocked());
587 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900588 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000589 }
590 }
591 }
592
593 public void updateConfigsLocked(TvStreamConfig[] configs) {
594 mConfigs = configs;
595 }
596
Wonsik Kim969167d2014-06-24 16:33:17 +0900597 public TvInputHardwareInfo getHardwareInfoLocked() {
598 return mHardwareInfo;
599 }
600
601 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000602 return mInfo;
603 }
604
605 public ITvInputHardware getHardwareLocked() {
606 return mHardware;
607 }
608
Terry Heoc086a3d2014-06-18 14:26:44 +0900609 public TvInputHardwareImpl getHardwareImplLocked() {
610 return mHardware;
611 }
612
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000613 public ITvInputHardwareCallback getCallbackLocked() {
614 return mCallback;
615 }
616
617 public TvStreamConfig[] getConfigsLocked() {
618 return mConfigs;
619 }
620
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900621 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000622 return mCallingUid;
623 }
624
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900625 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000626 return mResolvedUserId;
627 }
628
Terry Heoc086a3d2014-06-18 14:26:44 +0900629 public void setOnFirstFrameCapturedLocked(Runnable runnable) {
630 mOnFirstFrameCaptured = runnable;
631 }
632
633 public Runnable getOnFirstFrameCapturedLocked() {
634 return mOnFirstFrameCaptured;
635 }
636
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000637 @Override
638 public void binderDied() {
639 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900640 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000641 }
642 }
643 }
644
645 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
646 private final TvInputHardwareInfo mInfo;
647 private boolean mReleased = false;
648 private final Object mImplLock = new Object();
649
Wonsik Kim06c41a32014-08-04 18:57:47 +0900650 private final AudioManager.OnAudioPortUpdateListener mAudioListener =
651 new AudioManager.OnAudioPortUpdateListener() {
652 @Override
653 public void onAudioPortListUpdate(AudioPort[] portList) {
654 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900655 updateAudioConfigLocked();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900656 }
657 }
658
659 @Override
660 public void onAudioPatchListUpdate(AudioPatch[] patchList) {
661 // No-op
662 }
663
664 @Override
665 public void onServiceDied() {
666 synchronized (mImplLock) {
667 mAudioSource = null;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900668 mAudioSink.clear();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900669 mAudioPatch = null;
670 }
671 }
672 };
673 private int mOverrideAudioType = AudioManager.DEVICE_NONE;
674 private String mOverrideAudioAddress = "";
675 private AudioDevicePort mAudioSource;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900676 private List<AudioDevicePort> mAudioSink = new ArrayList<>();
Wonsik Kimd7c29182014-05-27 10:38:21 +0900677 private AudioPatch mAudioPatch = null;
Wonsik Kim485e7e12015-01-26 17:28:55 +0900678 // Set to an invalid value for a volume, so that current volume can be applied at the
679 // first call to updateAudioConfigLocked().
680 private float mCommittedVolume = -1f;
Wonsik Kim49e55932014-12-04 17:22:09 +0900681 private float mSourceVolume = 0.0f;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900682
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900683 private TvStreamConfig mActiveConfig = null;
684
Wonsik Kimca17a902014-07-31 10:09:46 +0900685 private int mDesiredSamplingRate = 0;
686 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
687 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
688
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000689 public TvInputHardwareImpl(TvInputHardwareInfo info) {
690 mInfo = info;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900691 mAudioManager.registerAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900692 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900693 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900694 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kimca17a902014-07-31 10:09:46 +0900695 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900696 }
697
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900698 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
699 sinks.clear();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700700 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900701 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
702 return;
703 }
704 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
Paul McLeanceb47aa2015-02-19 15:09:53 -0800705 for (AudioDevicePort port : devicePorts) {
706 if ((port.type() & sinkDevice) != 0) {
707 sinks.add(port);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900708 }
709 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900710 }
711
712 private AudioDevicePort findAudioDevicePort(int type, String address) {
713 if (type == AudioManager.DEVICE_NONE) {
714 return null;
715 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700716 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900717 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
718 return null;
719 }
Paul McLeanceb47aa2015-02-19 15:09:53 -0800720 for (AudioDevicePort port : devicePorts) {
721 if (port.type() == type && port.address().equals(address)) {
722 return port;
Wonsik Kimca17a902014-07-31 10:09:46 +0900723 }
724 }
725 return null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000726 }
727
728 public void release() {
729 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900730 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900731 if (mAudioPatch != null) {
732 mAudioManager.releaseAudioPatch(mAudioPatch);
733 mAudioPatch = null;
734 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000735 mReleased = true;
736 }
737 }
738
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900739 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
740 // attempts to call setSurface with different TvStreamConfig objects, the last call will
741 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000742 @Override
743 public boolean setSurface(Surface surface, TvStreamConfig config)
744 throws RemoteException {
745 synchronized (mImplLock) {
746 if (mReleased) {
747 throw new IllegalStateException("Device already released.");
748 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900749
Wonsik Kim102670f2014-11-20 14:38:02 +0900750 int result = TvInputHal.SUCCESS;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900751 if (surface == null) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900752 // The value of config is ignored when surface == null.
753 if (mActiveConfig != null) {
754 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
755 mActiveConfig = null;
756 } else {
757 // We already have no active stream.
758 return true;
759 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900760 } else {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900761 // It's impossible to set a non-null surface with a null config.
762 if (config == null) {
763 return false;
764 }
765 // Remove stream only if we have an existing active configuration.
766 if (mActiveConfig != null && !config.equals(mActiveConfig)) {
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900767 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
768 if (result != TvInputHal.SUCCESS) {
769 mActiveConfig = null;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900770 }
771 }
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900772 // Proceed only if all previous operations succeeded.
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900773 if (result == TvInputHal.SUCCESS) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900774 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
775 if (result == TvInputHal.SUCCESS) {
776 mActiveConfig = config;
777 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900778 }
779 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900780 updateAudioConfigLocked();
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900781 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000782 }
783 }
784
Wonsik Kim7587d292014-08-09 10:01:01 +0900785 /**
786 * Update audio configuration (source, sink, patch) all up to current state.
787 */
788 private void updateAudioConfigLocked() {
789 boolean sinkUpdated = updateAudioSinkLocked();
790 boolean sourceUpdated = updateAudioSourceLocked();
791 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
792 // because Java won't evaluate the latter if the former is true.
793
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900794 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900795 if (mAudioPatch != null) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900796 mAudioManager.releaseAudioPatch(mAudioPatch);
797 mAudioPatch = null;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900798 }
799 return;
800 }
801
Wonsik Kim49e55932014-12-04 17:22:09 +0900802 updateVolume();
803 float volume = mSourceVolume * getMediaStreamVolume();
Wonsik Kim8e45a332014-08-04 10:17:18 +0900804 AudioGainConfig sourceGainConfig = null;
Wonsik Kim49e55932014-12-04 17:22:09 +0900805 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
Wonsik Kim8e45a332014-08-04 10:17:18 +0900806 AudioGain sourceGain = null;
807 for (AudioGain gain : mAudioSource.gains()) {
808 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
809 sourceGain = gain;
810 break;
811 }
812 }
813 // NOTE: we only change the source gain in MODE_JOINT here.
814 if (sourceGain != null) {
815 int steps = (sourceGain.maxValue() - sourceGain.minValue())
816 / sourceGain.stepValue();
817 int gainValue = sourceGain.minValue();
Wonsik Kim49e55932014-12-04 17:22:09 +0900818 if (volume < 1.0f) {
819 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900820 } else {
821 gainValue = sourceGain.maxValue();
822 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900823 // size of gain values is 1 in MODE_JOINT
824 int[] gainValues = new int[] { gainValue };
Wonsik Kim8e45a332014-08-04 10:17:18 +0900825 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
826 sourceGain.channelMask(), gainValues, 0);
827 } else {
828 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
829 }
830 }
831
Wonsik Kimca17a902014-07-31 10:09:46 +0900832 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900833 List<AudioPortConfig> sinkConfigs = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900834 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
Wonsik Kim7587d292014-08-09 10:01:01 +0900835 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900836
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900837 for (AudioDevicePort audioSink : mAudioSink) {
838 AudioPortConfig sinkConfig = audioSink.activeConfig();
839 int sinkSamplingRate = mDesiredSamplingRate;
840 int sinkChannelMask = mDesiredChannelMask;
841 int sinkFormat = mDesiredFormat;
842 // If sinkConfig != null and values are set to default,
843 // fill in the sinkConfig values.
844 if (sinkConfig != null) {
845 if (sinkSamplingRate == 0) {
846 sinkSamplingRate = sinkConfig.samplingRate();
847 }
848 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
849 sinkChannelMask = sinkConfig.channelMask();
850 }
851 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
852 sinkChannelMask = sinkConfig.format();
853 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900854 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900855
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900856 if (sinkConfig == null
857 || sinkConfig.samplingRate() != sinkSamplingRate
858 || sinkConfig.channelMask() != sinkChannelMask
859 || sinkConfig.format() != sinkFormat) {
860 // Check for compatibility and reset to default if necessary.
861 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
862 && audioSink.samplingRates().length > 0) {
863 sinkSamplingRate = audioSink.samplingRates()[0];
864 }
865 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
866 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
867 }
868 if (!intArrayContains(audioSink.formats(), sinkFormat)) {
869 sinkFormat = AudioFormat.ENCODING_DEFAULT;
870 }
871 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
872 sinkFormat, null);
873 shouldRecreateAudioPatch = true;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900874 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900875 sinkConfigs.add(sinkConfig);
Wonsik Kimca17a902014-07-31 10:09:46 +0900876 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900877 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
878 // non-empty at the beginning of this method.
879 AudioPortConfig sinkConfig = sinkConfigs.get(0);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900880 if (sourceConfig == null || sourceGainConfig != null) {
Wonsik Kim71dfa962014-11-15 14:18:49 +0900881 int sourceSamplingRate = 0;
882 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
883 sourceSamplingRate = sinkConfig.samplingRate();
884 } else if (mAudioSource.samplingRates().length > 0) {
885 // Use any sampling rate and hope audio patch can handle resampling...
886 sourceSamplingRate = mAudioSource.samplingRates()[0];
887 }
888 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
889 for (int inChannelMask : mAudioSource.channelMasks()) {
890 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
891 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
892 sourceChannelMask = inChannelMask;
893 break;
894 }
895 }
896 int sourceFormat = AudioFormat.ENCODING_DEFAULT;
897 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
898 sourceFormat = sinkConfig.format();
899 }
900 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
901 sourceFormat, sourceGainConfig);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900902 shouldRecreateAudioPatch = true;
Wonsik Kimca17a902014-07-31 10:09:46 +0900903 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900904 if (shouldRecreateAudioPatch) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900905 mCommittedVolume = volume;
Wonsik Kim8e45a332014-08-04 10:17:18 +0900906 mAudioManager.createAudioPatch(
907 audioPatchArray,
908 new AudioPortConfig[] { sourceConfig },
Jae Seo93ff14b2015-06-21 14:08:54 -0700909 sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
Wonsik Kim8e45a332014-08-04 10:17:18 +0900910 mAudioPatch = audioPatchArray[0];
Wonsik Kim71dfa962014-11-15 14:18:49 +0900911 if (sourceGainConfig != null) {
912 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
913 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900914 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900915 }
916
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000917 @Override
Wonsik Kim8e45a332014-08-04 10:17:18 +0900918 public void setStreamVolume(float volume) throws RemoteException {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000919 synchronized (mImplLock) {
920 if (mReleased) {
921 throw new IllegalStateException("Device already released.");
922 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900923 mSourceVolume = volume;
Wonsik Kim7587d292014-08-09 10:01:01 +0900924 updateAudioConfigLocked();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000925 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000926 }
927
928 @Override
929 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
930 synchronized (mImplLock) {
931 if (mReleased) {
932 throw new IllegalStateException("Device already released.");
933 }
934 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900935 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000936 return false;
937 }
938 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
939 return false;
940 }
Terry Heoc086a3d2014-06-18 14:26:44 +0900941
942 private boolean startCapture(Surface surface, TvStreamConfig config) {
943 synchronized (mImplLock) {
944 if (mReleased) {
945 return false;
946 }
947 if (surface == null || config == null) {
948 return false;
949 }
950 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
951 return false;
952 }
953
Wonsik Kim8f24a8b2014-10-22 16:27:39 +0900954 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
Terry Heoc086a3d2014-06-18 14:26:44 +0900955 return result == TvInputHal.SUCCESS;
956 }
957 }
958
959 private boolean stopCapture(TvStreamConfig config) {
960 synchronized (mImplLock) {
961 if (mReleased) {
962 return false;
963 }
964 if (config == null) {
965 return false;
966 }
967
968 int result = mHal.removeStream(mInfo.getDeviceId(), config);
969 return result == TvInputHal.SUCCESS;
970 }
971 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900972
Wonsik Kim7587d292014-08-09 10:01:01 +0900973 private boolean updateAudioSourceLocked() {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900974 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900975 return false;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900976 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900977 AudioDevicePort previousSource = mAudioSource;
978 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
979 return mAudioSource == null ? (previousSource != null)
980 : !mAudioSource.equals(previousSource);
981 }
982
983 private boolean updateAudioSinkLocked() {
984 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
985 return false;
986 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900987 List<AudioDevicePort> previousSink = mAudioSink;
988 mAudioSink = new ArrayList<>();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900989 if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900990 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900991 } else {
992 AudioDevicePort audioSink =
993 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
994 if (audioSink != null) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900995 mAudioSink.add(audioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900996 }
997 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900998
999 // Returns true if mAudioSink and previousSink differs.
1000 if (mAudioSink.size() != previousSink.size()) {
1001 return true;
1002 }
1003 previousSink.removeAll(mAudioSink);
1004 return !previousSink.isEmpty();
Wonsik Kim06c41a32014-08-04 18:57:47 +09001005 }
1006
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001007 private void handleAudioSinkUpdated() {
1008 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +09001009 updateAudioConfigLocked();
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001010 }
1011 }
1012
Wonsik Kimca17a902014-07-31 10:09:46 +09001013 @Override
1014 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1015 int channelMask, int format) {
1016 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +09001017 mOverrideAudioType = audioType;
1018 mOverrideAudioAddress = audioAddress;
Wonsik Kim06c41a32014-08-04 18:57:47 +09001019
Wonsik Kimca17a902014-07-31 10:09:46 +09001020 mDesiredSamplingRate = samplingRate;
1021 mDesiredChannelMask = channelMask;
1022 mDesiredFormat = format;
1023
Wonsik Kim7587d292014-08-09 10:01:01 +09001024 updateAudioConfigLocked();
Wonsik Kimca17a902014-07-31 10:09:46 +09001025 }
1026 }
Wonsik Kim49e55932014-12-04 17:22:09 +09001027
1028 public void onMediaStreamVolumeChanged() {
1029 synchronized (mImplLock) {
1030 updateAudioConfigLocked();
1031 }
1032 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001033 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001034
Wonsik Kim187423c2014-06-25 14:12:48 +09001035 interface Listener {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001036 void onStateChanged(String inputId, int state);
1037 void onHardwareDeviceAdded(TvInputHardwareInfo info);
1038 void onHardwareDeviceRemoved(TvInputHardwareInfo info);
1039 void onHdmiDeviceAdded(HdmiDeviceInfo device);
1040 void onHdmiDeviceRemoved(HdmiDeviceInfo device);
1041 void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
Wonsik Kim187423c2014-06-25 14:12:48 +09001042 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001043
Wonsik Kim187423c2014-06-25 14:12:48 +09001044 private class ListenerHandler extends Handler {
1045 private static final int STATE_CHANGED = 1;
1046 private static final int HARDWARE_DEVICE_ADDED = 2;
1047 private static final int HARDWARE_DEVICE_REMOVED = 3;
Jae Seo546c6352014-08-07 11:57:01 -07001048 private static final int HDMI_DEVICE_ADDED = 4;
1049 private static final int HDMI_DEVICE_REMOVED = 5;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001050 private static final int HDMI_DEVICE_UPDATED = 6;
Wonsik Kim969167d2014-06-24 16:33:17 +09001051
1052 @Override
1053 public final void handleMessage(Message msg) {
1054 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001055 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +09001056 String inputId = (String) msg.obj;
1057 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +09001058 mListener.onStateChanged(inputId, state);
1059 break;
1060 }
1061 case HARDWARE_DEVICE_ADDED: {
1062 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1063 mListener.onHardwareDeviceAdded(info);
1064 break;
1065 }
1066 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001067 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1068 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001069 break;
1070 }
Jae Seo546c6352014-08-07 11:57:01 -07001071 case HDMI_DEVICE_ADDED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001072 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001073 mListener.onHdmiDeviceAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001074 break;
1075 }
Jae Seo546c6352014-08-07 11:57:01 -07001076 case HDMI_DEVICE_REMOVED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001077 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001078 mListener.onHdmiDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +09001079 break;
1080 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001081 case HDMI_DEVICE_UPDATED: {
Wonsik Kim184a6d62014-10-08 13:05:56 +09001082 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001083 String inputId;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001084 synchronized (mLock) {
1085 inputId = mHdmiInputIdMap.get(info.getId());
1086 }
1087 if (inputId != null) {
1088 mListener.onHdmiDeviceUpdated(inputId, info);
1089 } else {
1090 Slog.w(TAG, "Could not resolve input ID matching the device info; "
1091 + "ignoring.");
1092 }
1093 break;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001094 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001095 default: {
1096 Slog.w(TAG, "Unhandled message: " + msg);
1097 break;
1098 }
1099 }
1100 }
1101 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001102
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001103 // Listener implementations for HdmiControlService
1104
1105 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1106 @Override
1107 public void onReceived(HdmiHotplugEvent event) {
1108 synchronized (mLock) {
1109 mHdmiStateMap.put(event.getPort(), event.isConnected());
Wonsik Kimd922a542014-07-24 18:25:29 +09001110 TvInputHardwareInfo hardwareInfo =
1111 findHardwareInfoForHdmiPortLocked(event.getPort());
1112 if (hardwareInfo == null) {
1113 return;
1114 }
1115 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001116 if (inputId == null) {
1117 return;
1118 }
Wonsik Kim102d0b72015-11-26 18:51:09 +09001119 // No HDMI hotplug does not necessarily mean disconnected, as old devices may
1120 // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to
1121 // denote unknown state.
1122 int state = event.isConnected()
1123 ? INPUT_STATE_CONNECTED
1124 : INPUT_STATE_CONNECTED_STANDBY;
1125 mHandler.obtainMessage(
1126 ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget();
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001127 }
1128 }
1129 }
1130
Wonsik Kim187423c2014-06-25 14:12:48 +09001131 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1132 @Override
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001133 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
Jinsuk Kim98d760e2014-12-23 07:01:51 +09001134 if (!deviceInfo.isSourceType()) return;
Wonsik Kimd922a542014-07-24 18:25:29 +09001135 synchronized (mLock) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001136 int messageType = 0;
Wonsik Kime92f8572014-08-12 18:30:24 +09001137 Object obj = null;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001138 switch (status) {
1139 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001140 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001141 mHdmiDeviceList.add(deviceInfo);
1142 } else {
1143 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1144 return;
1145 }
1146 messageType = ListenerHandler.HDMI_DEVICE_ADDED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001147 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001148 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001149 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001150 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001151 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1152 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001153 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1154 return;
1155 }
1156 messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001157 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001158 break;
1159 }
1160 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001161 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1162 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001163 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1164 return;
1165 }
1166 mHdmiDeviceList.add(deviceInfo);
1167 messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001168 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001169 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001170 }
1171 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001172
Wonsik Kime92f8572014-08-12 18:30:24 +09001173 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
Wonsik Kimd922a542014-07-24 18:25:29 +09001174 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1175 msg.sendToTarget();
1176 } else {
1177 mPendingHdmiDeviceEvents.add(msg);
1178 }
1179 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001180 }
Jungshik Jangcf445872014-08-20 15:06:01 +09001181
1182 private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1183 for (HdmiDeviceInfo info : mHdmiDeviceList) {
1184 if (info.getId() == id) {
1185 return info;
1186 }
1187 }
1188 return null;
1189 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001190 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001191
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001192 private final class HdmiSystemAudioModeChangeListener extends
1193 IHdmiSystemAudioModeChangeListener.Stub {
1194 @Override
1195 public void onStatusChanged(boolean enabled) throws RemoteException {
1196 synchronized (mLock) {
1197 for (int i = 0; i < mConnections.size(); ++i) {
1198 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
Jungshik Jang44bb3a52014-08-26 17:24:24 +09001199 if (impl != null) {
1200 impl.handleAudioSinkUpdated();
1201 }
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001202 }
1203 }
1204 }
1205 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001206}