blob: c5f41611e59c00fe560c43e54558a392e2d6bafc [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;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
Wonsik Kim49e55932014-12-04 17:22:09 +090022import android.content.BroadcastReceiver;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000023import android.content.Context;
Wonsik Kim49e55932014-12-04 17:22:09 +090024import android.content.Intent;
25import android.content.IntentFilter;
Jungshik Jang61daf6b2014-08-08 11:38:28 +090026import android.hardware.hdmi.HdmiControlManager;
Jungshik Jang61f4fbd2014-08-06 19:21:12 +090027import android.hardware.hdmi.HdmiDeviceInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090028import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090029import android.hardware.hdmi.IHdmiControlService;
Wonsik Kim187423c2014-06-25 14:12:48 +090030import android.hardware.hdmi.IHdmiDeviceEventListener;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090031import android.hardware.hdmi.IHdmiHotplugEventListener;
Jungshik Jang1f819bb2014-08-08 10:46:54 +090032import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
Wonsik Kimd7c29182014-05-27 10:38:21 +090033import android.media.AudioDevicePort;
Wonsik Kimca17a902014-07-31 10:09:46 +090034import android.media.AudioFormat;
Wonsik Kim8e45a332014-08-04 10:17:18 +090035import android.media.AudioGain;
36import android.media.AudioGainConfig;
Wonsik Kimd7c29182014-05-27 10:38:21 +090037import android.media.AudioManager;
38import android.media.AudioPatch;
39import android.media.AudioPort;
40import android.media.AudioPortConfig;
Jae Seod5cc4a22014-05-30 16:57:43 -070041import android.media.tv.ITvInputHardware;
42import android.media.tv.ITvInputHardwareCallback;
Jae Seo546c6352014-08-07 11:57:01 -070043import android.media.tv.TvInputHardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090044import android.media.tv.TvInputInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070045import android.media.tv.TvStreamConfig;
Wonsik Kim969167d2014-06-24 16:33:17 +090046import android.os.Handler;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000047import android.os.IBinder;
Wonsik Kim969167d2014-06-24 16:33:17 +090048import android.os.Message;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000049import android.os.RemoteException;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090050import android.os.ServiceManager;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090051import android.util.ArrayMap;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000052import android.util.Slog;
53import android.util.SparseArray;
Wonsik Kim969167d2014-06-24 16:33:17 +090054import android.util.SparseBooleanArray;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000055import android.view.KeyEvent;
56import android.view.Surface;
57
Wonsik Kime92f8572014-08-12 18:30:24 +090058import com.android.internal.os.SomeArgs;
Wonsik Kim969167d2014-06-24 16:33:17 +090059import com.android.server.SystemService;
60
Wonsik Kimc22dbb62014-05-26 02:26:04 +000061import java.util.ArrayList;
Wonsik Kim8e45a332014-08-04 10:17:18 +090062import java.util.Arrays;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090063import java.util.Collections;
Wonsik Kimd922a542014-07-24 18:25:29 +090064import java.util.Iterator;
65import java.util.LinkedList;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000066import java.util.List;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090067import java.util.Map;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000068
69/**
70 * A helper class for TvInputManagerService to handle TV input hardware.
71 *
72 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
73 * calls to tv_input HAL module.
74 *
75 * @hide
76 */
Jinsuk Kim7474fac2014-07-18 17:22:26 +090077class TvInputHardwareManager implements TvInputHal.Callback {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000078 private static final String TAG = TvInputHardwareManager.class.getSimpleName();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090079
Wonsik Kim49e55932014-12-04 17:22:09 +090080 private final Context mContext;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090081 private final Listener mListener;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000082 private final TvInputHal mHal = new TvInputHal(this);
Wonsik Kimd922a542014-07-24 18:25:29 +090083 private final SparseArray<Connection> mConnections = new SparseArray<>();
84 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
Jae Seo546c6352014-08-07 11:57:01 -070085 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090086 /* A map from a device ID to the matching TV input ID. */
Wonsik Kimd922a542014-07-24 18:25:29 +090087 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090088 /* A map from a HDMI logical address to the matching TV input ID. */
Jae Seo546c6352014-08-07 11:57:01 -070089 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
Wonsik Kimd922a542014-07-24 18:25:29 +090090 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090091
Wonsik Kimd7c29182014-05-27 10:38:21 +090092 private final AudioManager mAudioManager;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090093 private IHdmiControlService mHdmiControlService;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090094 private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
95 new HdmiHotplugEventListener();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090096 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
Jungshik Jang1f819bb2014-08-08 10:46:54 +090097 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
98 new HdmiSystemAudioModeChangeListener();
Wonsik Kim49e55932014-12-04 17:22:09 +090099 private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() {
100 @Override
101 public void onReceive(Context context, Intent intent) {
102 handleVolumeChange(context, intent);
103 }
104 };
105 private int mCurrentIndex = 0;
106 private int mCurrentMaxIndex = 0;
107 private final boolean mUseMasterVolume;
Jinsuk Kimd38bf472014-08-11 11:29:18 +0900108
Wonsik Kimd922a542014-07-24 18:25:29 +0900109 // TODO: Should handle STANDBY case.
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900110 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
Wonsik Kimd922a542014-07-24 18:25:29 +0900111 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900112
Wonsik Kim187423c2014-06-25 14:12:48 +0900113 // Calls to mListener should happen here.
114 private final Handler mHandler = new ListenerHandler();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000115
116 private final Object mLock = new Object();
117
Wonsik Kim187423c2014-06-25 14:12:48 +0900118 public TvInputHardwareManager(Context context, Listener listener) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900119 mContext = context;
Wonsik Kim187423c2014-06-25 14:12:48 +0900120 mListener = listener;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900121 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kim49e55932014-12-04 17:22:09 +0900122 mUseMasterVolume = mContext.getResources().getBoolean(
123 com.android.internal.R.bool.config_useMasterVolume);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000124 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +0900125 }
126
127 public void onBootPhase(int phase) {
128 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900129 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService(
130 Context.HDMI_CONTROL_SERVICE));
131 if (mHdmiControlService != null) {
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900132 try {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900133 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
134 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
Jungshik Jang1f819bb2014-08-08 10:46:54 +0900135 mHdmiControlService.addSystemAudioModeChangeListener(
136 mHdmiSystemAudioModeChangeListener);
Jae Seo546c6352014-08-07 11:57:01 -0700137 mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices());
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900138 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900139 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900140 }
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900141 } else {
142 Slog.w(TAG, "HdmiControlService is not available");
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900143 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900144 if (!mUseMasterVolume) {
145 final IntentFilter filter = new IntentFilter();
146 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
147 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
148 mContext.registerReceiver(mVolumeReceiver, filter);
149 }
150 updateVolume();
Wonsik Kim969167d2014-06-24 16:33:17 +0900151 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000152 }
153
154 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900155 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000156 synchronized (mLock) {
157 Connection connection = new Connection(info);
158 connection.updateConfigsLocked(configs);
159 mConnections.put(info.getDeviceId(), connection);
Wonsik Kimd922a542014-07-24 18:25:29 +0900160 buildHardwareListLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900161 mHandler.obtainMessage(
162 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
Wonsik Kimd922a542014-07-24 18:25:29 +0900163 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
164 processPendingHdmiDeviceEventsLocked();
165 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000166 }
167 }
168
Wonsik Kimd922a542014-07-24 18:25:29 +0900169 private void buildHardwareListLocked() {
170 mHardwareList.clear();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000171 for (int i = 0; i < mConnections.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900172 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000173 }
174 }
175
176 @Override
177 public void onDeviceUnavailable(int deviceId) {
178 synchronized (mLock) {
179 Connection connection = mConnections.get(deviceId);
180 if (connection == null) {
181 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
182 return;
183 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900184 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000185 mConnections.remove(deviceId);
Wonsik Kimd922a542014-07-24 18:25:29 +0900186 buildHardwareListLocked();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900187 TvInputHardwareInfo info = connection.getHardwareInfoLocked();
Wonsik Kimd922a542014-07-24 18:25:29 +0900188 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Jae Seo546c6352014-08-07 11:57:01 -0700189 // Remove HDMI devices linked with this hardware.
190 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900191 HdmiDeviceInfo deviceInfo = it.next();
Wonsik Kimd922a542014-07-24 18:25:29 +0900192 if (deviceInfo.getPortId() == info.getHdmiPortId()) {
Jae Seo546c6352014-08-07 11:57:01 -0700193 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
Wonsik Kimd922a542014-07-24 18:25:29 +0900194 deviceInfo).sendToTarget();
195 it.remove();
196 }
197 }
198 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900199 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900200 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000201 }
202 }
203
204 @Override
205 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
206 synchronized (mLock) {
207 Connection connection = mConnections.get(deviceId);
208 if (connection == null) {
209 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
210 + deviceId);
211 return;
212 }
213 connection.updateConfigsLocked(configs);
Wonsik Kim9a103652014-10-21 17:46:31 +0900214 String inputId = mHardwareInputIdMap.get(deviceId);
215 if (inputId != null) {
216 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim5b820a82014-10-27 11:02:10 +0900217 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900218 }
Dongwon Kang017bb852014-12-26 14:53:14 +0900219 ITvInputHardwareCallback callback = connection.getCallbackLocked();
220 if (callback != null) {
221 try {
222 callback.onStreamConfigChanged(configs);
223 } catch (RemoteException e) {
224 Slog.e(TAG, "error in onStreamConfigurationChanged", e);
225 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000226 }
227 }
228 }
229
Terry Heoc086a3d2014-06-18 14:26:44 +0900230 @Override
231 public void onFirstFrameCaptured(int deviceId, int streamId) {
232 synchronized (mLock) {
233 Connection connection = mConnections.get(deviceId);
234 if (connection == null) {
235 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
236 + deviceId);
237 return;
238 }
239 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
240 if (runnable != null) {
241 runnable.run();
242 connection.setOnFirstFrameCapturedLocked(null);
243 }
244 }
245 }
246
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000247 public List<TvInputHardwareInfo> getHardwareList() {
248 synchronized (mLock) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900249 return Collections.unmodifiableList(mHardwareList);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000250 }
251 }
252
Jae Seo546c6352014-08-07 11:57:01 -0700253 public List<HdmiDeviceInfo> getHdmiDeviceList() {
Wonsik Kimd922a542014-07-24 18:25:29 +0900254 synchronized (mLock) {
Jae Seo546c6352014-08-07 11:57:01 -0700255 return Collections.unmodifiableList(mHdmiDeviceList);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900256 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900257 }
258
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900259 private boolean checkUidChangedLocked(
260 Connection connection, int callingUid, int resolvedUserId) {
261 Integer connectionCallingUid = connection.getCallingUidLocked();
262 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
263 if (connectionCallingUid == null || connectionResolvedUserId == null) {
264 return true;
265 }
266 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
267 return true;
268 }
269 return false;
270 }
271
Wonsik Kim969167d2014-06-24 16:33:17 +0900272 private int convertConnectedToState(boolean connected) {
273 if (connected) {
274 return INPUT_STATE_CONNECTED;
275 } else {
276 return INPUT_STATE_DISCONNECTED;
277 }
278 }
279
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900280 public void addHardwareTvInput(int deviceId, TvInputInfo info) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900281 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900282 String oldInputId = mHardwareInputIdMap.get(deviceId);
283 if (oldInputId != null) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900284 Slog.w(TAG, "Trying to override previous registration: old = "
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900285 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
Wonsik Kim969167d2014-06-24 16:33:17 +0900286 + info + ":" + deviceId);
287 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900288 mHardwareInputIdMap.put(deviceId, info.getId());
289 mInputMap.put(info.getId(), info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900290
Wonsik Kim9a103652014-10-21 17:46:31 +0900291 // Process pending state changes
292
293 // For logical HDMI devices, they have information from HDMI CEC signals.
Wonsik Kim969167d2014-06-24 16:33:17 +0900294 for (int i = 0; i < mHdmiStateMap.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900295 TvInputHardwareInfo hardwareInfo =
296 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
297 if (hardwareInfo == null) {
298 continue;
299 }
300 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900301 if (inputId != null && inputId.equals(info.getId())) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900302 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim969167d2014-06-24 16:33:17 +0900303 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
304 inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900305 return;
Wonsik Kim969167d2014-06-24 16:33:17 +0900306 }
307 }
Wonsik Kim9a103652014-10-21 17:46:31 +0900308 // For the rest of the devices, we can tell by the number of available streams.
309 Connection connection = mConnections.get(deviceId);
310 if (connection != null) {
311 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
312 convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
313 info.getId()).sendToTarget();
314 return;
315 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900316 }
317 }
318
Wonsik Kim38feae92014-07-21 21:35:50 +0900319 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
320 for (int i = 0; i < map.size(); ++i) {
321 if (map.valueAt(i).equals(value)) {
322 return i;
323 }
324 }
325 return -1;
326 }
327
Wonsik Kim71dfa962014-11-15 14:18:49 +0900328 private static boolean intArrayContains(int[] array, int value) {
329 for (int element : array) {
330 if (element == value) return true;
331 }
332 return false;
333 }
334
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900335 public void addHdmiTvInput(int id, TvInputInfo info) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900336 if (info.getType() != TvInputInfo.TYPE_HDMI) {
337 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
338 }
339 synchronized (mLock) {
340 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900341 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
342 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900343 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
344 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900345 String oldInputId = mHdmiInputIdMap.get(id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900346 if (oldInputId != null) {
347 Slog.w(TAG, "Trying to override previous registration: old = "
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900348 + mInputMap.get(oldInputId) + ":" + id + ", new = "
349 + info + ":" + id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900350 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900351 mHdmiInputIdMap.put(id, info.getId());
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900352 mInputMap.put(info.getId(), info);
353 }
354 }
355
356 public void removeTvInput(String inputId) {
357 synchronized (mLock) {
358 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900359 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900360 if (hardwareIndex >= 0) {
361 mHardwareInputIdMap.removeAt(hardwareIndex);
362 }
Jae Seo546c6352014-08-07 11:57:01 -0700363 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
364 if (deviceIndex >= 0) {
365 mHdmiInputIdMap.removeAt(deviceIndex);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900366 }
367 }
368 }
369
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000370 /**
371 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
372 * the object, and if more than one process attempts to create hardware with the same deviceId,
373 * the latest service will get the object and all the other hardware are released. The
374 * release is notified via ITvInputHardwareCallback.onReleased().
375 */
376 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900377 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000378 if (callback == null) {
379 throw new NullPointerException();
380 }
381 synchronized (mLock) {
382 Connection connection = mConnections.get(deviceId);
383 if (connection == null) {
384 Slog.e(TAG, "Invalid deviceId : " + deviceId);
385 return null;
386 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900387 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900388 TvInputHardwareImpl hardware =
389 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000390 try {
391 callback.asBinder().linkToDeath(connection, 0);
392 } catch (RemoteException e) {
393 hardware.release();
394 return null;
395 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900396 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000397 }
398 return connection.getHardwareLocked();
399 }
400 }
401
402 /**
403 * Release the specified hardware.
404 */
405 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
406 int resolvedUserId) {
407 synchronized (mLock) {
408 Connection connection = mConnections.get(deviceId);
409 if (connection == null) {
410 Slog.e(TAG, "Invalid deviceId : " + deviceId);
411 return;
412 }
413 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900414 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000415 return;
416 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900417 connection.resetLocked(null, null, null, null, null);
418 }
419 }
420
Wonsik Kimd922a542014-07-24 18:25:29 +0900421 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
422 for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900423 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
424 && hardwareInfo.getHdmiPortId() == port) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900425 return hardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +0900426 }
427 }
428 return null;
429 }
430
Terry Heoc086a3d2014-06-18 14:26:44 +0900431 private int findDeviceIdForInputIdLocked(String inputId) {
432 for (int i = 0; i < mConnections.size(); ++i) {
433 Connection connection = mConnections.get(i);
434 if (connection.getInfoLocked().getId().equals(inputId)) {
435 return i;
436 }
437 }
438 return -1;
439 }
440
441 /**
442 * Get the list of TvStreamConfig which is buffered mode.
443 */
444 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
445 int resolvedUserId) {
446 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>();
447 synchronized (mLock) {
448 int deviceId = findDeviceIdForInputIdLocked(inputId);
449 if (deviceId < 0) {
450 Slog.e(TAG, "Invalid inputId : " + inputId);
451 return configsList;
452 }
453 Connection connection = mConnections.get(deviceId);
454 for (TvStreamConfig config : connection.getConfigsLocked()) {
455 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
456 configsList.add(config);
457 }
458 }
459 }
460 return configsList;
461 }
462
463 /**
464 * Take a snapshot of the given TV input into the provided Surface.
465 */
466 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
467 int callingUid, int resolvedUserId) {
468 synchronized (mLock) {
469 int deviceId = findDeviceIdForInputIdLocked(inputId);
470 if (deviceId < 0) {
471 Slog.e(TAG, "Invalid inputId : " + inputId);
472 return false;
473 }
474 Connection connection = mConnections.get(deviceId);
475 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
476 if (hardwareImpl != null) {
477 // Stop previous capture.
478 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
479 if (runnable != null) {
480 runnable.run();
481 connection.setOnFirstFrameCapturedLocked(null);
482 }
483
484 boolean result = hardwareImpl.startCapture(surface, config);
485 if (result) {
486 connection.setOnFirstFrameCapturedLocked(new Runnable() {
487 @Override
488 public void run() {
489 hardwareImpl.stopCapture(config);
490 }
491 });
492 }
493 return result;
494 }
495 }
496 return false;
497 }
498
Wonsik Kimd922a542014-07-24 18:25:29 +0900499 private void processPendingHdmiDeviceEventsLocked() {
500 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
501 Message msg = it.next();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900502 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
Wonsik Kimd922a542014-07-24 18:25:29 +0900503 TvInputHardwareInfo hardwareInfo =
504 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
505 if (hardwareInfo != null) {
506 msg.sendToTarget();
507 it.remove();
508 }
509 }
510 }
511
Wonsik Kim49e55932014-12-04 17:22:09 +0900512 private void updateVolume() {
513 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
514 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
515 }
516
517 private void handleVolumeChange(Context context, Intent intent) {
518 String action = intent.getAction();
519 if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) {
520 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
521 if (streamType != AudioManager.STREAM_MUSIC) {
522 return;
523 }
524 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
525 if (index == mCurrentIndex) {
526 return;
527 }
528 mCurrentIndex = index;
529 } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) {
530 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
531 if (streamType != AudioManager.STREAM_MUSIC) {
532 return;
533 }
534 // volume index will be updated at onMediaStreamVolumeChanged() through updateVolume().
535 } else {
536 Slog.w(TAG, "Unrecognized intent: " + intent);
537 return;
538 }
539 synchronized (mLock) {
540 for (int i = 0; i < mConnections.size(); ++i) {
541 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
542 if (hardwareImpl != null) {
543 hardwareImpl.onMediaStreamVolumeChanged();
544 }
545 }
546 }
547 }
548
549 private float getMediaStreamVolume() {
550 return mUseMasterVolume ? 1.0f : ((float) mCurrentIndex / (float) mCurrentMaxIndex);
551 }
552
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000553 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900554 private final TvInputHardwareInfo mHardwareInfo;
555 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000556 private TvInputHardwareImpl mHardware = null;
557 private ITvInputHardwareCallback mCallback;
558 private TvStreamConfig[] mConfigs = null;
559 private Integer mCallingUid = null;
560 private Integer mResolvedUserId = null;
Terry Heoc086a3d2014-06-18 14:26:44 +0900561 private Runnable mOnFirstFrameCaptured;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000562
Wonsik Kim969167d2014-06-24 16:33:17 +0900563 public Connection(TvInputHardwareInfo hardwareInfo) {
564 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000565 }
566
567 // *Locked methods assume TvInputHardwareManager.mLock is held.
568
Wonsik Kim969167d2014-06-24 16:33:17 +0900569 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
570 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000571 if (mHardware != null) {
572 try {
573 mCallback.onReleased();
574 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900575 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000576 }
577 mHardware.release();
578 }
579 mHardware = hardware;
580 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900581 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000582 mCallingUid = callingUid;
583 mResolvedUserId = resolvedUserId;
Terry Heoc086a3d2014-06-18 14:26:44 +0900584 mOnFirstFrameCaptured = null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000585
586 if (mHardware != null && mCallback != null) {
587 try {
588 mCallback.onStreamConfigChanged(getConfigsLocked());
589 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900590 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000591 }
592 }
593 }
594
595 public void updateConfigsLocked(TvStreamConfig[] configs) {
596 mConfigs = configs;
597 }
598
Wonsik Kim969167d2014-06-24 16:33:17 +0900599 public TvInputHardwareInfo getHardwareInfoLocked() {
600 return mHardwareInfo;
601 }
602
603 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000604 return mInfo;
605 }
606
607 public ITvInputHardware getHardwareLocked() {
608 return mHardware;
609 }
610
Terry Heoc086a3d2014-06-18 14:26:44 +0900611 public TvInputHardwareImpl getHardwareImplLocked() {
612 return mHardware;
613 }
614
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000615 public ITvInputHardwareCallback getCallbackLocked() {
616 return mCallback;
617 }
618
619 public TvStreamConfig[] getConfigsLocked() {
620 return mConfigs;
621 }
622
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900623 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000624 return mCallingUid;
625 }
626
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900627 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000628 return mResolvedUserId;
629 }
630
Terry Heoc086a3d2014-06-18 14:26:44 +0900631 public void setOnFirstFrameCapturedLocked(Runnable runnable) {
632 mOnFirstFrameCaptured = runnable;
633 }
634
635 public Runnable getOnFirstFrameCapturedLocked() {
636 return mOnFirstFrameCaptured;
637 }
638
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000639 @Override
640 public void binderDied() {
641 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900642 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000643 }
644 }
645 }
646
647 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
648 private final TvInputHardwareInfo mInfo;
649 private boolean mReleased = false;
650 private final Object mImplLock = new Object();
651
Wonsik Kim06c41a32014-08-04 18:57:47 +0900652 private final AudioManager.OnAudioPortUpdateListener mAudioListener =
653 new AudioManager.OnAudioPortUpdateListener() {
654 @Override
655 public void onAudioPortListUpdate(AudioPort[] portList) {
656 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900657 updateAudioConfigLocked();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900658 }
659 }
660
661 @Override
662 public void onAudioPatchListUpdate(AudioPatch[] patchList) {
663 // No-op
664 }
665
666 @Override
667 public void onServiceDied() {
668 synchronized (mImplLock) {
669 mAudioSource = null;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900670 mAudioSink.clear();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900671 mAudioPatch = null;
672 }
673 }
674 };
675 private int mOverrideAudioType = AudioManager.DEVICE_NONE;
676 private String mOverrideAudioAddress = "";
677 private AudioDevicePort mAudioSource;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900678 private List<AudioDevicePort> mAudioSink = new ArrayList<>();
Wonsik Kimd7c29182014-05-27 10:38:21 +0900679 private AudioPatch mAudioPatch = null;
Wonsik Kim485e7e12015-01-26 17:28:55 +0900680 // Set to an invalid value for a volume, so that current volume can be applied at the
681 // first call to updateAudioConfigLocked().
682 private float mCommittedVolume = -1f;
Wonsik Kim49e55932014-12-04 17:22:09 +0900683 private float mSourceVolume = 0.0f;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900684
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900685 private TvStreamConfig mActiveConfig = null;
686
Wonsik Kimca17a902014-07-31 10:09:46 +0900687 private int mDesiredSamplingRate = 0;
688 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
689 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
690
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000691 public TvInputHardwareImpl(TvInputHardwareInfo info) {
692 mInfo = info;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900693 mAudioManager.registerAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900694 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900695 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900696 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kimca17a902014-07-31 10:09:46 +0900697 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900698 }
699
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900700 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
701 sinks.clear();
Paul McLeanceb47aa2015-02-19 15:09:53 -0800702 ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900703 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
704 return;
705 }
706 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
Paul McLeanceb47aa2015-02-19 15:09:53 -0800707 for (AudioDevicePort port : devicePorts) {
708 if ((port.type() & sinkDevice) != 0) {
709 sinks.add(port);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900710 }
711 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900712 }
713
714 private AudioDevicePort findAudioDevicePort(int type, String address) {
715 if (type == AudioManager.DEVICE_NONE) {
716 return null;
717 }
Paul McLeanceb47aa2015-02-19 15:09:53 -0800718 ArrayList<AudioDevicePort> devicePorts = new ArrayList<AudioDevicePort>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900719 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
720 return null;
721 }
Paul McLeanceb47aa2015-02-19 15:09:53 -0800722 for (AudioDevicePort port : devicePorts) {
723 if (port.type() == type && port.address().equals(address)) {
724 return port;
Wonsik Kimca17a902014-07-31 10:09:46 +0900725 }
726 }
727 return null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000728 }
729
730 public void release() {
731 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900732 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900733 if (mAudioPatch != null) {
734 mAudioManager.releaseAudioPatch(mAudioPatch);
735 mAudioPatch = null;
736 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000737 mReleased = true;
738 }
739 }
740
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900741 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
742 // attempts to call setSurface with different TvStreamConfig objects, the last call will
743 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000744 @Override
745 public boolean setSurface(Surface surface, TvStreamConfig config)
746 throws RemoteException {
747 synchronized (mImplLock) {
748 if (mReleased) {
749 throw new IllegalStateException("Device already released.");
750 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900751
Wonsik Kim102670f2014-11-20 14:38:02 +0900752 int result = TvInputHal.SUCCESS;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900753 if (surface == null) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900754 // The value of config is ignored when surface == null.
755 if (mActiveConfig != null) {
756 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
757 mActiveConfig = null;
758 } else {
759 // We already have no active stream.
760 return true;
761 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900762 } else {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900763 // It's impossible to set a non-null surface with a null config.
764 if (config == null) {
765 return false;
766 }
767 // Remove stream only if we have an existing active configuration.
768 if (mActiveConfig != null && !config.equals(mActiveConfig)) {
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900769 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
770 if (result != TvInputHal.SUCCESS) {
771 mActiveConfig = null;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900772 }
773 }
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900774 // Proceed only if all previous operations succeeded.
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900775 if (result == TvInputHal.SUCCESS) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900776 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
777 if (result == TvInputHal.SUCCESS) {
778 mActiveConfig = config;
779 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900780 }
781 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900782 updateAudioConfigLocked();
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900783 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000784 }
785 }
786
Wonsik Kim7587d292014-08-09 10:01:01 +0900787 /**
788 * Update audio configuration (source, sink, patch) all up to current state.
789 */
790 private void updateAudioConfigLocked() {
791 boolean sinkUpdated = updateAudioSinkLocked();
792 boolean sourceUpdated = updateAudioSourceLocked();
793 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
794 // because Java won't evaluate the latter if the former is true.
795
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900796 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900797 if (mAudioPatch != null) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900798 mAudioManager.releaseAudioPatch(mAudioPatch);
799 mAudioPatch = null;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900800 }
801 return;
802 }
803
Wonsik Kim49e55932014-12-04 17:22:09 +0900804 updateVolume();
805 float volume = mSourceVolume * getMediaStreamVolume();
Wonsik Kim8e45a332014-08-04 10:17:18 +0900806 AudioGainConfig sourceGainConfig = null;
Wonsik Kim49e55932014-12-04 17:22:09 +0900807 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
Wonsik Kim8e45a332014-08-04 10:17:18 +0900808 AudioGain sourceGain = null;
809 for (AudioGain gain : mAudioSource.gains()) {
810 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
811 sourceGain = gain;
812 break;
813 }
814 }
815 // NOTE: we only change the source gain in MODE_JOINT here.
816 if (sourceGain != null) {
817 int steps = (sourceGain.maxValue() - sourceGain.minValue())
818 / sourceGain.stepValue();
819 int gainValue = sourceGain.minValue();
Wonsik Kim49e55932014-12-04 17:22:09 +0900820 if (volume < 1.0f) {
821 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900822 } else {
823 gainValue = sourceGain.maxValue();
824 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900825 // size of gain values is 1 in MODE_JOINT
826 int[] gainValues = new int[] { gainValue };
Wonsik Kim8e45a332014-08-04 10:17:18 +0900827 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
828 sourceGain.channelMask(), gainValues, 0);
829 } else {
830 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
831 }
832 }
833
Wonsik Kimca17a902014-07-31 10:09:46 +0900834 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900835 List<AudioPortConfig> sinkConfigs = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900836 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
Wonsik Kim7587d292014-08-09 10:01:01 +0900837 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900838
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900839 for (AudioDevicePort audioSink : mAudioSink) {
840 AudioPortConfig sinkConfig = audioSink.activeConfig();
841 int sinkSamplingRate = mDesiredSamplingRate;
842 int sinkChannelMask = mDesiredChannelMask;
843 int sinkFormat = mDesiredFormat;
844 // If sinkConfig != null and values are set to default,
845 // fill in the sinkConfig values.
846 if (sinkConfig != null) {
847 if (sinkSamplingRate == 0) {
848 sinkSamplingRate = sinkConfig.samplingRate();
849 }
850 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
851 sinkChannelMask = sinkConfig.channelMask();
852 }
853 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
854 sinkChannelMask = sinkConfig.format();
855 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900856 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900857
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900858 if (sinkConfig == null
859 || sinkConfig.samplingRate() != sinkSamplingRate
860 || sinkConfig.channelMask() != sinkChannelMask
861 || sinkConfig.format() != sinkFormat) {
862 // Check for compatibility and reset to default if necessary.
863 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
864 && audioSink.samplingRates().length > 0) {
865 sinkSamplingRate = audioSink.samplingRates()[0];
866 }
867 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
868 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
869 }
870 if (!intArrayContains(audioSink.formats(), sinkFormat)) {
871 sinkFormat = AudioFormat.ENCODING_DEFAULT;
872 }
873 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
874 sinkFormat, null);
875 shouldRecreateAudioPatch = true;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900876 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900877 sinkConfigs.add(sinkConfig);
Wonsik Kimca17a902014-07-31 10:09:46 +0900878 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900879 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
880 // non-empty at the beginning of this method.
881 AudioPortConfig sinkConfig = sinkConfigs.get(0);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900882 if (sourceConfig == null || sourceGainConfig != null) {
Wonsik Kim71dfa962014-11-15 14:18:49 +0900883 int sourceSamplingRate = 0;
884 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
885 sourceSamplingRate = sinkConfig.samplingRate();
886 } else if (mAudioSource.samplingRates().length > 0) {
887 // Use any sampling rate and hope audio patch can handle resampling...
888 sourceSamplingRate = mAudioSource.samplingRates()[0];
889 }
890 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
891 for (int inChannelMask : mAudioSource.channelMasks()) {
892 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
893 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
894 sourceChannelMask = inChannelMask;
895 break;
896 }
897 }
898 int sourceFormat = AudioFormat.ENCODING_DEFAULT;
899 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
900 sourceFormat = sinkConfig.format();
901 }
902 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
903 sourceFormat, sourceGainConfig);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900904 shouldRecreateAudioPatch = true;
Wonsik Kimca17a902014-07-31 10:09:46 +0900905 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900906 if (shouldRecreateAudioPatch) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900907 mCommittedVolume = volume;
Wonsik Kim8e45a332014-08-04 10:17:18 +0900908 mAudioManager.createAudioPatch(
909 audioPatchArray,
910 new AudioPortConfig[] { sourceConfig },
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900911 sinkConfigs.toArray(new AudioPortConfig[0]));
Wonsik Kim8e45a332014-08-04 10:17:18 +0900912 mAudioPatch = audioPatchArray[0];
Wonsik Kim71dfa962014-11-15 14:18:49 +0900913 if (sourceGainConfig != null) {
914 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
915 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900916 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900917 }
918
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000919 @Override
Wonsik Kim8e45a332014-08-04 10:17:18 +0900920 public void setStreamVolume(float volume) throws RemoteException {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000921 synchronized (mImplLock) {
922 if (mReleased) {
923 throw new IllegalStateException("Device already released.");
924 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900925 mSourceVolume = volume;
Wonsik Kim7587d292014-08-09 10:01:01 +0900926 updateAudioConfigLocked();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000927 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000928 }
929
930 @Override
931 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
932 synchronized (mImplLock) {
933 if (mReleased) {
934 throw new IllegalStateException("Device already released.");
935 }
936 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900937 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000938 return false;
939 }
940 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
941 return false;
942 }
Terry Heoc086a3d2014-06-18 14:26:44 +0900943
944 private boolean startCapture(Surface surface, TvStreamConfig config) {
945 synchronized (mImplLock) {
946 if (mReleased) {
947 return false;
948 }
949 if (surface == null || config == null) {
950 return false;
951 }
952 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
953 return false;
954 }
955
Wonsik Kim8f24a8b2014-10-22 16:27:39 +0900956 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
Terry Heoc086a3d2014-06-18 14:26:44 +0900957 return result == TvInputHal.SUCCESS;
958 }
959 }
960
961 private boolean stopCapture(TvStreamConfig config) {
962 synchronized (mImplLock) {
963 if (mReleased) {
964 return false;
965 }
966 if (config == null) {
967 return false;
968 }
969
970 int result = mHal.removeStream(mInfo.getDeviceId(), config);
971 return result == TvInputHal.SUCCESS;
972 }
973 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900974
Wonsik Kim7587d292014-08-09 10:01:01 +0900975 private boolean updateAudioSourceLocked() {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900976 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900977 return false;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900978 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900979 AudioDevicePort previousSource = mAudioSource;
980 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
981 return mAudioSource == null ? (previousSource != null)
982 : !mAudioSource.equals(previousSource);
983 }
984
985 private boolean updateAudioSinkLocked() {
986 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
987 return false;
988 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900989 List<AudioDevicePort> previousSink = mAudioSink;
990 mAudioSink = new ArrayList<>();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900991 if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900992 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900993 } else {
994 AudioDevicePort audioSink =
995 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
996 if (audioSink != null) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900997 mAudioSink.add(audioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900998 }
999 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +09001000
1001 // Returns true if mAudioSink and previousSink differs.
1002 if (mAudioSink.size() != previousSink.size()) {
1003 return true;
1004 }
1005 previousSink.removeAll(mAudioSink);
1006 return !previousSink.isEmpty();
Wonsik Kim06c41a32014-08-04 18:57:47 +09001007 }
1008
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001009 private void handleAudioSinkUpdated() {
1010 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +09001011 updateAudioConfigLocked();
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001012 }
1013 }
1014
Wonsik Kimca17a902014-07-31 10:09:46 +09001015 @Override
1016 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1017 int channelMask, int format) {
1018 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +09001019 mOverrideAudioType = audioType;
1020 mOverrideAudioAddress = audioAddress;
Wonsik Kim06c41a32014-08-04 18:57:47 +09001021
Wonsik Kimca17a902014-07-31 10:09:46 +09001022 mDesiredSamplingRate = samplingRate;
1023 mDesiredChannelMask = channelMask;
1024 mDesiredFormat = format;
1025
Wonsik Kim7587d292014-08-09 10:01:01 +09001026 updateAudioConfigLocked();
Wonsik Kimca17a902014-07-31 10:09:46 +09001027 }
1028 }
Wonsik Kim49e55932014-12-04 17:22:09 +09001029
1030 public void onMediaStreamVolumeChanged() {
1031 synchronized (mImplLock) {
1032 updateAudioConfigLocked();
1033 }
1034 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001035 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001036
Wonsik Kim187423c2014-06-25 14:12:48 +09001037 interface Listener {
1038 public void onStateChanged(String inputId, int state);
1039 public void onHardwareDeviceAdded(TvInputHardwareInfo info);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001040 public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
Jae Seo546c6352014-08-07 11:57:01 -07001041 public void onHdmiDeviceAdded(HdmiDeviceInfo device);
1042 public void onHdmiDeviceRemoved(HdmiDeviceInfo device);
Wonsik Kime92f8572014-08-12 18:30:24 +09001043 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
Wonsik Kim187423c2014-06-25 14:12:48 +09001044 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001045
Wonsik Kim187423c2014-06-25 14:12:48 +09001046 private class ListenerHandler extends Handler {
1047 private static final int STATE_CHANGED = 1;
1048 private static final int HARDWARE_DEVICE_ADDED = 2;
1049 private static final int HARDWARE_DEVICE_REMOVED = 3;
Jae Seo546c6352014-08-07 11:57:01 -07001050 private static final int HDMI_DEVICE_ADDED = 4;
1051 private static final int HDMI_DEVICE_REMOVED = 5;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001052 private static final int HDMI_DEVICE_UPDATED = 6;
Wonsik Kim969167d2014-06-24 16:33:17 +09001053
1054 @Override
1055 public final void handleMessage(Message msg) {
1056 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001057 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +09001058 String inputId = (String) msg.obj;
1059 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +09001060 mListener.onStateChanged(inputId, state);
1061 break;
1062 }
1063 case HARDWARE_DEVICE_ADDED: {
1064 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1065 mListener.onHardwareDeviceAdded(info);
1066 break;
1067 }
1068 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001069 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1070 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001071 break;
1072 }
Jae Seo546c6352014-08-07 11:57:01 -07001073 case HDMI_DEVICE_ADDED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001074 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001075 mListener.onHdmiDeviceAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001076 break;
1077 }
Jae Seo546c6352014-08-07 11:57:01 -07001078 case HDMI_DEVICE_REMOVED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001079 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001080 mListener.onHdmiDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +09001081 break;
1082 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001083 case HDMI_DEVICE_UPDATED: {
Wonsik Kim184a6d62014-10-08 13:05:56 +09001084 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1085 String inputId = null;
1086 synchronized (mLock) {
1087 inputId = mHdmiInputIdMap.get(info.getId());
1088 }
1089 if (inputId != null) {
1090 mListener.onHdmiDeviceUpdated(inputId, info);
1091 } else {
1092 Slog.w(TAG, "Could not resolve input ID matching the device info; "
1093 + "ignoring.");
1094 }
1095 break;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001096 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001097 default: {
1098 Slog.w(TAG, "Unhandled message: " + msg);
1099 break;
1100 }
1101 }
1102 }
1103 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001104
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001105 // Listener implementations for HdmiControlService
1106
1107 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1108 @Override
1109 public void onReceived(HdmiHotplugEvent event) {
1110 synchronized (mLock) {
1111 mHdmiStateMap.put(event.getPort(), event.isConnected());
Wonsik Kimd922a542014-07-24 18:25:29 +09001112 TvInputHardwareInfo hardwareInfo =
1113 findHardwareInfoForHdmiPortLocked(event.getPort());
1114 if (hardwareInfo == null) {
1115 return;
1116 }
1117 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001118 if (inputId == null) {
1119 return;
1120 }
1121 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1122 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1123 }
1124 }
1125 }
1126
Wonsik Kim187423c2014-06-25 14:12:48 +09001127 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1128 @Override
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001129 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
Jinsuk Kim98d760e2014-12-23 07:01:51 +09001130 if (!deviceInfo.isSourceType()) return;
Wonsik Kimd922a542014-07-24 18:25:29 +09001131 synchronized (mLock) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001132 int messageType = 0;
Wonsik Kime92f8572014-08-12 18:30:24 +09001133 Object obj = null;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001134 switch (status) {
1135 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001136 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001137 mHdmiDeviceList.add(deviceInfo);
1138 } else {
1139 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1140 return;
1141 }
1142 messageType = ListenerHandler.HDMI_DEVICE_ADDED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001143 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001144 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001145 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001146 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001147 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1148 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001149 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1150 return;
1151 }
1152 messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001153 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001154 break;
1155 }
1156 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001157 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1158 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001159 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1160 return;
1161 }
1162 mHdmiDeviceList.add(deviceInfo);
1163 messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001164 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001165 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001166 }
1167 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001168
Wonsik Kime92f8572014-08-12 18:30:24 +09001169 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
Wonsik Kimd922a542014-07-24 18:25:29 +09001170 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1171 msg.sendToTarget();
1172 } else {
1173 mPendingHdmiDeviceEvents.add(msg);
1174 }
1175 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001176 }
Jungshik Jangcf445872014-08-20 15:06:01 +09001177
1178 private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1179 for (HdmiDeviceInfo info : mHdmiDeviceList) {
1180 if (info.getId() == id) {
1181 return info;
1182 }
1183 }
1184 return null;
1185 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001186 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001187
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001188 private final class HdmiSystemAudioModeChangeListener extends
1189 IHdmiSystemAudioModeChangeListener.Stub {
1190 @Override
1191 public void onStatusChanged(boolean enabled) throws RemoteException {
1192 synchronized (mLock) {
1193 for (int i = 0; i < mConnections.size(); ++i) {
1194 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
Jungshik Jang44bb3a52014-08-26 17:24:24 +09001195 if (impl != null) {
1196 impl.handleAudioSinkUpdated();
1197 }
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001198 }
1199 }
1200 }
1201 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001202}