blob: 7f4c42b5c6867926ca3c6ca134600f477a763918 [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 Kim969167d2014-06-24 16:33:17 +090058import com.android.server.SystemService;
59
Wonsik Kimc22dbb62014-05-26 02:26:04 +000060import java.util.ArrayList;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090061import java.util.Collections;
Wonsik Kimd922a542014-07-24 18:25:29 +090062import java.util.Iterator;
63import java.util.LinkedList;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000064import java.util.List;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090065import java.util.Map;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000066
67/**
68 * A helper class for TvInputManagerService to handle TV input hardware.
69 *
70 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
71 * calls to tv_input HAL module.
72 *
73 * @hide
74 */
Jinsuk Kim7474fac2014-07-18 17:22:26 +090075class TvInputHardwareManager implements TvInputHal.Callback {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000076 private static final String TAG = TvInputHardwareManager.class.getSimpleName();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090077
Wonsik Kim49e55932014-12-04 17:22:09 +090078 private final Context mContext;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090079 private final Listener mListener;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000080 private final TvInputHal mHal = new TvInputHal(this);
Wonsik Kimd922a542014-07-24 18:25:29 +090081 private final SparseArray<Connection> mConnections = new SparseArray<>();
82 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>();
Jae Seo546c6352014-08-07 11:57:01 -070083 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090084 /* A map from a device ID to the matching TV input ID. */
Wonsik Kimd922a542014-07-24 18:25:29 +090085 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090086 /* A map from a HDMI logical address to the matching TV input ID. */
Jae Seo546c6352014-08-07 11:57:01 -070087 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>();
Wonsik Kimd922a542014-07-24 18:25:29 +090088 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090089
Wonsik Kimd7c29182014-05-27 10:38:21 +090090 private final AudioManager mAudioManager;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090091 private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
92 new HdmiHotplugEventListener();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090093 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
Jungshik Jang1f819bb2014-08-08 10:46:54 +090094 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
95 new HdmiSystemAudioModeChangeListener();
Wonsik Kim49e55932014-12-04 17:22:09 +090096 private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() {
97 @Override
98 public void onReceive(Context context, Intent intent) {
99 handleVolumeChange(context, intent);
100 }
101 };
102 private int mCurrentIndex = 0;
103 private int mCurrentMaxIndex = 0;
Jinsuk Kimd38bf472014-08-11 11:29:18 +0900104
Wonsik Kimd922a542014-07-24 18:25:29 +0900105 // TODO: Should handle STANDBY case.
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900106 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
Wonsik Kimd922a542014-07-24 18:25:29 +0900107 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
Wonsik Kim969167d2014-06-24 16:33:17 +0900108
Wonsik Kim187423c2014-06-25 14:12:48 +0900109 // Calls to mListener should happen here.
110 private final Handler mHandler = new ListenerHandler();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000111
112 private final Object mLock = new Object();
113
Wonsik Kim187423c2014-06-25 14:12:48 +0900114 public TvInputHardwareManager(Context context, Listener listener) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900115 mContext = context;
Wonsik Kim187423c2014-06-25 14:12:48 +0900116 mListener = listener;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900117 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000118 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +0900119 }
120
121 public void onBootPhase(int phase) {
122 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Jae Seo38b32572015-06-05 14:43:11 -0700123 IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
124 ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
125 if (hdmiControlService != null) {
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900126 try {
Jae Seo38b32572015-06-05 14:43:11 -0700127 hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
128 hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
129 hdmiControlService.addSystemAudioModeChangeListener(
Jungshik Jang1f819bb2014-08-08 10:46:54 +0900130 mHdmiSystemAudioModeChangeListener);
Jae Seo38b32572015-06-05 14:43:11 -0700131 mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900132 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900133 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900134 }
Jinsuk Kim08f1ab02014-10-13 10:38:16 +0900135 } else {
136 Slog.w(TAG, "HdmiControlService is not available");
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900137 }
John Spurlockee5ad722015-03-03 16:17:21 -0500138 final IntentFilter filter = new IntentFilter();
139 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
140 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
141 mContext.registerReceiver(mVolumeReceiver, filter);
Wonsik Kim49e55932014-12-04 17:22:09 +0900142 updateVolume();
Wonsik Kim969167d2014-06-24 16:33:17 +0900143 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000144 }
145
146 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900147 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000148 synchronized (mLock) {
149 Connection connection = new Connection(info);
150 connection.updateConfigsLocked(configs);
151 mConnections.put(info.getDeviceId(), connection);
Wonsik Kimd922a542014-07-24 18:25:29 +0900152 buildHardwareListLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900153 mHandler.obtainMessage(
154 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
Wonsik Kimd922a542014-07-24 18:25:29 +0900155 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
156 processPendingHdmiDeviceEventsLocked();
157 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000158 }
159 }
160
Wonsik Kimd922a542014-07-24 18:25:29 +0900161 private void buildHardwareListLocked() {
162 mHardwareList.clear();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000163 for (int i = 0; i < mConnections.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900164 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000165 }
166 }
167
168 @Override
169 public void onDeviceUnavailable(int deviceId) {
170 synchronized (mLock) {
171 Connection connection = mConnections.get(deviceId);
172 if (connection == null) {
173 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
174 return;
175 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900176 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000177 mConnections.remove(deviceId);
Wonsik Kimd922a542014-07-24 18:25:29 +0900178 buildHardwareListLocked();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900179 TvInputHardwareInfo info = connection.getHardwareInfoLocked();
Wonsik Kimd922a542014-07-24 18:25:29 +0900180 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Jae Seo546c6352014-08-07 11:57:01 -0700181 // Remove HDMI devices linked with this hardware.
182 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900183 HdmiDeviceInfo deviceInfo = it.next();
Wonsik Kimd922a542014-07-24 18:25:29 +0900184 if (deviceInfo.getPortId() == info.getHdmiPortId()) {
Jae Seo546c6352014-08-07 11:57:01 -0700185 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
Wonsik Kimd922a542014-07-24 18:25:29 +0900186 deviceInfo).sendToTarget();
187 it.remove();
188 }
189 }
190 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900191 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900192 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000193 }
194 }
195
196 @Override
197 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
198 synchronized (mLock) {
199 Connection connection = mConnections.get(deviceId);
200 if (connection == null) {
201 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
202 + deviceId);
203 return;
204 }
205 connection.updateConfigsLocked(configs);
Wonsik Kim9a103652014-10-21 17:46:31 +0900206 String inputId = mHardwareInputIdMap.get(deviceId);
207 if (inputId != null) {
208 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim5b820a82014-10-27 11:02:10 +0900209 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900210 }
Dongwon Kang017bb852014-12-26 14:53:14 +0900211 ITvInputHardwareCallback callback = connection.getCallbackLocked();
212 if (callback != null) {
213 try {
214 callback.onStreamConfigChanged(configs);
215 } catch (RemoteException e) {
216 Slog.e(TAG, "error in onStreamConfigurationChanged", e);
217 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000218 }
219 }
220 }
221
Terry Heoc086a3d2014-06-18 14:26:44 +0900222 @Override
223 public void onFirstFrameCaptured(int deviceId, int streamId) {
224 synchronized (mLock) {
225 Connection connection = mConnections.get(deviceId);
226 if (connection == null) {
227 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
228 + deviceId);
229 return;
230 }
231 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
232 if (runnable != null) {
233 runnable.run();
234 connection.setOnFirstFrameCapturedLocked(null);
235 }
236 }
237 }
238
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000239 public List<TvInputHardwareInfo> getHardwareList() {
240 synchronized (mLock) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900241 return Collections.unmodifiableList(mHardwareList);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000242 }
243 }
244
Jae Seo546c6352014-08-07 11:57:01 -0700245 public List<HdmiDeviceInfo> getHdmiDeviceList() {
Wonsik Kimd922a542014-07-24 18:25:29 +0900246 synchronized (mLock) {
Jae Seo546c6352014-08-07 11:57:01 -0700247 return Collections.unmodifiableList(mHdmiDeviceList);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900248 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900249 }
250
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900251 private boolean checkUidChangedLocked(
252 Connection connection, int callingUid, int resolvedUserId) {
253 Integer connectionCallingUid = connection.getCallingUidLocked();
254 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700255 return connectionCallingUid == null || connectionResolvedUserId == null
256 || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId;
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900257 }
258
Wonsik Kim969167d2014-06-24 16:33:17 +0900259 private int convertConnectedToState(boolean connected) {
260 if (connected) {
261 return INPUT_STATE_CONNECTED;
262 } else {
263 return INPUT_STATE_DISCONNECTED;
264 }
265 }
266
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900267 public void addHardwareTvInput(int deviceId, TvInputInfo info) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900268 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900269 String oldInputId = mHardwareInputIdMap.get(deviceId);
270 if (oldInputId != null) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900271 Slog.w(TAG, "Trying to override previous registration: old = "
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900272 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
Wonsik Kim969167d2014-06-24 16:33:17 +0900273 + info + ":" + deviceId);
274 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900275 mHardwareInputIdMap.put(deviceId, info.getId());
276 mInputMap.put(info.getId(), info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900277
Wonsik Kim9a103652014-10-21 17:46:31 +0900278 // Process pending state changes
279
280 // For logical HDMI devices, they have information from HDMI CEC signals.
Wonsik Kim969167d2014-06-24 16:33:17 +0900281 for (int i = 0; i < mHdmiStateMap.size(); ++i) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900282 TvInputHardwareInfo hardwareInfo =
283 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
284 if (hardwareInfo == null) {
285 continue;
286 }
287 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900288 if (inputId != null && inputId.equals(info.getId())) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900289 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim969167d2014-06-24 16:33:17 +0900290 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
291 inputId).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900292 return;
Wonsik Kim969167d2014-06-24 16:33:17 +0900293 }
294 }
Wonsik Kim9a103652014-10-21 17:46:31 +0900295 // For the rest of the devices, we can tell by the number of available streams.
296 Connection connection = mConnections.get(deviceId);
297 if (connection != null) {
298 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
299 convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
300 info.getId()).sendToTarget();
Wonsik Kim9a103652014-10-21 17:46:31 +0900301 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900302 }
303 }
304
Wonsik Kim38feae92014-07-21 21:35:50 +0900305 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
306 for (int i = 0; i < map.size(); ++i) {
307 if (map.valueAt(i).equals(value)) {
308 return i;
309 }
310 }
311 return -1;
312 }
313
Wonsik Kim71dfa962014-11-15 14:18:49 +0900314 private static boolean intArrayContains(int[] array, int value) {
315 for (int element : array) {
316 if (element == value) return true;
317 }
318 return false;
319 }
320
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900321 public void addHdmiTvInput(int id, TvInputInfo info) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900322 if (info.getType() != TvInputInfo.TYPE_HDMI) {
323 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
324 }
325 synchronized (mLock) {
326 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900327 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
328 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900329 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
330 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900331 String oldInputId = mHdmiInputIdMap.get(id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900332 if (oldInputId != null) {
333 Slog.w(TAG, "Trying to override previous registration: old = "
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900334 + mInputMap.get(oldInputId) + ":" + id + ", new = "
335 + info + ":" + id);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900336 }
Jinsuk Kim8960d1b2014-08-13 10:48:30 +0900337 mHdmiInputIdMap.put(id, info.getId());
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900338 mInputMap.put(info.getId(), info);
339 }
340 }
341
342 public void removeTvInput(String inputId) {
343 synchronized (mLock) {
344 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900345 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900346 if (hardwareIndex >= 0) {
347 mHardwareInputIdMap.removeAt(hardwareIndex);
348 }
Jae Seo546c6352014-08-07 11:57:01 -0700349 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
350 if (deviceIndex >= 0) {
351 mHdmiInputIdMap.removeAt(deviceIndex);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900352 }
353 }
354 }
355
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000356 /**
357 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
358 * the object, and if more than one process attempts to create hardware with the same deviceId,
359 * the latest service will get the object and all the other hardware are released. The
360 * release is notified via ITvInputHardwareCallback.onReleased().
361 */
362 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900363 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000364 if (callback == null) {
365 throw new NullPointerException();
366 }
367 synchronized (mLock) {
368 Connection connection = mConnections.get(deviceId);
369 if (connection == null) {
370 Slog.e(TAG, "Invalid deviceId : " + deviceId);
371 return null;
372 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900373 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900374 TvInputHardwareImpl hardware =
375 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000376 try {
377 callback.asBinder().linkToDeath(connection, 0);
378 } catch (RemoteException e) {
379 hardware.release();
380 return null;
381 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900382 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000383 }
384 return connection.getHardwareLocked();
385 }
386 }
387
388 /**
389 * Release the specified hardware.
390 */
391 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
392 int resolvedUserId) {
393 synchronized (mLock) {
394 Connection connection = mConnections.get(deviceId);
395 if (connection == null) {
396 Slog.e(TAG, "Invalid deviceId : " + deviceId);
397 return;
398 }
399 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900400 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000401 return;
402 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900403 connection.resetLocked(null, null, null, null, null);
404 }
405 }
406
Wonsik Kimd922a542014-07-24 18:25:29 +0900407 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
408 for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900409 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
410 && hardwareInfo.getHdmiPortId() == port) {
Wonsik Kimd922a542014-07-24 18:25:29 +0900411 return hardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +0900412 }
413 }
414 return null;
415 }
416
Terry Heoc086a3d2014-06-18 14:26:44 +0900417 private int findDeviceIdForInputIdLocked(String inputId) {
418 for (int i = 0; i < mConnections.size(); ++i) {
419 Connection connection = mConnections.get(i);
420 if (connection.getInfoLocked().getId().equals(inputId)) {
421 return i;
422 }
423 }
424 return -1;
425 }
426
427 /**
428 * Get the list of TvStreamConfig which is buffered mode.
429 */
430 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
431 int resolvedUserId) {
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700432 List<TvStreamConfig> configsList = new ArrayList<>();
Terry Heoc086a3d2014-06-18 14:26:44 +0900433 synchronized (mLock) {
434 int deviceId = findDeviceIdForInputIdLocked(inputId);
435 if (deviceId < 0) {
436 Slog.e(TAG, "Invalid inputId : " + inputId);
437 return configsList;
438 }
439 Connection connection = mConnections.get(deviceId);
440 for (TvStreamConfig config : connection.getConfigsLocked()) {
441 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
442 configsList.add(config);
443 }
444 }
445 }
446 return configsList;
447 }
448
449 /**
450 * Take a snapshot of the given TV input into the provided Surface.
451 */
452 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
453 int callingUid, int resolvedUserId) {
454 synchronized (mLock) {
455 int deviceId = findDeviceIdForInputIdLocked(inputId);
456 if (deviceId < 0) {
457 Slog.e(TAG, "Invalid inputId : " + inputId);
458 return false;
459 }
460 Connection connection = mConnections.get(deviceId);
461 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
462 if (hardwareImpl != null) {
463 // Stop previous capture.
464 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
465 if (runnable != null) {
466 runnable.run();
467 connection.setOnFirstFrameCapturedLocked(null);
468 }
469
470 boolean result = hardwareImpl.startCapture(surface, config);
471 if (result) {
472 connection.setOnFirstFrameCapturedLocked(new Runnable() {
473 @Override
474 public void run() {
475 hardwareImpl.stopCapture(config);
476 }
477 });
478 }
479 return result;
480 }
481 }
482 return false;
483 }
484
Wonsik Kimd922a542014-07-24 18:25:29 +0900485 private void processPendingHdmiDeviceEventsLocked() {
486 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
487 Message msg = it.next();
Jungshik Jang61f4fbd2014-08-06 19:21:12 +0900488 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
Wonsik Kimd922a542014-07-24 18:25:29 +0900489 TvInputHardwareInfo hardwareInfo =
490 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
491 if (hardwareInfo != null) {
492 msg.sendToTarget();
493 it.remove();
494 }
495 }
496 }
497
Wonsik Kim49e55932014-12-04 17:22:09 +0900498 private void updateVolume() {
499 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
500 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
501 }
502
503 private void handleVolumeChange(Context context, Intent intent) {
504 String action = intent.getAction();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700505 switch (action) {
506 case AudioManager.VOLUME_CHANGED_ACTION: {
507 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
508 if (streamType != AudioManager.STREAM_MUSIC) {
509 return;
510 }
511 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
512 if (index == mCurrentIndex) {
513 return;
514 }
515 mCurrentIndex = index;
516 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900517 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700518 case AudioManager.STREAM_MUTE_CHANGED_ACTION: {
519 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
520 if (streamType != AudioManager.STREAM_MUSIC) {
521 return;
522 }
523 // volume index will be updated at onMediaStreamVolumeChanged() through
524 // updateVolume().
525 break;
Wonsik Kim49e55932014-12-04 17:22:09 +0900526 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700527 default:
528 Slog.w(TAG, "Unrecognized intent: " + intent);
Wonsik Kim49e55932014-12-04 17:22:09 +0900529 return;
Wonsik Kim49e55932014-12-04 17:22:09 +0900530 }
531 synchronized (mLock) {
532 for (int i = 0; i < mConnections.size(); ++i) {
533 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
534 if (hardwareImpl != null) {
535 hardwareImpl.onMediaStreamVolumeChanged();
536 }
537 }
538 }
539 }
540
541 private float getMediaStreamVolume() {
John Spurlockee5ad722015-03-03 16:17:21 -0500542 return (float) mCurrentIndex / (float) mCurrentMaxIndex;
Wonsik Kim49e55932014-12-04 17:22:09 +0900543 }
544
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000545 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900546 private final TvInputHardwareInfo mHardwareInfo;
547 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000548 private TvInputHardwareImpl mHardware = null;
549 private ITvInputHardwareCallback mCallback;
550 private TvStreamConfig[] mConfigs = null;
551 private Integer mCallingUid = null;
552 private Integer mResolvedUserId = null;
Terry Heoc086a3d2014-06-18 14:26:44 +0900553 private Runnable mOnFirstFrameCaptured;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000554
Wonsik Kim969167d2014-06-24 16:33:17 +0900555 public Connection(TvInputHardwareInfo hardwareInfo) {
556 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000557 }
558
559 // *Locked methods assume TvInputHardwareManager.mLock is held.
560
Wonsik Kim969167d2014-06-24 16:33:17 +0900561 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
562 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000563 if (mHardware != null) {
564 try {
565 mCallback.onReleased();
566 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900567 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000568 }
569 mHardware.release();
570 }
571 mHardware = hardware;
572 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900573 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000574 mCallingUid = callingUid;
575 mResolvedUserId = resolvedUserId;
Terry Heoc086a3d2014-06-18 14:26:44 +0900576 mOnFirstFrameCaptured = null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000577
578 if (mHardware != null && mCallback != null) {
579 try {
580 mCallback.onStreamConfigChanged(getConfigsLocked());
581 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900582 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000583 }
584 }
585 }
586
587 public void updateConfigsLocked(TvStreamConfig[] configs) {
588 mConfigs = configs;
589 }
590
Wonsik Kim969167d2014-06-24 16:33:17 +0900591 public TvInputHardwareInfo getHardwareInfoLocked() {
592 return mHardwareInfo;
593 }
594
595 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000596 return mInfo;
597 }
598
599 public ITvInputHardware getHardwareLocked() {
600 return mHardware;
601 }
602
Terry Heoc086a3d2014-06-18 14:26:44 +0900603 public TvInputHardwareImpl getHardwareImplLocked() {
604 return mHardware;
605 }
606
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000607 public ITvInputHardwareCallback getCallbackLocked() {
608 return mCallback;
609 }
610
611 public TvStreamConfig[] getConfigsLocked() {
612 return mConfigs;
613 }
614
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900615 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000616 return mCallingUid;
617 }
618
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900619 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000620 return mResolvedUserId;
621 }
622
Terry Heoc086a3d2014-06-18 14:26:44 +0900623 public void setOnFirstFrameCapturedLocked(Runnable runnable) {
624 mOnFirstFrameCaptured = runnable;
625 }
626
627 public Runnable getOnFirstFrameCapturedLocked() {
628 return mOnFirstFrameCaptured;
629 }
630
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000631 @Override
632 public void binderDied() {
633 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900634 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000635 }
636 }
637 }
638
639 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
640 private final TvInputHardwareInfo mInfo;
641 private boolean mReleased = false;
642 private final Object mImplLock = new Object();
643
Wonsik Kim06c41a32014-08-04 18:57:47 +0900644 private final AudioManager.OnAudioPortUpdateListener mAudioListener =
645 new AudioManager.OnAudioPortUpdateListener() {
646 @Override
647 public void onAudioPortListUpdate(AudioPort[] portList) {
648 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900649 updateAudioConfigLocked();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900650 }
651 }
652
653 @Override
654 public void onAudioPatchListUpdate(AudioPatch[] patchList) {
655 // No-op
656 }
657
658 @Override
659 public void onServiceDied() {
660 synchronized (mImplLock) {
661 mAudioSource = null;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900662 mAudioSink.clear();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900663 mAudioPatch = null;
664 }
665 }
666 };
667 private int mOverrideAudioType = AudioManager.DEVICE_NONE;
668 private String mOverrideAudioAddress = "";
669 private AudioDevicePort mAudioSource;
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900670 private List<AudioDevicePort> mAudioSink = new ArrayList<>();
Wonsik Kimd7c29182014-05-27 10:38:21 +0900671 private AudioPatch mAudioPatch = null;
Wonsik Kim485e7e12015-01-26 17:28:55 +0900672 // Set to an invalid value for a volume, so that current volume can be applied at the
673 // first call to updateAudioConfigLocked().
674 private float mCommittedVolume = -1f;
Wonsik Kim49e55932014-12-04 17:22:09 +0900675 private float mSourceVolume = 0.0f;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900676
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900677 private TvStreamConfig mActiveConfig = null;
678
Wonsik Kimca17a902014-07-31 10:09:46 +0900679 private int mDesiredSamplingRate = 0;
680 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
681 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
682
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000683 public TvInputHardwareImpl(TvInputHardwareInfo info) {
684 mInfo = info;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900685 mAudioManager.registerAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900686 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900687 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900688 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kimca17a902014-07-31 10:09:46 +0900689 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900690 }
691
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900692 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
693 sinks.clear();
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700694 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900695 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
696 return;
697 }
698 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
Paul McLeanceb47aa2015-02-19 15:09:53 -0800699 for (AudioDevicePort port : devicePorts) {
700 if ((port.type() & sinkDevice) != 0) {
701 sinks.add(port);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900702 }
703 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900704 }
705
706 private AudioDevicePort findAudioDevicePort(int type, String address) {
707 if (type == AudioManager.DEVICE_NONE) {
708 return null;
709 }
Jae Seo6e4cbfd2015-06-21 16:40:34 -0700710 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900711 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
712 return null;
713 }
Paul McLeanceb47aa2015-02-19 15:09:53 -0800714 for (AudioDevicePort port : devicePorts) {
715 if (port.type() == type && port.address().equals(address)) {
716 return port;
Wonsik Kimca17a902014-07-31 10:09:46 +0900717 }
718 }
719 return null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000720 }
721
722 public void release() {
723 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900724 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
Wonsik Kimd7c29182014-05-27 10:38:21 +0900725 if (mAudioPatch != null) {
726 mAudioManager.releaseAudioPatch(mAudioPatch);
727 mAudioPatch = null;
728 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000729 mReleased = true;
730 }
731 }
732
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900733 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
734 // attempts to call setSurface with different TvStreamConfig objects, the last call will
735 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000736 @Override
737 public boolean setSurface(Surface surface, TvStreamConfig config)
738 throws RemoteException {
739 synchronized (mImplLock) {
740 if (mReleased) {
741 throw new IllegalStateException("Device already released.");
742 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900743
Wonsik Kim102670f2014-11-20 14:38:02 +0900744 int result = TvInputHal.SUCCESS;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900745 if (surface == null) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900746 // The value of config is ignored when surface == null.
747 if (mActiveConfig != null) {
748 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
749 mActiveConfig = null;
750 } else {
751 // We already have no active stream.
752 return true;
753 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900754 } else {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900755 // It's impossible to set a non-null surface with a null config.
756 if (config == null) {
757 return false;
758 }
759 // Remove stream only if we have an existing active configuration.
760 if (mActiveConfig != null && !config.equals(mActiveConfig)) {
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900761 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
762 if (result != TvInputHal.SUCCESS) {
763 mActiveConfig = null;
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900764 }
765 }
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900766 // Proceed only if all previous operations succeeded.
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900767 if (result == TvInputHal.SUCCESS) {
Wonsik Kimb683e6e2014-11-17 16:31:39 +0900768 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
769 if (result == TvInputHal.SUCCESS) {
770 mActiveConfig = config;
771 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900772 }
773 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900774 updateAudioConfigLocked();
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900775 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000776 }
777 }
778
Wonsik Kim7587d292014-08-09 10:01:01 +0900779 /**
780 * Update audio configuration (source, sink, patch) all up to current state.
781 */
782 private void updateAudioConfigLocked() {
783 boolean sinkUpdated = updateAudioSinkLocked();
784 boolean sourceUpdated = updateAudioSourceLocked();
785 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
786 // because Java won't evaluate the latter if the former is true.
787
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900788 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900789 if (mAudioPatch != null) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900790 mAudioManager.releaseAudioPatch(mAudioPatch);
791 mAudioPatch = null;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900792 }
793 return;
794 }
795
Wonsik Kim49e55932014-12-04 17:22:09 +0900796 updateVolume();
797 float volume = mSourceVolume * getMediaStreamVolume();
Wonsik Kim8e45a332014-08-04 10:17:18 +0900798 AudioGainConfig sourceGainConfig = null;
Wonsik Kim49e55932014-12-04 17:22:09 +0900799 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
Wonsik Kim8e45a332014-08-04 10:17:18 +0900800 AudioGain sourceGain = null;
801 for (AudioGain gain : mAudioSource.gains()) {
802 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
803 sourceGain = gain;
804 break;
805 }
806 }
807 // NOTE: we only change the source gain in MODE_JOINT here.
808 if (sourceGain != null) {
809 int steps = (sourceGain.maxValue() - sourceGain.minValue())
810 / sourceGain.stepValue();
811 int gainValue = sourceGain.minValue();
Wonsik Kim49e55932014-12-04 17:22:09 +0900812 if (volume < 1.0f) {
813 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900814 } else {
815 gainValue = sourceGain.maxValue();
816 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900817 // size of gain values is 1 in MODE_JOINT
818 int[] gainValues = new int[] { gainValue };
Wonsik Kim8e45a332014-08-04 10:17:18 +0900819 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
820 sourceGain.channelMask(), gainValues, 0);
821 } else {
822 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
823 }
824 }
825
Wonsik Kimca17a902014-07-31 10:09:46 +0900826 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900827 List<AudioPortConfig> sinkConfigs = new ArrayList<>();
Wonsik Kimca17a902014-07-31 10:09:46 +0900828 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
Wonsik Kim7587d292014-08-09 10:01:01 +0900829 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900830
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900831 for (AudioDevicePort audioSink : mAudioSink) {
832 AudioPortConfig sinkConfig = audioSink.activeConfig();
833 int sinkSamplingRate = mDesiredSamplingRate;
834 int sinkChannelMask = mDesiredChannelMask;
835 int sinkFormat = mDesiredFormat;
836 // If sinkConfig != null and values are set to default,
837 // fill in the sinkConfig values.
838 if (sinkConfig != null) {
839 if (sinkSamplingRate == 0) {
840 sinkSamplingRate = sinkConfig.samplingRate();
841 }
842 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
843 sinkChannelMask = sinkConfig.channelMask();
844 }
845 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
846 sinkChannelMask = sinkConfig.format();
847 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900848 }
Wonsik Kim71dfa962014-11-15 14:18:49 +0900849
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900850 if (sinkConfig == null
851 || sinkConfig.samplingRate() != sinkSamplingRate
852 || sinkConfig.channelMask() != sinkChannelMask
853 || sinkConfig.format() != sinkFormat) {
854 // Check for compatibility and reset to default if necessary.
855 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
856 && audioSink.samplingRates().length > 0) {
857 sinkSamplingRate = audioSink.samplingRates()[0];
858 }
859 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
860 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
861 }
862 if (!intArrayContains(audioSink.formats(), sinkFormat)) {
863 sinkFormat = AudioFormat.ENCODING_DEFAULT;
864 }
865 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
866 sinkFormat, null);
867 shouldRecreateAudioPatch = true;
Wonsik Kim71dfa962014-11-15 14:18:49 +0900868 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900869 sinkConfigs.add(sinkConfig);
Wonsik Kimca17a902014-07-31 10:09:46 +0900870 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900871 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
872 // non-empty at the beginning of this method.
873 AudioPortConfig sinkConfig = sinkConfigs.get(0);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900874 if (sourceConfig == null || sourceGainConfig != null) {
Wonsik Kim71dfa962014-11-15 14:18:49 +0900875 int sourceSamplingRate = 0;
876 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
877 sourceSamplingRate = sinkConfig.samplingRate();
878 } else if (mAudioSource.samplingRates().length > 0) {
879 // Use any sampling rate and hope audio patch can handle resampling...
880 sourceSamplingRate = mAudioSource.samplingRates()[0];
881 }
882 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
883 for (int inChannelMask : mAudioSource.channelMasks()) {
884 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
885 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
886 sourceChannelMask = inChannelMask;
887 break;
888 }
889 }
890 int sourceFormat = AudioFormat.ENCODING_DEFAULT;
891 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
892 sourceFormat = sinkConfig.format();
893 }
894 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
895 sourceFormat, sourceGainConfig);
Wonsik Kim8e45a332014-08-04 10:17:18 +0900896 shouldRecreateAudioPatch = true;
Wonsik Kimca17a902014-07-31 10:09:46 +0900897 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900898 if (shouldRecreateAudioPatch) {
Wonsik Kim49e55932014-12-04 17:22:09 +0900899 mCommittedVolume = volume;
Wonsik Kim8e45a332014-08-04 10:17:18 +0900900 mAudioManager.createAudioPatch(
901 audioPatchArray,
902 new AudioPortConfig[] { sourceConfig },
Jae Seo93ff14b2015-06-21 14:08:54 -0700903 sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
Wonsik Kim8e45a332014-08-04 10:17:18 +0900904 mAudioPatch = audioPatchArray[0];
Wonsik Kim71dfa962014-11-15 14:18:49 +0900905 if (sourceGainConfig != null) {
906 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
907 }
Wonsik Kim8e45a332014-08-04 10:17:18 +0900908 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900909 }
910
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000911 @Override
Wonsik Kim8e45a332014-08-04 10:17:18 +0900912 public void setStreamVolume(float volume) throws RemoteException {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000913 synchronized (mImplLock) {
914 if (mReleased) {
915 throw new IllegalStateException("Device already released.");
916 }
Wonsik Kim49e55932014-12-04 17:22:09 +0900917 mSourceVolume = volume;
Wonsik Kim7587d292014-08-09 10:01:01 +0900918 updateAudioConfigLocked();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000919 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000920 }
921
922 @Override
923 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
924 synchronized (mImplLock) {
925 if (mReleased) {
926 throw new IllegalStateException("Device already released.");
927 }
928 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900929 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000930 return false;
931 }
932 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
933 return false;
934 }
Terry Heoc086a3d2014-06-18 14:26:44 +0900935
936 private boolean startCapture(Surface surface, TvStreamConfig config) {
937 synchronized (mImplLock) {
938 if (mReleased) {
939 return false;
940 }
941 if (surface == null || config == null) {
942 return false;
943 }
944 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
945 return false;
946 }
947
Wonsik Kim8f24a8b2014-10-22 16:27:39 +0900948 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
Terry Heoc086a3d2014-06-18 14:26:44 +0900949 return result == TvInputHal.SUCCESS;
950 }
951 }
952
953 private boolean stopCapture(TvStreamConfig config) {
954 synchronized (mImplLock) {
955 if (mReleased) {
956 return false;
957 }
958 if (config == null) {
959 return false;
960 }
961
962 int result = mHal.removeStream(mInfo.getDeviceId(), config);
963 return result == TvInputHal.SUCCESS;
964 }
965 }
Wonsik Kimca17a902014-07-31 10:09:46 +0900966
Wonsik Kim7587d292014-08-09 10:01:01 +0900967 private boolean updateAudioSourceLocked() {
Wonsik Kim06c41a32014-08-04 18:57:47 +0900968 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
Wonsik Kim7587d292014-08-09 10:01:01 +0900969 return false;
Wonsik Kim06c41a32014-08-04 18:57:47 +0900970 }
Wonsik Kim7587d292014-08-09 10:01:01 +0900971 AudioDevicePort previousSource = mAudioSource;
972 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
973 return mAudioSource == null ? (previousSource != null)
974 : !mAudioSource.equals(previousSource);
975 }
976
977 private boolean updateAudioSinkLocked() {
978 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
979 return false;
980 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900981 List<AudioDevicePort> previousSink = mAudioSink;
982 mAudioSink = new ArrayList<>();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900983 if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900984 findAudioSinkFromAudioPolicy(mAudioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900985 } else {
986 AudioDevicePort audioSink =
987 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
988 if (audioSink != null) {
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900989 mAudioSink.add(audioSink);
Wonsik Kim06c41a32014-08-04 18:57:47 +0900990 }
991 }
Wonsik Kimb2b85f92015-01-19 16:07:48 +0900992
993 // Returns true if mAudioSink and previousSink differs.
994 if (mAudioSink.size() != previousSink.size()) {
995 return true;
996 }
997 previousSink.removeAll(mAudioSink);
998 return !previousSink.isEmpty();
Wonsik Kim06c41a32014-08-04 18:57:47 +0900999 }
1000
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001001 private void handleAudioSinkUpdated() {
1002 synchronized (mImplLock) {
Wonsik Kim7587d292014-08-09 10:01:01 +09001003 updateAudioConfigLocked();
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001004 }
1005 }
1006
Wonsik Kimca17a902014-07-31 10:09:46 +09001007 @Override
1008 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1009 int channelMask, int format) {
1010 synchronized (mImplLock) {
Wonsik Kim06c41a32014-08-04 18:57:47 +09001011 mOverrideAudioType = audioType;
1012 mOverrideAudioAddress = audioAddress;
Wonsik Kim06c41a32014-08-04 18:57:47 +09001013
Wonsik Kimca17a902014-07-31 10:09:46 +09001014 mDesiredSamplingRate = samplingRate;
1015 mDesiredChannelMask = channelMask;
1016 mDesiredFormat = format;
1017
Wonsik Kim7587d292014-08-09 10:01:01 +09001018 updateAudioConfigLocked();
Wonsik Kimca17a902014-07-31 10:09:46 +09001019 }
1020 }
Wonsik Kim49e55932014-12-04 17:22:09 +09001021
1022 public void onMediaStreamVolumeChanged() {
1023 synchronized (mImplLock) {
1024 updateAudioConfigLocked();
1025 }
1026 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001027 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001028
Wonsik Kim187423c2014-06-25 14:12:48 +09001029 interface Listener {
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001030 void onStateChanged(String inputId, int state);
1031 void onHardwareDeviceAdded(TvInputHardwareInfo info);
1032 void onHardwareDeviceRemoved(TvInputHardwareInfo info);
1033 void onHdmiDeviceAdded(HdmiDeviceInfo device);
1034 void onHdmiDeviceRemoved(HdmiDeviceInfo device);
1035 void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
Wonsik Kim187423c2014-06-25 14:12:48 +09001036 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001037
Wonsik Kim187423c2014-06-25 14:12:48 +09001038 private class ListenerHandler extends Handler {
1039 private static final int STATE_CHANGED = 1;
1040 private static final int HARDWARE_DEVICE_ADDED = 2;
1041 private static final int HARDWARE_DEVICE_REMOVED = 3;
Jae Seo546c6352014-08-07 11:57:01 -07001042 private static final int HDMI_DEVICE_ADDED = 4;
1043 private static final int HDMI_DEVICE_REMOVED = 5;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001044 private static final int HDMI_DEVICE_UPDATED = 6;
Wonsik Kim969167d2014-06-24 16:33:17 +09001045
1046 @Override
1047 public final void handleMessage(Message msg) {
1048 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +09001049 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +09001050 String inputId = (String) msg.obj;
1051 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +09001052 mListener.onStateChanged(inputId, state);
1053 break;
1054 }
1055 case HARDWARE_DEVICE_ADDED: {
1056 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1057 mListener.onHardwareDeviceAdded(info);
1058 break;
1059 }
1060 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +09001061 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1062 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001063 break;
1064 }
Jae Seo546c6352014-08-07 11:57:01 -07001065 case HDMI_DEVICE_ADDED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001066 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001067 mListener.onHdmiDeviceAdded(info);
Wonsik Kim187423c2014-06-25 14:12:48 +09001068 break;
1069 }
Jae Seo546c6352014-08-07 11:57:01 -07001070 case HDMI_DEVICE_REMOVED: {
Jungshik Jang61f4fbd2014-08-06 19:21:12 +09001071 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo546c6352014-08-07 11:57:01 -07001072 mListener.onHdmiDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +09001073 break;
1074 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001075 case HDMI_DEVICE_UPDATED: {
Wonsik Kim184a6d62014-10-08 13:05:56 +09001076 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
Jae Seo6e4cbfd2015-06-21 16:40:34 -07001077 String inputId;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001078 synchronized (mLock) {
1079 inputId = mHdmiInputIdMap.get(info.getId());
1080 }
1081 if (inputId != null) {
1082 mListener.onHdmiDeviceUpdated(inputId, info);
1083 } else {
1084 Slog.w(TAG, "Could not resolve input ID matching the device info; "
1085 + "ignoring.");
1086 }
1087 break;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001088 }
Wonsik Kim969167d2014-06-24 16:33:17 +09001089 default: {
1090 Slog.w(TAG, "Unhandled message: " + msg);
1091 break;
1092 }
1093 }
1094 }
1095 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001096
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001097 // Listener implementations for HdmiControlService
1098
1099 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1100 @Override
1101 public void onReceived(HdmiHotplugEvent event) {
1102 synchronized (mLock) {
1103 mHdmiStateMap.put(event.getPort(), event.isConnected());
Wonsik Kimd922a542014-07-24 18:25:29 +09001104 TvInputHardwareInfo hardwareInfo =
1105 findHardwareInfoForHdmiPortLocked(event.getPort());
1106 if (hardwareInfo == null) {
1107 return;
1108 }
1109 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001110 if (inputId == null) {
1111 return;
1112 }
1113 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1114 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1115 }
1116 }
1117 }
1118
Wonsik Kim187423c2014-06-25 14:12:48 +09001119 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1120 @Override
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001121 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
Jinsuk Kim98d760e2014-12-23 07:01:51 +09001122 if (!deviceInfo.isSourceType()) return;
Wonsik Kimd922a542014-07-24 18:25:29 +09001123 synchronized (mLock) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001124 int messageType = 0;
Wonsik Kime92f8572014-08-12 18:30:24 +09001125 Object obj = null;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001126 switch (status) {
1127 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001128 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001129 mHdmiDeviceList.add(deviceInfo);
1130 } else {
1131 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1132 return;
1133 }
1134 messageType = ListenerHandler.HDMI_DEVICE_ADDED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001135 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001136 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001137 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001138 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001139 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1140 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001141 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1142 return;
1143 }
1144 messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
Wonsik Kime92f8572014-08-12 18:30:24 +09001145 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001146 break;
1147 }
1148 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
Jungshik Jangcf445872014-08-20 15:06:01 +09001149 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1150 if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001151 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1152 return;
1153 }
1154 mHdmiDeviceList.add(deviceInfo);
1155 messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
Wonsik Kim184a6d62014-10-08 13:05:56 +09001156 obj = deviceInfo;
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001157 break;
Wonsik Kimd922a542014-07-24 18:25:29 +09001158 }
1159 }
Jungshik Jang61daf6b2014-08-08 11:38:28 +09001160
Wonsik Kime92f8572014-08-12 18:30:24 +09001161 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
Wonsik Kimd922a542014-07-24 18:25:29 +09001162 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1163 msg.sendToTarget();
1164 } else {
1165 mPendingHdmiDeviceEvents.add(msg);
1166 }
1167 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001168 }
Jungshik Jangcf445872014-08-20 15:06:01 +09001169
1170 private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1171 for (HdmiDeviceInfo info : mHdmiDeviceList) {
1172 if (info.getId() == id) {
1173 return info;
1174 }
1175 }
1176 return null;
1177 }
Wonsik Kim187423c2014-06-25 14:12:48 +09001178 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +09001179
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001180 private final class HdmiSystemAudioModeChangeListener extends
1181 IHdmiSystemAudioModeChangeListener.Stub {
1182 @Override
1183 public void onStatusChanged(boolean enabled) throws RemoteException {
1184 synchronized (mLock) {
1185 for (int i = 0; i < mConnections.size(); ++i) {
1186 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
Jungshik Jang44bb3a52014-08-26 17:24:24 +09001187 if (impl != null) {
1188 impl.handleAudioSinkUpdated();
1189 }
Jungshik Jang1f819bb2014-08-08 10:46:54 +09001190 }
1191 }
1192 }
1193 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001194}