blob: 0f77242d34114d8c8a6e83b94784bc424be38ca3 [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;
Wally Yauad36f6472015-10-06 11:33:19 -070041import android.media.AudioSystem;
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
Wonsik Kimd922a542014-07-24 18:25:29 +0900106 // TODO: Should handle STANDBY case.
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900107 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
Wonsik Kimd922a542014-07-24 18:25:29 +0900108 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900109
Wonsik Kim187423c2014-06-25 14:12:48 +0900110 // Calls to mListener should happen here.
111 private final Handler mHandler = new ListenerHandler();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000112
113 private final Object mLock = new Object();
114
Wonsik Kim187423c2014-06-25 14:12:48 +0900115 public TvInputHardwareManager(Context context, Listener listener) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900116 mContext = context;
Wonsik Kim187423c2014-06-25 14:12:48 +0900117 mListener = listener;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900118 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000119 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +0900120 }
121
122 public void onBootPhase(int phase) {
123 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Jae Seo38b32572015-06-05 14:43:11 -0700124 IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
125 ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
126 if (hdmiControlService != null) {
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900127 try {
Jae Seo38b32572015-06-05 14:43:11 -0700128 hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
129 hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
130 hdmiControlService.addSystemAudioModeChangeListener(
Jungshik Jang1f819bb2014-08-08 10:46:54 +0900131 mHdmiSystemAudioModeChangeListener);
Jae Seo38b32572015-06-05 14:43:11 -0700132 mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900133 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900134 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900135 }
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900136 } else {
137 Slog.w(TAG, "HdmiControlService is not available");
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900138 }
John Spurlockee5ad722015-03-03 16:17:21 -0500139 final IntentFilter filter = new IntentFilter();
140 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
141 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
142 mContext.registerReceiver(mVolumeReceiver, filter);
Wonsik Kim49e55932014-12-04 17:22:09 +0900143 updateVolume();
Wonsik Kim969167d2014-06-24 16:33:17 +0900144 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000145 }
146
147 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900148 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000149 synchronized (mLock) {
150 Connection connection = new Connection(info);
151 connection.updateConfigsLocked(configs);
152 mConnections.put(info.getDeviceId(), connection);
Wonsik Kimd922a542014-07-24 18:25:29 +0900153 buildHardwareListLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900154 mHandler.obtainMessage(
155 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
Wonsik Kimd922a542014-07-24 18:25:29 +0900156 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
157 processPendingHdmiDeviceEventsLocked();
158 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000159 }
160 }
161
Wonsik Kimd922a542014-07-24 18:25:29 +0900162 private void buildHardwareListLocked() {
163 mHardwareList.clear();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000164 for (int i = 0; i < mConnections.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900165 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000166 }
167 }
168
169 @Override
170 public void onDeviceUnavailable(int deviceId) {
171 synchronized (mLock) {
172 Connection connection = mConnections.get(deviceId);
173 if (connection == null) {
174 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
175 return;
176 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900177 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000178 mConnections.remove(deviceId);
Wonsik Kimd922a542014-07-24 18:25:29 +0900179 buildHardwareListLocked();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900180 TvInputHardwareInfo info = connection.getHardwareInfoLocked();
Wonsik Kimd922a542014-07-24 18:25:29 +0900181 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Jae Seo546c6352014-08-07 11:57:01 -0700182 // Remove HDMI devices linked with this hardware.
183 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900184 HdmiDeviceInfo deviceInfo = it.next();
Wonsik Kimd922a542014-07-24 18:25:29 +0900185 if (deviceInfo.getPortId() == info.getHdmiPortId()) {
Jae Seo546c6352014-08-07 11:57:01 -0700186 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
Wonsik Kimd922a542014-07-24 18:25:29 +0900187 deviceInfo).sendToTarget();
188 it.remove();
189 }
190 }
191 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900192 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900193 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000194 }
195 }
196
197 @Override
198 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
199 synchronized (mLock) {
200 Connection connection = mConnections.get(deviceId);
201 if (connection == null) {
202 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
203 + deviceId);
204 return;
205 }
206 connection.updateConfigsLocked(configs);
Wonsik Kim9a103652014-10-21 17:46:31 +0900207 String inputId = mHardwareInputIdMap.get(deviceId);
208 if (inputId != null) {
209 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim5b820a82014-10-27 11:02:10 +0900210 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900211 }
Dongwon Kang017bb852014-12-26 14:53:14 +0900212 ITvInputHardwareCallback callback = connection.getCallbackLocked();
213 if (callback != null) {
214 try {
215 callback.onStreamConfigChanged(configs);
216 } catch (RemoteException e) {
217 Slog.e(TAG, "error in onStreamConfigurationChanged", e);
218 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000219 }
220 }
221 }
222
Terry Heoc086a3d2014-06-18 14:26:44 +0900223 @Override
224 public void onFirstFrameCaptured(int deviceId, int streamId) {
225 synchronized (mLock) {
226 Connection connection = mConnections.get(deviceId);
227 if (connection == null) {
228 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
229 + deviceId);
230 return;
231 }
232 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
233 if (runnable != null) {
234 runnable.run();
235 connection.setOnFirstFrameCapturedLocked(null);
236 }
237 }
238 }
239
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000240 public List<TvInputHardwareInfo> getHardwareList() {
241 synchronized (mLock) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900242 return Collections.unmodifiableList(mHardwareList);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000243 }
244 }
245
Jae Seo546c6352014-08-07 11:57:01 -0700246 public List<HdmiDeviceInfo> getHdmiDeviceList() {
Wonsik Kimd922a542014-07-24 18:25:29 +0900247 synchronized (mLock) {
Jae Seo546c6352014-08-07 11:57:01 -0700248 return Collections.unmodifiableList(mHdmiDeviceList);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900249 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900250 }
251
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900252 private boolean checkUidChangedLocked(
253 Connection connection, int callingUid, int resolvedUserId) {
254 Integer connectionCallingUid = connection.getCallingUidLocked();
255 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700256 return connectionCallingUid == null || connectionResolvedUserId == null
257 || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId;
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900258 }
259
Wonsik Kim969167d2014-06-24 16:33:17 +0900260 private int convertConnectedToState(boolean connected) {
261 if (connected) {
262 return INPUT_STATE_CONNECTED;
263 } else {
264 return INPUT_STATE_DISCONNECTED;
265 }
266 }
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 Kim187423c2014-06-25 14:12:48 +0900290 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim969167d2014-06-24 16:33:17 +0900291 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
292 inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900293 return;
Wonsik Kim969167d2014-06-24 16:33:17 +0900294 }
295 }
Wonsik Kim9a103652014-10-21 17:46:31 +0900296 // For the rest of the devices, we can tell by the number of available streams.
297 Connection connection = mConnections.get(deviceId);
298 if (connection != null) {
299 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
300 convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
301 info.getId()).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900302 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900303 }
304 }
305
Wonsik Kim38feae92014-07-21 21:35:50 +0900306 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
307 for (int i = 0; i < map.size(); ++i) {
308 if (map.valueAt(i).equals(value)) {
309 return i;
310 }
311 }
312 return -1;
313 }
314
Wonsik Kim71dfa962014-11-15 14:18:49 +0900315 private static boolean intArrayContains(int[] array, int value) {
316 for (int element : array) {
317 if (element == value) return true;
318 }
319 return false;
320 }
321
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900322 public void addHdmiTvInput(int id, TvInputInfo info) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900323 if (info.getType() != TvInputInfo.TYPE_HDMI) {
324 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
325 }
326 synchronized (mLock) {
327 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900328 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
329 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900330 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
331 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900332 String oldInputId = mHdmiInputIdMap.get(id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900333 if (oldInputId != null) {
334 Slog.w(TAG, "Trying to override previous registration: old = "
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900335 + mInputMap.get(oldInputId) + ":" + id + ", new = "
336 + info + ":" + id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900337 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900338 mHdmiInputIdMap.put(id, info.getId());
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900339 mInputMap.put(info.getId(), info);
340 }
341 }
342
343 public void removeTvInput(String inputId) {
344 synchronized (mLock) {
345 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900346 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900347 if (hardwareIndex >= 0) {
348 mHardwareInputIdMap.removeAt(hardwareIndex);
349 }
Jae Seo546c6352014-08-07 11:57:01 -0700350 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
351 if (deviceIndex >= 0) {
352 mHdmiInputIdMap.removeAt(deviceIndex);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900353 }
354 }
355 }
356
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000357 /**
358 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
359 * the object, and if more than one process attempts to create hardware with the same deviceId,
360 * the latest service will get the object and all the other hardware are released. The
361 * release is notified via ITvInputHardwareCallback.onReleased().
362 */
363 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900364 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000365 if (callback == null) {
366 throw new NullPointerException();
367 }
368 synchronized (mLock) {
369 Connection connection = mConnections.get(deviceId);
370 if (connection == null) {
371 Slog.e(TAG, "Invalid deviceId : " + deviceId);
372 return null;
373 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900374 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900375 TvInputHardwareImpl hardware =
376 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000377 try {
378 callback.asBinder().linkToDeath(connection, 0);
379 } catch (RemoteException e) {
380 hardware.release();
381 return null;
382 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900383 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000384 }
385 return connection.getHardwareLocked();
386 }
387 }
388
389 /**
390 * Release the specified hardware.
391 */
392 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
393 int resolvedUserId) {
394 synchronized (mLock) {
395 Connection connection = mConnections.get(deviceId);
396 if (connection == null) {
397 Slog.e(TAG, "Invalid deviceId : " + deviceId);
398 return;
399 }
400 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900401 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000402 return;
403 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900404 connection.resetLocked(null, null, null, null, null);
405 }
406 }
407
Wonsik Kimd922a542014-07-24 18:25:29 +0900408 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
409 for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900410 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
411 && hardwareInfo.getHdmiPortId() == port) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900412 return hardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +0900413 }
414 }
415 return null;
416 }
417
Terry Heoc086a3d2014-06-18 14:26:44 +0900418 private int findDeviceIdForInputIdLocked(String inputId) {
419 for (int i = 0; i < mConnections.size(); ++i) {
420 Connection connection = mConnections.get(i);
421 if (connection.getInfoLocked().getId().equals(inputId)) {
422 return i;
423 }
424 }
425 return -1;
426 }
427
428 /**
429 * Get the list of TvStreamConfig which is buffered mode.
430 */
431 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
432 int resolvedUserId) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700433 List<TvStreamConfig> configsList = new ArrayList<>();
Terry Heoc086a3d2014-06-18 14:26:44 +0900434 synchronized (mLock) {
435 int deviceId = findDeviceIdForInputIdLocked(inputId);
436 if (deviceId < 0) {
437 Slog.e(TAG, "Invalid inputId : " + inputId);
438 return configsList;
439 }
440 Connection connection = mConnections.get(deviceId);
441 for (TvStreamConfig config : connection.getConfigsLocked()) {
442 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
443 configsList.add(config);
444 }
445 }
446 }
447 return configsList;
448 }
449
450 /**
451 * Take a snapshot of the given TV input into the provided Surface.
452 */
453 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
454 int callingUid, int resolvedUserId) {
455 synchronized (mLock) {
456 int deviceId = findDeviceIdForInputIdLocked(inputId);
457 if (deviceId < 0) {
458 Slog.e(TAG, "Invalid inputId : " + inputId);
459 return false;
460 }
461 Connection connection = mConnections.get(deviceId);
462 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
463 if (hardwareImpl != null) {
464 // Stop previous capture.
465 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
466 if (runnable != null) {
467 runnable.run();
468 connection.setOnFirstFrameCapturedLocked(null);
469 }
470
471 boolean result = hardwareImpl.startCapture(surface, config);
472 if (result) {
473 connection.setOnFirstFrameCapturedLocked(new Runnable() {
474 @Override
475 public void run() {
476 hardwareImpl.stopCapture(config);
477 }
478 });
479 }
480 return result;
481 }
482 }
483 return false;
484 }
485
Wonsik Kimd922a542014-07-24 18:25:29 +0900486 private void processPendingHdmiDeviceEventsLocked() {
487 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
488 Message msg = it.next();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900489 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
Wonsik Kimd922a542014-07-24 18:25:29 +0900490 TvInputHardwareInfo hardwareInfo =
491 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
492 if (hardwareInfo != null) {
493 msg.sendToTarget();
494 it.remove();
495 }
496 }
497 }
498
Wonsik Kim49e55932014-12-04 17:22:09 +0900499 private void updateVolume() {
500 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
501 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
502 }
503
504 private void handleVolumeChange(Context context, Intent intent) {
505 String action = intent.getAction();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700506 switch (action) {
507 case AudioManager.VOLUME_CHANGED_ACTION: {
508 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
509 if (streamType != AudioManager.STREAM_MUSIC) {
510 return;
511 }
512 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
513 if (index == mCurrentIndex) {
514 return;
515 }
516 mCurrentIndex = index;
517 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900518 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700519 case AudioManager.STREAM_MUTE_CHANGED_ACTION: {
520 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
521 if (streamType != AudioManager.STREAM_MUSIC) {
522 return;
523 }
524 // volume index will be updated at onMediaStreamVolumeChanged() through
525 // updateVolume().
526 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900527 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700528 default:
529 Slog.w(TAG, "Unrecognized intent: " + intent);
Wonsik Kim49e55932014-12-04 17:22:09 +0900530 return;
Wonsik Kim49e55932014-12-04 17:22:09 +0900531 }
532 synchronized (mLock) {
533 for (int i = 0; i < mConnections.size(); ++i) {
534 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
535 if (hardwareImpl != null) {
536 hardwareImpl.onMediaStreamVolumeChanged();
537 }
538 }
539 }
540 }
541
542 private float getMediaStreamVolume() {
John Spurlockee5ad722015-03-03 16:17:21 -0500543 return (float) mCurrentIndex / (float) mCurrentMaxIndex;
Wonsik Kim49e55932014-12-04 17:22:09 +0900544 }
545
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000546 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900547 private final TvInputHardwareInfo mHardwareInfo;
548 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000549 private TvInputHardwareImpl mHardware = null;
550 private ITvInputHardwareCallback mCallback;
551 private TvStreamConfig[] mConfigs = null;
552 private Integer mCallingUid = null;
553 private Integer mResolvedUserId = null;
Terry Heoc086a3d2014-06-18 14:26:44 +0900554 private Runnable mOnFirstFrameCaptured;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000555
Wonsik Kim969167d2014-06-24 16:33:17 +0900556 public Connection(TvInputHardwareInfo hardwareInfo) {
557 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000558 }
559
560 // *Locked methods assume TvInputHardwareManager.mLock is held.
561
Wonsik Kim969167d2014-06-24 16:33:17 +0900562 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
563 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000564 if (mHardware != null) {
565 try {
566 mCallback.onReleased();
567 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900568 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000569 }
570 mHardware.release();
571 }
572 mHardware = hardware;
573 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900574 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000575 mCallingUid = callingUid;
576 mResolvedUserId = resolvedUserId;
Terry Heoc086a3d2014-06-18 14:26:44 +0900577 mOnFirstFrameCaptured = null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000578
579 if (mHardware != null && mCallback != null) {
580 try {
581 mCallback.onStreamConfigChanged(getConfigsLocked());
582 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900583 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000584 }
585 }
586 }
587
588 public void updateConfigsLocked(TvStreamConfig[] configs) {
589 mConfigs = configs;
590 }
591
Wonsik Kim969167d2014-06-24 16:33:17 +0900592 public TvInputHardwareInfo getHardwareInfoLocked() {
593 return mHardwareInfo;
594 }
595
596 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000597 return mInfo;
598 }
599
600 public ITvInputHardware getHardwareLocked() {
601 return mHardware;
602 }
603
Terry Heoc086a3d2014-06-18 14:26:44 +0900604 public TvInputHardwareImpl getHardwareImplLocked() {
605 return mHardware;
606 }
607
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000608 public ITvInputHardwareCallback getCallbackLocked() {
609 return mCallback;
610 }
611
612 public TvStreamConfig[] getConfigsLocked() {
613 return mConfigs;
614 }
615
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900616 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000617 return mCallingUid;
618 }
619
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900620 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000621 return mResolvedUserId;
622 }
623
Terry Heoc086a3d2014-06-18 14:26:44 +0900624 public void setOnFirstFrameCapturedLocked(Runnable runnable) {
625 mOnFirstFrameCaptured = runnable;
626 }
627
628 public Runnable getOnFirstFrameCapturedLocked() {
629 return mOnFirstFrameCaptured;
630 }
631
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000632 @Override
633 public void binderDied() {
634 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900635 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000636 }
637 }
638 }
639
640 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
641 private final TvInputHardwareInfo mInfo;
642 private boolean mReleased = false;
643 private final Object mImplLock = new Object();
644
Wonsik Kim06c41a32014-08-04 18:57:47 +0900645 private final AudioManager.OnAudioPortUpdateListener mAudioListener =
646 new AudioManager.OnAudioPortUpdateListener() {
647 @Override
648 public void onAudioPortListUpdate(AudioPort[] portList) {
649 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900650 updateAudioConfigLocked();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900651 }
652 }
653
654 @Override
655 public void onAudioPatchListUpdate(AudioPatch[] patchList) {
656 // No-op
657 }
658
659 @Override
660 public void onServiceDied() {
661 synchronized (mImplLock) {
662 mAudioSource = null;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900663 mAudioSink.clear();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900664 mAudioPatch = null;
665 }
666 }
667 };
668 private int mOverrideAudioType = AudioManager.DEVICE_NONE;
669 private String mOverrideAudioAddress = "";
670 private AudioDevicePort mAudioSource;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900671 private List<AudioDevicePort> mAudioSink = new ArrayList<>();
Wonsik Kimd7c29182014-05-27 10:38:21 +0900672 private AudioPatch mAudioPatch = null;
Wonsik Kim485e7e12015-01-26 17:28:55 +0900673 // Set to an invalid value for a volume, so that current volume can be applied at the
674 // first call to updateAudioConfigLocked().
675 private float mCommittedVolume = -1f;
Wonsik Kim49e55932014-12-04 17:22:09 +0900676 private float mSourceVolume = 0.0f;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900677
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900678 private TvStreamConfig mActiveConfig = null;
679
Wonsik Kimca17a902014-07-31 10:09:46 +0900680 private int mDesiredSamplingRate = 0;
681 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
682 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
683
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000684 public TvInputHardwareImpl(TvInputHardwareInfo info) {
685 mInfo = info;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900686 mAudioManager.registerAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900687 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900688 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900689 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kimca17a902014-07-31 10:09:46 +0900690 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900691 }
692
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900693 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
694 sinks.clear();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700695 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900696 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
697 return;
698 }
699 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
Paul McLeanceb47aa2015-02-19 15:09:53 -0800700 for (AudioDevicePort port : devicePorts) {
Wally Yauad36f6472015-10-06 11:33:19 -0700701 if ((port.type() & sinkDevice) != 0 &&
702 (port.type() & AudioSystem.DEVICE_BIT_IN) == 0) {
Paul McLeanceb47aa2015-02-19 15:09:53 -0800703 sinks.add(port);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900704 }
705 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900706 }
707
708 private AudioDevicePort findAudioDevicePort(int type, String address) {
709 if (type == AudioManager.DEVICE_NONE) {
710 return null;
711 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700712 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900713 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
714 return null;
715 }
Paul McLeanceb47aa2015-02-19 15:09:53 -0800716 for (AudioDevicePort port : devicePorts) {
717 if (port.type() == type && port.address().equals(address)) {
718 return port;
Wonsik Kimca17a902014-07-31 10:09:46 +0900719 }
720 }
721 return null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000722 }
723
724 public void release() {
725 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900726 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900727 if (mAudioPatch != null) {
728 mAudioManager.releaseAudioPatch(mAudioPatch);
729 mAudioPatch = null;
730 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000731 mReleased = true;
732 }
733 }
734
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900735 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
736 // attempts to call setSurface with different TvStreamConfig objects, the last call will
737 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000738 @Override
739 public boolean setSurface(Surface surface, TvStreamConfig config)
740 throws RemoteException {
741 synchronized (mImplLock) {
742 if (mReleased) {
743 throw new IllegalStateException("Device already released.");
744 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900745
Wonsik Kim102670f2014-11-20 14:38:02 +0900746 int result = TvInputHal.SUCCESS;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900747 if (surface == null) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900748 // The value of config is ignored when surface == null.
749 if (mActiveConfig != null) {
750 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
751 mActiveConfig = null;
752 } else {
753 // We already have no active stream.
754 return true;
755 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900756 } else {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900757 // It's impossible to set a non-null surface with a null config.
758 if (config == null) {
759 return false;
760 }
761 // Remove stream only if we have an existing active configuration.
762 if (mActiveConfig != null && !config.equals(mActiveConfig)) {
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900763 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
764 if (result != TvInputHal.SUCCESS) {
765 mActiveConfig = null;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900766 }
767 }
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900768 // Proceed only if all previous operations succeeded.
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900769 if (result == TvInputHal.SUCCESS) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900770 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
771 if (result == TvInputHal.SUCCESS) {
772 mActiveConfig = config;
773 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900774 }
775 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900776 updateAudioConfigLocked();
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900777 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000778 }
779 }
780
Wonsik Kim7587d292014-08-09 10:01:01 +0900781 /**
782 * Update audio configuration (source, sink, patch) all up to current state.
783 */
784 private void updateAudioConfigLocked() {
785 boolean sinkUpdated = updateAudioSinkLocked();
786 boolean sourceUpdated = updateAudioSourceLocked();
787 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
788 // because Java won't evaluate the latter if the former is true.
789
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900790 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900791 if (mAudioPatch != null) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900792 mAudioManager.releaseAudioPatch(mAudioPatch);
793 mAudioPatch = null;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900794 }
795 return;
796 }
797
Wonsik Kim49e55932014-12-04 17:22:09 +0900798 updateVolume();
799 float volume = mSourceVolume * getMediaStreamVolume();
Wonsik Kim8e45a332014-08-04 10:17:18 +0900800 AudioGainConfig sourceGainConfig = null;
Wonsik Kim49e55932014-12-04 17:22:09 +0900801 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
Wonsik Kim8e45a332014-08-04 10:17:18 +0900802 AudioGain sourceGain = null;
803 for (AudioGain gain : mAudioSource.gains()) {
804 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
805 sourceGain = gain;
806 break;
807 }
808 }
809 // NOTE: we only change the source gain in MODE_JOINT here.
810 if (sourceGain != null) {
811 int steps = (sourceGain.maxValue() - sourceGain.minValue())
812 / sourceGain.stepValue();
813 int gainValue = sourceGain.minValue();
Wonsik Kim49e55932014-12-04 17:22:09 +0900814 if (volume < 1.0f) {
815 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900816 } else {
817 gainValue = sourceGain.maxValue();
818 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900819 // size of gain values is 1 in MODE_JOINT
820 int[] gainValues = new int[] { gainValue };
Wonsik Kim8e45a332014-08-04 10:17:18 +0900821 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
822 sourceGain.channelMask(), gainValues, 0);
823 } else {
824 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
825 }
826 }
827
Wonsik Kimca17a902014-07-31 10:09:46 +0900828 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900829 List<AudioPortConfig> sinkConfigs = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900830 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
Wonsik Kim7587d292014-08-09 10:01:01 +0900831 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900832
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900833 for (AudioDevicePort audioSink : mAudioSink) {
834 AudioPortConfig sinkConfig = audioSink.activeConfig();
835 int sinkSamplingRate = mDesiredSamplingRate;
836 int sinkChannelMask = mDesiredChannelMask;
837 int sinkFormat = mDesiredFormat;
838 // If sinkConfig != null and values are set to default,
839 // fill in the sinkConfig values.
840 if (sinkConfig != null) {
841 if (sinkSamplingRate == 0) {
842 sinkSamplingRate = sinkConfig.samplingRate();
843 }
844 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
845 sinkChannelMask = sinkConfig.channelMask();
846 }
847 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
848 sinkChannelMask = sinkConfig.format();
849 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900850 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900851
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900852 if (sinkConfig == null
853 || sinkConfig.samplingRate() != sinkSamplingRate
854 || sinkConfig.channelMask() != sinkChannelMask
855 || sinkConfig.format() != sinkFormat) {
856 // Check for compatibility and reset to default if necessary.
857 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
858 && audioSink.samplingRates().length > 0) {
859 sinkSamplingRate = audioSink.samplingRates()[0];
860 }
861 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
862 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
863 }
864 if (!intArrayContains(audioSink.formats(), sinkFormat)) {
865 sinkFormat = AudioFormat.ENCODING_DEFAULT;
866 }
867 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
868 sinkFormat, null);
869 shouldRecreateAudioPatch = true;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900870 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900871 sinkConfigs.add(sinkConfig);
Wonsik Kimca17a902014-07-31 10:09:46 +0900872 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900873 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
874 // non-empty at the beginning of this method.
875 AudioPortConfig sinkConfig = sinkConfigs.get(0);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900876 if (sourceConfig == null || sourceGainConfig != null) {
Wonsik Kim71dfa962014-11-15 14:18:49 +0900877 int sourceSamplingRate = 0;
878 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
879 sourceSamplingRate = sinkConfig.samplingRate();
880 } else if (mAudioSource.samplingRates().length > 0) {
881 // Use any sampling rate and hope audio patch can handle resampling...
882 sourceSamplingRate = mAudioSource.samplingRates()[0];
883 }
884 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
885 for (int inChannelMask : mAudioSource.channelMasks()) {
886 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
887 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
888 sourceChannelMask = inChannelMask;
889 break;
890 }
891 }
892 int sourceFormat = AudioFormat.ENCODING_DEFAULT;
893 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
894 sourceFormat = sinkConfig.format();
895 }
896 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
897 sourceFormat, sourceGainConfig);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900898 shouldRecreateAudioPatch = true;
Wonsik Kimca17a902014-07-31 10:09:46 +0900899 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900900 if (shouldRecreateAudioPatch) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900901 mCommittedVolume = volume;
Wonsik Kim8e45a332014-08-04 10:17:18 +0900902 mAudioManager.createAudioPatch(
903 audioPatchArray,
904 new AudioPortConfig[] { sourceConfig },
Jae Seo93ff14b2015-06-21 14:08:54 -0700905 sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
Wonsik Kim8e45a332014-08-04 10:17:18 +0900906 mAudioPatch = audioPatchArray[0];
Wonsik Kim71dfa962014-11-15 14:18:49 +0900907 if (sourceGainConfig != null) {
908 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
909 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900910 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900911 }
912
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000913 @Override
Wonsik Kim8e45a332014-08-04 10:17:18 +0900914 public void setStreamVolume(float volume) throws RemoteException {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000915 synchronized (mImplLock) {
916 if (mReleased) {
917 throw new IllegalStateException("Device already released.");
918 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900919 mSourceVolume = volume;
Wonsik Kim7587d292014-08-09 10:01:01 +0900920 updateAudioConfigLocked();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000921 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000922 }
923
924 @Override
925 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
926 synchronized (mImplLock) {
927 if (mReleased) {
928 throw new IllegalStateException("Device already released.");
929 }
930 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900931 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000932 return false;
933 }
934 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
935 return false;
936 }
Terry Heoc086a3d2014-06-18 14:26:44 +0900937
938 private boolean startCapture(Surface surface, TvStreamConfig config) {
939 synchronized (mImplLock) {
940 if (mReleased) {
941 return false;
942 }
943 if (surface == null || config == null) {
944 return false;
945 }
946 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
947 return false;
948 }
949
Wonsik Kim8f24a8b2014-10-22 16:27:39 +0900950 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
Terry Heoc086a3d2014-06-18 14:26:44 +0900951 return result == TvInputHal.SUCCESS;
952 }
953 }
954
955 private boolean stopCapture(TvStreamConfig config) {
956 synchronized (mImplLock) {
957 if (mReleased) {
958 return false;
959 }
960 if (config == null) {
961 return false;
962 }
963
964 int result = mHal.removeStream(mInfo.getDeviceId(), config);
965 return result == TvInputHal.SUCCESS;
966 }
967 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900968
Wonsik Kim7587d292014-08-09 10:01:01 +0900969 private boolean updateAudioSourceLocked() {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900970 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900971 return false;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900972 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900973 AudioDevicePort previousSource = mAudioSource;
974 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
975 return mAudioSource == null ? (previousSource != null)
976 : !mAudioSource.equals(previousSource);
977 }
978
979 private boolean updateAudioSinkLocked() {
980 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
981 return false;
982 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900983 List<AudioDevicePort> previousSink = mAudioSink;
984 mAudioSink = new ArrayList<>();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900985 if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900986 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900987 } else {
988 AudioDevicePort audioSink =
989 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
990 if (audioSink != null) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900991 mAudioSink.add(audioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900992 }
993 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900994
995 // Returns true if mAudioSink and previousSink differs.
996 if (mAudioSink.size() != previousSink.size()) {
997 return true;
998 }
999 previousSink.removeAll(mAudioSink);
1000 return !previousSink.isEmpty();
Wonsik Kim06c41a32014-08-04 18:57:47 +09001001 }
1002
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001003 private void handleAudioSinkUpdated() {
1004 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +09001005 updateAudioConfigLocked();
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001006 }
1007 }
1008
Wonsik Kimca17a902014-07-31 10:09:46 +09001009 @Override
1010 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1011 int channelMask, int format) {
1012 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +09001013 mOverrideAudioType = audioType;
1014 mOverrideAudioAddress = audioAddress;
Wonsik Kim06c41a32014-08-04 18:57:47 +09001015
Wonsik Kimca17a902014-07-31 10:09:46 +09001016 mDesiredSamplingRate = samplingRate;
1017 mDesiredChannelMask = channelMask;
1018 mDesiredFormat = format;
1019
Wonsik Kim7587d292014-08-09 10:01:01 +09001020 updateAudioConfigLocked();
Wonsik Kimca17a902014-07-31 10:09:46 +09001021 }
1022 }
Wonsik Kim49e55932014-12-04 17:22:09 +09001023
1024 public void onMediaStreamVolumeChanged() {
1025 synchronized (mImplLock) {
1026 updateAudioConfigLocked();
1027 }
1028 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001029 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001030
Wonsik Kim187423c2014-06-25 14:12:48 +09001031 interface Listener {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001032 void onStateChanged(String inputId, int state);
1033 void onHardwareDeviceAdded(TvInputHardwareInfo info);
1034 void onHardwareDeviceRemoved(TvInputHardwareInfo info);
1035 void onHdmiDeviceAdded(HdmiDeviceInfo device);
1036 void onHdmiDeviceRemoved(HdmiDeviceInfo device);
1037 void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
Wonsik Kim187423c2014-06-25 14:12:48 +09001038 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001039
Wonsik Kim187423c2014-06-25 14:12:48 +09001040 private class ListenerHandler extends Handler {
1041 private static final int STATE_CHANGED = 1;
1042 private static final int HARDWARE_DEVICE_ADDED = 2;
1043 private static final int HARDWARE_DEVICE_REMOVED = 3;
Jae Seo546c6352014-08-07 11:57:01 -07001044 private static final int HDMI_DEVICE_ADDED = 4;
1045 private static final int HDMI_DEVICE_REMOVED = 5;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001046 private static final int HDMI_DEVICE_UPDATED = 6;
Wonsik Kim969167d2014-06-24 16:33:17 +09001047
1048 @Override
1049 public final void handleMessage(Message msg) {
1050 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001051 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +09001052 String inputId = (String) msg.obj;
1053 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +09001054 mListener.onStateChanged(inputId, state);
1055 break;
1056 }
1057 case HARDWARE_DEVICE_ADDED: {
1058 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1059 mListener.onHardwareDeviceAdded(info);
1060 break;
1061 }
1062 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001063 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1064 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001065 break;
1066 }
Jae Seo546c6352014-08-07 11:57:01 -07001067 case HDMI_DEVICE_ADDED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001068 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001069 mListener.onHdmiDeviceAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001070 break;
1071 }
Jae Seo546c6352014-08-07 11:57:01 -07001072 case HDMI_DEVICE_REMOVED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001073 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001074 mListener.onHdmiDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +09001075 break;
1076 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001077 case HDMI_DEVICE_UPDATED: {
Wonsik Kim184a6d62014-10-08 13:05:56 +09001078 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001079 String inputId;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001080 synchronized (mLock) {
1081 inputId = mHdmiInputIdMap.get(info.getId());
1082 }
1083 if (inputId != null) {
1084 mListener.onHdmiDeviceUpdated(inputId, info);
1085 } else {
1086 Slog.w(TAG, "Could not resolve input ID matching the device info; "
1087 + "ignoring.");
1088 }
1089 break;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001090 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001091 default: {
1092 Slog.w(TAG, "Unhandled message: " + msg);
1093 break;
1094 }
1095 }
1096 }
1097 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001098
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001099 // Listener implementations for HdmiControlService
1100
1101 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1102 @Override
1103 public void onReceived(HdmiHotplugEvent event) {
1104 synchronized (mLock) {
1105 mHdmiStateMap.put(event.getPort(), event.isConnected());
Wonsik Kimd922a542014-07-24 18:25:29 +09001106 TvInputHardwareInfo hardwareInfo =
1107 findHardwareInfoForHdmiPortLocked(event.getPort());
1108 if (hardwareInfo == null) {
1109 return;
1110 }
1111 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001112 if (inputId == null) {
1113 return;
1114 }
1115 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1116 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1117 }
1118 }
1119 }
1120
Wonsik Kim187423c2014-06-25 14:12:48 +09001121 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1122 @Override
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001123 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
Jinsuk Kim98d760e2014-12-23 07:01:51 +09001124 if (!deviceInfo.isSourceType()) return;
Wonsik Kimd922a542014-07-24 18:25:29 +09001125 synchronized (mLock) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001126 int messageType = 0;
Wonsik Kime92f8572014-08-12 18:30:24 +09001127 Object obj = null;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001128 switch (status) {
1129 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001130 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001131 mHdmiDeviceList.add(deviceInfo);
1132 } else {
1133 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1134 return;
1135 }
1136 messageType = ListenerHandler.HDMI_DEVICE_ADDED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001137 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001138 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001139 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001140 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001141 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1142 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001143 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1144 return;
1145 }
1146 messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001147 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001148 break;
1149 }
1150 case HdmiControlManager.DEVICE_EVENT_UPDATE_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 mHdmiDeviceList.add(deviceInfo);
1157 messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001158 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001159 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001160 }
1161 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001162
Wonsik Kime92f8572014-08-12 18:30:24 +09001163 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
Wonsik Kimd922a542014-07-24 18:25:29 +09001164 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1165 msg.sendToTarget();
1166 } else {
1167 mPendingHdmiDeviceEvents.add(msg);
1168 }
1169 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001170 }
Jungshik Jangcf445872014-08-20 15:06:01 +09001171
1172 private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1173 for (HdmiDeviceInfo info : mHdmiDeviceList) {
1174 if (info.getId() == id) {
1175 return info;
1176 }
1177 }
1178 return null;
1179 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001180 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001181
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001182 private final class HdmiSystemAudioModeChangeListener extends
1183 IHdmiSystemAudioModeChangeListener.Stub {
1184 @Override
1185 public void onStatusChanged(boolean enabled) throws RemoteException {
1186 synchronized (mLock) {
1187 for (int i = 0; i < mConnections.size(); ++i) {
1188 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
Jungshik Jang44bb3a52014-08-26 17:24:24 +09001189 if (impl != null) {
1190 impl.handleAudioSinkUpdated();
1191 }
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001192 }
1193 }
1194 }
1195 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001196}