blob: d05515d1982532c18661506e31c0735b09382c80 [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 Kimc22dbb62014-05-26 02:26:04 +000022import android.content.Context;
Wonsik Kim187423c2014-06-25 14:12:48 +090023import android.hardware.hdmi.HdmiCecDeviceInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090024import android.hardware.hdmi.HdmiHotplugEvent;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090025import android.hardware.hdmi.IHdmiControlService;
Wonsik Kim187423c2014-06-25 14:12:48 +090026import android.hardware.hdmi.IHdmiDeviceEventListener;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090027import android.hardware.hdmi.IHdmiHotplugEventListener;
28import android.hardware.hdmi.IHdmiInputChangeListener;
Wonsik Kimd7c29182014-05-27 10:38:21 +090029import android.media.AudioDevicePort;
30import android.media.AudioManager;
31import android.media.AudioPatch;
32import android.media.AudioPort;
33import android.media.AudioPortConfig;
Jae Seod5cc4a22014-05-30 16:57:43 -070034import android.media.tv.ITvInputHardware;
35import android.media.tv.ITvInputHardwareCallback;
36import android.media.tv.TvInputHardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090037import android.media.tv.TvInputInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070038import android.media.tv.TvStreamConfig;
Wonsik Kim969167d2014-06-24 16:33:17 +090039import android.os.Handler;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000040import android.os.IBinder;
Wonsik Kim969167d2014-06-24 16:33:17 +090041import android.os.Looper;
42import android.os.Message;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000043import android.os.RemoteException;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090044import android.os.ServiceManager;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090045import android.util.ArrayMap;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000046import android.util.Slog;
47import android.util.SparseArray;
Wonsik Kim969167d2014-06-24 16:33:17 +090048import android.util.SparseBooleanArray;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000049import android.view.KeyEvent;
50import android.view.Surface;
51
Wonsik Kim969167d2014-06-24 16:33:17 +090052import com.android.server.SystemService;
53
Wonsik Kimc22dbb62014-05-26 02:26:04 +000054import java.util.ArrayList;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090055import java.util.Collections;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000056import java.util.HashSet;
57import java.util.List;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090058import java.util.Map;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000059import java.util.Set;
60
61/**
62 * A helper class for TvInputManagerService to handle TV input hardware.
63 *
64 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
65 * calls to tv_input HAL module.
66 *
67 * @hide
68 */
Jinsuk Kim7474fac2014-07-18 17:22:26 +090069class TvInputHardwareManager implements TvInputHal.Callback {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000070 private static final String TAG = TvInputHardwareManager.class.getSimpleName();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090071
72 private final Context mContext;
73 private final Listener mListener;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000074 private final TvInputHal mHal = new TvInputHal(this);
75 private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
76 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090077 /* A map from a device ID to the matching TV input ID. */
78 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<String>();
79 /* A map from a HDMI logical address to the matching TV input ID. */
80 private final SparseArray<String> mHdmiCecInputIdMap = new SparseArray<String>();
81 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<String, TvInputInfo>();
82
Wonsik Kimd7c29182014-05-27 10:38:21 +090083 private final AudioManager mAudioManager;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090084 private IHdmiControlService mHdmiControlService;
Jinsuk Kim7474fac2014-07-18 17:22:26 +090085 private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
86 new HdmiHotplugEventListener();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090087 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
Jinsuk Kim7474fac2014-07-18 17:22:26 +090088 private final IHdmiInputChangeListener mHdmiInputChangeListener = new HdmiInputChangeListener();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +090089 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
90 // TODO: Should handle INACTIVE case.
91 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
Wonsik Kim969167d2014-06-24 16:33:17 +090092
Wonsik Kim187423c2014-06-25 14:12:48 +090093 // Calls to mListener should happen here.
94 private final Handler mHandler = new ListenerHandler();
Wonsik Kimc22dbb62014-05-26 02:26:04 +000095
96 private final Object mLock = new Object();
97
Wonsik Kim187423c2014-06-25 14:12:48 +090098 public TvInputHardwareManager(Context context, Listener listener) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000099 mContext = context;
Wonsik Kim187423c2014-06-25 14:12:48 +0900100 mListener = listener;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900101 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000102 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +0900103 }
104
105 public void onBootPhase(int phase) {
106 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900107 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService(
108 Context.HDMI_CONTROL_SERVICE));
109 if (mHdmiControlService != null) {
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900110 try {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900111 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
112 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
113 mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900114 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900115 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900116 }
117 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900118 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000119 }
120
121 @Override
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900122 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000123 synchronized (mLock) {
124 Connection connection = new Connection(info);
125 connection.updateConfigsLocked(configs);
126 mConnections.put(info.getDeviceId(), connection);
127 buildInfoListLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900128 mHandler.obtainMessage(
129 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000130 }
131 }
132
133 private void buildInfoListLocked() {
134 mInfoList.clear();
135 for (int i = 0; i < mConnections.size(); ++i) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900136 mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000137 }
138 }
139
140 @Override
141 public void onDeviceUnavailable(int deviceId) {
142 synchronized (mLock) {
143 Connection connection = mConnections.get(deviceId);
144 if (connection == null) {
145 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
146 return;
147 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900148 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000149 mConnections.remove(deviceId);
150 buildInfoListLocked();
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900151 TvInputHardwareInfo info = connection.getHardwareInfoLocked();
Wonsik Kim187423c2014-06-25 14:12:48 +0900152 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900153 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000154 }
155 }
156
157 @Override
158 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
159 synchronized (mLock) {
160 Connection connection = mConnections.get(deviceId);
161 if (connection == null) {
162 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
163 + deviceId);
164 return;
165 }
166 connection.updateConfigsLocked(configs);
167 try {
168 connection.getCallbackLocked().onStreamConfigChanged(configs);
169 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900170 Slog.e(TAG, "error in onStreamConfigurationChanged", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000171 }
172 }
173 }
174
Terry Heoc086a3d2014-06-18 14:26:44 +0900175 @Override
176 public void onFirstFrameCaptured(int deviceId, int streamId) {
177 synchronized (mLock) {
178 Connection connection = mConnections.get(deviceId);
179 if (connection == null) {
180 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
181 + deviceId);
182 return;
183 }
184 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
185 if (runnable != null) {
186 runnable.run();
187 connection.setOnFirstFrameCapturedLocked(null);
188 }
189 }
190 }
191
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000192 public List<TvInputHardwareInfo> getHardwareList() {
193 synchronized (mLock) {
194 return mInfoList;
195 }
196 }
197
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900198 public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() {
199 if (mHdmiControlService != null) {
200 try {
201 return mHdmiControlService.getInputDevices();
202 } catch (RemoteException e) {
203 Slog.e(TAG, "error in getHdmiCecInputDeviceList", e);
204 }
205 }
206 return Collections.emptyList();
207 }
208
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900209 private boolean checkUidChangedLocked(
210 Connection connection, int callingUid, int resolvedUserId) {
211 Integer connectionCallingUid = connection.getCallingUidLocked();
212 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
213 if (connectionCallingUid == null || connectionResolvedUserId == null) {
214 return true;
215 }
216 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
217 return true;
218 }
219 return false;
220 }
221
Wonsik Kim969167d2014-06-24 16:33:17 +0900222 private int convertConnectedToState(boolean connected) {
223 if (connected) {
224 return INPUT_STATE_CONNECTED;
225 } else {
226 return INPUT_STATE_DISCONNECTED;
227 }
228 }
229
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900230 public void addHardwareTvInput(int deviceId, TvInputInfo info) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900231 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900232 String oldInputId = mHardwareInputIdMap.get(deviceId);
233 if (oldInputId != null) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900234 Slog.w(TAG, "Trying to override previous registration: old = "
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900235 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
Wonsik Kim969167d2014-06-24 16:33:17 +0900236 + info + ":" + deviceId);
237 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900238 mHardwareInputIdMap.put(deviceId, info.getId());
239 mInputMap.put(info.getId(), info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900240
241 for (int i = 0; i < mHdmiStateMap.size(); ++i) {
242 String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
243 if (inputId != null && inputId.equals(info.getId())) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900244 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim969167d2014-06-24 16:33:17 +0900245 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
246 inputId).sendToTarget();
247 }
248 }
249 }
250 }
251
Wonsik Kim38feae92014-07-21 21:35:50 +0900252 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
253 for (int i = 0; i < map.size(); ++i) {
254 if (map.valueAt(i).equals(value)) {
255 return i;
256 }
257 }
258 return -1;
259 }
260
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900261 public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) {
262 if (info.getType() != TvInputInfo.TYPE_HDMI) {
263 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
264 }
265 synchronized (mLock) {
266 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900267 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
268 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900269 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
270 }
271 String oldInputId = mHdmiCecInputIdMap.get(logicalAddress);
272 if (oldInputId != null) {
273 Slog.w(TAG, "Trying to override previous registration: old = "
274 + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
275 + info + ":" + logicalAddress);
276 }
277 mHdmiCecInputIdMap.put(logicalAddress, info.getId());
278 mInputMap.put(info.getId(), info);
279 }
280 }
281
282 public void removeTvInput(String inputId) {
283 synchronized (mLock) {
284 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900285 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900286 if (hardwareIndex >= 0) {
287 mHardwareInputIdMap.removeAt(hardwareIndex);
288 }
Wonsik Kim38feae92014-07-21 21:35:50 +0900289 int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900290 if (cecIndex >= 0) {
291 mHdmiCecInputIdMap.removeAt(cecIndex);
292 }
293 }
294 }
295
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000296 /**
297 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
298 * the object, and if more than one process attempts to create hardware with the same deviceId,
299 * the latest service will get the object and all the other hardware are released. The
300 * release is notified via ITvInputHardwareCallback.onReleased().
301 */
302 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900303 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000304 if (callback == null) {
305 throw new NullPointerException();
306 }
307 synchronized (mLock) {
308 Connection connection = mConnections.get(deviceId);
309 if (connection == null) {
310 Slog.e(TAG, "Invalid deviceId : " + deviceId);
311 return null;
312 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900313 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900314 TvInputHardwareImpl hardware =
315 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000316 try {
317 callback.asBinder().linkToDeath(connection, 0);
318 } catch (RemoteException e) {
319 hardware.release();
320 return null;
321 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900322 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000323 }
324 return connection.getHardwareLocked();
325 }
326 }
327
328 /**
329 * Release the specified hardware.
330 */
331 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
332 int resolvedUserId) {
333 synchronized (mLock) {
334 Connection connection = mConnections.get(deviceId);
335 if (connection == null) {
336 Slog.e(TAG, "Invalid deviceId : " + deviceId);
337 return;
338 }
339 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900340 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000341 return;
342 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900343 connection.resetLocked(null, null, null, null, null);
344 }
345 }
346
347 private String findInputIdForHdmiPortLocked(int port) {
348 for (TvInputHardwareInfo hardwareInfo : mInfoList) {
349 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
350 && hardwareInfo.getHdmiPortId() == port) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900351 return mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900352 }
353 }
354 return null;
355 }
356
Terry Heoc086a3d2014-06-18 14:26:44 +0900357 private int findDeviceIdForInputIdLocked(String inputId) {
358 for (int i = 0; i < mConnections.size(); ++i) {
359 Connection connection = mConnections.get(i);
360 if (connection.getInfoLocked().getId().equals(inputId)) {
361 return i;
362 }
363 }
364 return -1;
365 }
366
367 /**
368 * Get the list of TvStreamConfig which is buffered mode.
369 */
370 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
371 int resolvedUserId) {
372 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>();
373 synchronized (mLock) {
374 int deviceId = findDeviceIdForInputIdLocked(inputId);
375 if (deviceId < 0) {
376 Slog.e(TAG, "Invalid inputId : " + inputId);
377 return configsList;
378 }
379 Connection connection = mConnections.get(deviceId);
380 for (TvStreamConfig config : connection.getConfigsLocked()) {
381 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
382 configsList.add(config);
383 }
384 }
385 }
386 return configsList;
387 }
388
389 /**
390 * Take a snapshot of the given TV input into the provided Surface.
391 */
392 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
393 int callingUid, int resolvedUserId) {
394 synchronized (mLock) {
395 int deviceId = findDeviceIdForInputIdLocked(inputId);
396 if (deviceId < 0) {
397 Slog.e(TAG, "Invalid inputId : " + inputId);
398 return false;
399 }
400 Connection connection = mConnections.get(deviceId);
401 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
402 if (hardwareImpl != null) {
403 // Stop previous capture.
404 Runnable runnable = connection.getOnFirstFrameCapturedLocked();
405 if (runnable != null) {
406 runnable.run();
407 connection.setOnFirstFrameCapturedLocked(null);
408 }
409
410 boolean result = hardwareImpl.startCapture(surface, config);
411 if (result) {
412 connection.setOnFirstFrameCapturedLocked(new Runnable() {
413 @Override
414 public void run() {
415 hardwareImpl.stopCapture(config);
416 }
417 });
418 }
419 return result;
420 }
421 }
422 return false;
423 }
424
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000425 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900426 private final TvInputHardwareInfo mHardwareInfo;
427 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000428 private TvInputHardwareImpl mHardware = null;
429 private ITvInputHardwareCallback mCallback;
430 private TvStreamConfig[] mConfigs = null;
431 private Integer mCallingUid = null;
432 private Integer mResolvedUserId = null;
Terry Heoc086a3d2014-06-18 14:26:44 +0900433 private Runnable mOnFirstFrameCaptured;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000434
Wonsik Kim969167d2014-06-24 16:33:17 +0900435 public Connection(TvInputHardwareInfo hardwareInfo) {
436 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000437 }
438
439 // *Locked methods assume TvInputHardwareManager.mLock is held.
440
Wonsik Kim969167d2014-06-24 16:33:17 +0900441 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
442 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000443 if (mHardware != null) {
444 try {
445 mCallback.onReleased();
446 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900447 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000448 }
449 mHardware.release();
450 }
451 mHardware = hardware;
452 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900453 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000454 mCallingUid = callingUid;
455 mResolvedUserId = resolvedUserId;
Terry Heoc086a3d2014-06-18 14:26:44 +0900456 mOnFirstFrameCaptured = null;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000457
458 if (mHardware != null && mCallback != null) {
459 try {
460 mCallback.onStreamConfigChanged(getConfigsLocked());
461 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900462 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000463 }
464 }
465 }
466
467 public void updateConfigsLocked(TvStreamConfig[] configs) {
468 mConfigs = configs;
469 }
470
Wonsik Kim969167d2014-06-24 16:33:17 +0900471 public TvInputHardwareInfo getHardwareInfoLocked() {
472 return mHardwareInfo;
473 }
474
475 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000476 return mInfo;
477 }
478
479 public ITvInputHardware getHardwareLocked() {
480 return mHardware;
481 }
482
Terry Heoc086a3d2014-06-18 14:26:44 +0900483 public TvInputHardwareImpl getHardwareImplLocked() {
484 return mHardware;
485 }
486
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000487 public ITvInputHardwareCallback getCallbackLocked() {
488 return mCallback;
489 }
490
491 public TvStreamConfig[] getConfigsLocked() {
492 return mConfigs;
493 }
494
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900495 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000496 return mCallingUid;
497 }
498
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900499 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000500 return mResolvedUserId;
501 }
502
Terry Heoc086a3d2014-06-18 14:26:44 +0900503 public void setOnFirstFrameCapturedLocked(Runnable runnable) {
504 mOnFirstFrameCaptured = runnable;
505 }
506
507 public Runnable getOnFirstFrameCapturedLocked() {
508 return mOnFirstFrameCaptured;
509 }
510
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000511 @Override
512 public void binderDied() {
513 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900514 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000515 }
516 }
517 }
518
519 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
520 private final TvInputHardwareInfo mInfo;
521 private boolean mReleased = false;
522 private final Object mImplLock = new Object();
523
Wonsik Kimd7c29182014-05-27 10:38:21 +0900524 private final AudioDevicePort mAudioSource;
525 private final AudioDevicePort mAudioSink;
526 private AudioPatch mAudioPatch = null;
527
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900528 private TvStreamConfig mActiveConfig = null;
529
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000530 public TvInputHardwareImpl(TvInputHardwareInfo info) {
531 mInfo = info;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900532 AudioDevicePort audioSource = null;
533 AudioDevicePort audioSink = null;
534 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
535 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
536 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
537 // Find source
538 for (AudioPort port : devicePorts) {
539 AudioDevicePort devicePort = (AudioDevicePort) port;
540 if (devicePort.type() == mInfo.getAudioType() &&
541 devicePort.address().equals(mInfo.getAudioAddress())) {
542 audioSource = devicePort;
543 break;
544 }
545 }
546 // Find sink
547 // TODO: App may want to specify sink device?
548 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
549 for (AudioPort port : devicePorts) {
550 AudioDevicePort devicePort = (AudioDevicePort) port;
551 if (devicePort.type() == sinkDevices) {
552 audioSink = devicePort;
553 break;
554 }
555 }
556 }
557 }
558 mAudioSource = audioSource;
559 mAudioSink = audioSink;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000560 }
561
562 public void release() {
563 synchronized (mImplLock) {
Wonsik Kimd7c29182014-05-27 10:38:21 +0900564 if (mAudioPatch != null) {
565 mAudioManager.releaseAudioPatch(mAudioPatch);
566 mAudioPatch = null;
567 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000568 mReleased = true;
569 }
570 }
571
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900572 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
573 // attempts to call setSurface with different TvStreamConfig objects, the last call will
574 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000575 @Override
576 public boolean setSurface(Surface surface, TvStreamConfig config)
577 throws RemoteException {
578 synchronized (mImplLock) {
579 if (mReleased) {
580 throw new IllegalStateException("Device already released.");
581 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900582 if (surface != null && config == null) {
583 return false;
584 }
585 if (surface == null && mActiveConfig == null) {
586 return false;
587 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900588 if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000589 if (surface != null) {
590 // Set "Active Source" for HDMI.
591 // TODO(hdmi): mHdmiClient.deviceSelect(...);
592 mActiveHdmiSources.add(mInfo.getDeviceId());
593 } else {
594 mActiveHdmiSources.remove(mInfo.getDeviceId());
595 if (mActiveHdmiSources.size() == 0) {
596 // Tell HDMI that no HDMI source is active
597 // TODO(hdmi): mHdmiClient.portSelect(null);
598 }
599 }
600 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900601 if (mAudioSource != null && mAudioSink != null) {
602 if (surface != null) {
603 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
604 AudioPortConfig sinkConfig = mAudioSink.activeConfig();
605 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
606 // TODO: build config if activeConfig() == null
607 mAudioManager.createAudioPatch(
608 audioPatchArray,
609 new AudioPortConfig[] { sourceConfig },
610 new AudioPortConfig[] { sinkConfig });
611 mAudioPatch = audioPatchArray[0];
612 } else {
613 mAudioManager.releaseAudioPatch(mAudioPatch);
614 mAudioPatch = null;
615 }
616 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900617 int result = TvInputHal.ERROR_UNKNOWN;
618 if (surface == null) {
619 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
620 mActiveConfig = null;
621 } else {
622 if (config != mActiveConfig && mActiveConfig != null) {
623 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
624 if (result != TvInputHal.SUCCESS) {
625 mActiveConfig = null;
626 return false;
627 }
628 }
629 result = mHal.addStream(mInfo.getDeviceId(), surface, config);
630 if (result == TvInputHal.SUCCESS) {
631 mActiveConfig = config;
632 }
633 }
634 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000635 }
636 }
637
638 @Override
639 public void setVolume(float volume) throws RemoteException {
640 synchronized (mImplLock) {
641 if (mReleased) {
642 throw new IllegalStateException("Device already released.");
643 }
644 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900645 // TODO: Use AudioGain?
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000646 }
647
648 @Override
649 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
650 synchronized (mImplLock) {
651 if (mReleased) {
652 throw new IllegalStateException("Device already released.");
653 }
654 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900655 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000656 return false;
657 }
658 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
659 return false;
660 }
Terry Heoc086a3d2014-06-18 14:26:44 +0900661
662 private boolean startCapture(Surface surface, TvStreamConfig config) {
663 synchronized (mImplLock) {
664 if (mReleased) {
665 return false;
666 }
667 if (surface == null || config == null) {
668 return false;
669 }
670 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
671 return false;
672 }
673
674 int result = mHal.addStream(mInfo.getDeviceId(), surface, config);
675 return result == TvInputHal.SUCCESS;
676 }
677 }
678
679 private boolean stopCapture(TvStreamConfig config) {
680 synchronized (mImplLock) {
681 if (mReleased) {
682 return false;
683 }
684 if (config == null) {
685 return false;
686 }
687
688 int result = mHal.removeStream(mInfo.getDeviceId(), config);
689 return result == TvInputHal.SUCCESS;
690 }
691 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000692 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900693
Wonsik Kim187423c2014-06-25 14:12:48 +0900694 interface Listener {
695 public void onStateChanged(String inputId, int state);
696 public void onHardwareDeviceAdded(TvInputHardwareInfo info);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900697 public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
Wonsik Kim187423c2014-06-25 14:12:48 +0900698 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
699 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
700 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900701
Wonsik Kim187423c2014-06-25 14:12:48 +0900702 private class ListenerHandler extends Handler {
703 private static final int STATE_CHANGED = 1;
704 private static final int HARDWARE_DEVICE_ADDED = 2;
705 private static final int HARDWARE_DEVICE_REMOVED = 3;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900706 private static final int HDMI_CEC_DEVICE_ADDED = 4;
707 private static final int HDMI_CEC_DEVICE_REMOVED = 5;
Wonsik Kim969167d2014-06-24 16:33:17 +0900708
709 @Override
710 public final void handleMessage(Message msg) {
711 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900712 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +0900713 String inputId = (String) msg.obj;
714 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +0900715 mListener.onStateChanged(inputId, state);
716 break;
717 }
718 case HARDWARE_DEVICE_ADDED: {
719 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
720 mListener.onHardwareDeviceAdded(info);
721 break;
722 }
723 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900724 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
725 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +0900726 break;
727 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900728 case HDMI_CEC_DEVICE_ADDED: {
Wonsik Kim187423c2014-06-25 14:12:48 +0900729 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
730 mListener.onHdmiCecDeviceAdded(info);
731 break;
732 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900733 case HDMI_CEC_DEVICE_REMOVED: {
Wonsik Kim187423c2014-06-25 14:12:48 +0900734 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
735 mListener.onHdmiCecDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900736 break;
737 }
738 default: {
739 Slog.w(TAG, "Unhandled message: " + msg);
740 break;
741 }
742 }
743 }
744 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900745
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900746 // Listener implementations for HdmiControlService
747
748 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
749 @Override
750 public void onReceived(HdmiHotplugEvent event) {
751 synchronized (mLock) {
752 mHdmiStateMap.put(event.getPort(), event.isConnected());
753 String inputId = findInputIdForHdmiPortLocked(event.getPort());
754 if (inputId == null) {
755 return;
756 }
757 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
758 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
759 }
760 }
761 }
762
Wonsik Kim187423c2014-06-25 14:12:48 +0900763 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
764 @Override
765 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
766 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900767 activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED
768 : ListenerHandler.HDMI_CEC_DEVICE_REMOVED,
Wonsik Kim187423c2014-06-25 14:12:48 +0900769 0, 0, deviceInfo).sendToTarget();
770 }
771 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900772
773 private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub {
774 @Override
775 public void onChanged(HdmiCecDeviceInfo device) throws RemoteException {
Jinsuk Kim469085e2014-07-21 23:52:39 +0000776 // TODO: Build a channel Uri for the TvInputInfo associated with the logical device
777 // and send an intent to TV app
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900778 }
779 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000780}