blob: 885486948c2f83207ec9c9deefb59bb58183178a [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
175 public List<TvInputHardwareInfo> getHardwareList() {
176 synchronized (mLock) {
177 return mInfoList;
178 }
179 }
180
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900181 public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() {
182 if (mHdmiControlService != null) {
183 try {
184 return mHdmiControlService.getInputDevices();
185 } catch (RemoteException e) {
186 Slog.e(TAG, "error in getHdmiCecInputDeviceList", e);
187 }
188 }
189 return Collections.emptyList();
190 }
191
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900192 private boolean checkUidChangedLocked(
193 Connection connection, int callingUid, int resolvedUserId) {
194 Integer connectionCallingUid = connection.getCallingUidLocked();
195 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
196 if (connectionCallingUid == null || connectionResolvedUserId == null) {
197 return true;
198 }
199 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
200 return true;
201 }
202 return false;
203 }
204
Wonsik Kim969167d2014-06-24 16:33:17 +0900205 private int convertConnectedToState(boolean connected) {
206 if (connected) {
207 return INPUT_STATE_CONNECTED;
208 } else {
209 return INPUT_STATE_DISCONNECTED;
210 }
211 }
212
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900213 public void addHardwareTvInput(int deviceId, TvInputInfo info) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900214 if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
215 throw new IllegalArgumentException("info (" + info + ") has virtual type.");
216 }
217 synchronized (mLock) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900218 String oldInputId = mHardwareInputIdMap.get(deviceId);
219 if (oldInputId != null) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900220 Slog.w(TAG, "Trying to override previous registration: old = "
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900221 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
Wonsik Kim969167d2014-06-24 16:33:17 +0900222 + info + ":" + deviceId);
223 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900224 mHardwareInputIdMap.put(deviceId, info.getId());
225 mInputMap.put(info.getId(), info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900226
227 for (int i = 0; i < mHdmiStateMap.size(); ++i) {
228 String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
229 if (inputId != null && inputId.equals(info.getId())) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900230 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
Wonsik Kim969167d2014-06-24 16:33:17 +0900231 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
232 inputId).sendToTarget();
233 }
234 }
235 }
236 }
237
Wonsik Kim38feae92014-07-21 21:35:50 +0900238 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
239 for (int i = 0; i < map.size(); ++i) {
240 if (map.valueAt(i).equals(value)) {
241 return i;
242 }
243 }
244 return -1;
245 }
246
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900247 public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) {
248 if (info.getType() != TvInputInfo.TYPE_HDMI) {
249 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
250 }
251 synchronized (mLock) {
252 String parentId = info.getParentId();
Wonsik Kim38feae92014-07-21 21:35:50 +0900253 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
254 if (parentIndex < 0) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900255 throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
256 }
257 String oldInputId = mHdmiCecInputIdMap.get(logicalAddress);
258 if (oldInputId != null) {
259 Slog.w(TAG, "Trying to override previous registration: old = "
260 + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
261 + info + ":" + logicalAddress);
262 }
263 mHdmiCecInputIdMap.put(logicalAddress, info.getId());
264 mInputMap.put(info.getId(), info);
265 }
266 }
267
268 public void removeTvInput(String inputId) {
269 synchronized (mLock) {
270 mInputMap.remove(inputId);
Wonsik Kim38feae92014-07-21 21:35:50 +0900271 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900272 if (hardwareIndex >= 0) {
273 mHardwareInputIdMap.removeAt(hardwareIndex);
274 }
Wonsik Kim38feae92014-07-21 21:35:50 +0900275 int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900276 if (cecIndex >= 0) {
277 mHdmiCecInputIdMap.removeAt(cecIndex);
278 }
279 }
280 }
281
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000282 /**
283 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
284 * the object, and if more than one process attempts to create hardware with the same deviceId,
285 * the latest service will get the object and all the other hardware are released. The
286 * release is notified via ITvInputHardwareCallback.onReleased().
287 */
288 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
Wonsik Kim969167d2014-06-24 16:33:17 +0900289 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000290 if (callback == null) {
291 throw new NullPointerException();
292 }
293 synchronized (mLock) {
294 Connection connection = mConnections.get(deviceId);
295 if (connection == null) {
296 Slog.e(TAG, "Invalid deviceId : " + deviceId);
297 return null;
298 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900299 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900300 TvInputHardwareImpl hardware =
301 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000302 try {
303 callback.asBinder().linkToDeath(connection, 0);
304 } catch (RemoteException e) {
305 hardware.release();
306 return null;
307 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900308 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000309 }
310 return connection.getHardwareLocked();
311 }
312 }
313
314 /**
315 * Release the specified hardware.
316 */
317 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
318 int resolvedUserId) {
319 synchronized (mLock) {
320 Connection connection = mConnections.get(deviceId);
321 if (connection == null) {
322 Slog.e(TAG, "Invalid deviceId : " + deviceId);
323 return;
324 }
325 if (connection.getHardwareLocked() != hardware
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900326 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000327 return;
328 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900329 connection.resetLocked(null, null, null, null, null);
330 }
331 }
332
333 private String findInputIdForHdmiPortLocked(int port) {
334 for (TvInputHardwareInfo hardwareInfo : mInfoList) {
335 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
336 && hardwareInfo.getHdmiPortId() == port) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900337 return mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
Wonsik Kim969167d2014-06-24 16:33:17 +0900338 }
339 }
340 return null;
341 }
342
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000343 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900344 private final TvInputHardwareInfo mHardwareInfo;
345 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000346 private TvInputHardwareImpl mHardware = null;
347 private ITvInputHardwareCallback mCallback;
348 private TvStreamConfig[] mConfigs = null;
349 private Integer mCallingUid = null;
350 private Integer mResolvedUserId = null;
351
Wonsik Kim969167d2014-06-24 16:33:17 +0900352 public Connection(TvInputHardwareInfo hardwareInfo) {
353 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000354 }
355
356 // *Locked methods assume TvInputHardwareManager.mLock is held.
357
Wonsik Kim969167d2014-06-24 16:33:17 +0900358 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
359 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000360 if (mHardware != null) {
361 try {
362 mCallback.onReleased();
363 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900364 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000365 }
366 mHardware.release();
367 }
368 mHardware = hardware;
369 mCallback = callback;
Wonsik Kim969167d2014-06-24 16:33:17 +0900370 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000371 mCallingUid = callingUid;
372 mResolvedUserId = resolvedUserId;
373
374 if (mHardware != null && mCallback != null) {
375 try {
376 mCallback.onStreamConfigChanged(getConfigsLocked());
377 } catch (RemoteException e) {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900378 Slog.e(TAG, "error in Connection::resetLocked", e);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000379 }
380 }
381 }
382
383 public void updateConfigsLocked(TvStreamConfig[] configs) {
384 mConfigs = configs;
385 }
386
Wonsik Kim969167d2014-06-24 16:33:17 +0900387 public TvInputHardwareInfo getHardwareInfoLocked() {
388 return mHardwareInfo;
389 }
390
391 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000392 return mInfo;
393 }
394
395 public ITvInputHardware getHardwareLocked() {
396 return mHardware;
397 }
398
399 public ITvInputHardwareCallback getCallbackLocked() {
400 return mCallback;
401 }
402
403 public TvStreamConfig[] getConfigsLocked() {
404 return mConfigs;
405 }
406
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900407 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000408 return mCallingUid;
409 }
410
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900411 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000412 return mResolvedUserId;
413 }
414
415 @Override
416 public void binderDied() {
417 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900418 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000419 }
420 }
421 }
422
423 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
424 private final TvInputHardwareInfo mInfo;
425 private boolean mReleased = false;
426 private final Object mImplLock = new Object();
427
Wonsik Kimd7c29182014-05-27 10:38:21 +0900428 private final AudioDevicePort mAudioSource;
429 private final AudioDevicePort mAudioSink;
430 private AudioPatch mAudioPatch = null;
431
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900432 private TvStreamConfig mActiveConfig = null;
433
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000434 public TvInputHardwareImpl(TvInputHardwareInfo info) {
435 mInfo = info;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900436 AudioDevicePort audioSource = null;
437 AudioDevicePort audioSink = null;
438 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
439 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
440 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
441 // Find source
442 for (AudioPort port : devicePorts) {
443 AudioDevicePort devicePort = (AudioDevicePort) port;
444 if (devicePort.type() == mInfo.getAudioType() &&
445 devicePort.address().equals(mInfo.getAudioAddress())) {
446 audioSource = devicePort;
447 break;
448 }
449 }
450 // Find sink
451 // TODO: App may want to specify sink device?
452 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
453 for (AudioPort port : devicePorts) {
454 AudioDevicePort devicePort = (AudioDevicePort) port;
455 if (devicePort.type() == sinkDevices) {
456 audioSink = devicePort;
457 break;
458 }
459 }
460 }
461 }
462 mAudioSource = audioSource;
463 mAudioSink = audioSink;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000464 }
465
466 public void release() {
467 synchronized (mImplLock) {
Wonsik Kimd7c29182014-05-27 10:38:21 +0900468 if (mAudioPatch != null) {
469 mAudioManager.releaseAudioPatch(mAudioPatch);
470 mAudioPatch = null;
471 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000472 mReleased = true;
473 }
474 }
475
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900476 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
477 // attempts to call setSurface with different TvStreamConfig objects, the last call will
478 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000479 @Override
480 public boolean setSurface(Surface surface, TvStreamConfig config)
481 throws RemoteException {
482 synchronized (mImplLock) {
483 if (mReleased) {
484 throw new IllegalStateException("Device already released.");
485 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900486 if (surface != null && config == null) {
487 return false;
488 }
489 if (surface == null && mActiveConfig == null) {
490 return false;
491 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900492 if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000493 if (surface != null) {
494 // Set "Active Source" for HDMI.
495 // TODO(hdmi): mHdmiClient.deviceSelect(...);
496 mActiveHdmiSources.add(mInfo.getDeviceId());
497 } else {
498 mActiveHdmiSources.remove(mInfo.getDeviceId());
499 if (mActiveHdmiSources.size() == 0) {
500 // Tell HDMI that no HDMI source is active
501 // TODO(hdmi): mHdmiClient.portSelect(null);
502 }
503 }
504 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900505 if (mAudioSource != null && mAudioSink != null) {
506 if (surface != null) {
507 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
508 AudioPortConfig sinkConfig = mAudioSink.activeConfig();
509 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
510 // TODO: build config if activeConfig() == null
511 mAudioManager.createAudioPatch(
512 audioPatchArray,
513 new AudioPortConfig[] { sourceConfig },
514 new AudioPortConfig[] { sinkConfig });
515 mAudioPatch = audioPatchArray[0];
516 } else {
517 mAudioManager.releaseAudioPatch(mAudioPatch);
518 mAudioPatch = null;
519 }
520 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900521 int result = TvInputHal.ERROR_UNKNOWN;
522 if (surface == null) {
523 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
524 mActiveConfig = null;
525 } else {
526 if (config != mActiveConfig && mActiveConfig != null) {
527 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
528 if (result != TvInputHal.SUCCESS) {
529 mActiveConfig = null;
530 return false;
531 }
532 }
533 result = mHal.addStream(mInfo.getDeviceId(), surface, config);
534 if (result == TvInputHal.SUCCESS) {
535 mActiveConfig = config;
536 }
537 }
538 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000539 }
540 }
541
542 @Override
543 public void setVolume(float volume) throws RemoteException {
544 synchronized (mImplLock) {
545 if (mReleased) {
546 throw new IllegalStateException("Device already released.");
547 }
548 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900549 // TODO: Use AudioGain?
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000550 }
551
552 @Override
553 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
554 synchronized (mImplLock) {
555 if (mReleased) {
556 throw new IllegalStateException("Device already released.");
557 }
558 }
Wonsik Kim610ccd92014-07-19 18:49:49 +0900559 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000560 return false;
561 }
562 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
563 return false;
564 }
565 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900566
Wonsik Kim187423c2014-06-25 14:12:48 +0900567 interface Listener {
568 public void onStateChanged(String inputId, int state);
569 public void onHardwareDeviceAdded(TvInputHardwareInfo info);
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900570 public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
Wonsik Kim187423c2014-06-25 14:12:48 +0900571 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
572 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
573 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900574
Wonsik Kim187423c2014-06-25 14:12:48 +0900575 private class ListenerHandler extends Handler {
576 private static final int STATE_CHANGED = 1;
577 private static final int HARDWARE_DEVICE_ADDED = 2;
578 private static final int HARDWARE_DEVICE_REMOVED = 3;
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900579 private static final int HDMI_CEC_DEVICE_ADDED = 4;
580 private static final int HDMI_CEC_DEVICE_REMOVED = 5;
Wonsik Kim969167d2014-06-24 16:33:17 +0900581
582 @Override
583 public final void handleMessage(Message msg) {
584 switch (msg.what) {
Wonsik Kim187423c2014-06-25 14:12:48 +0900585 case STATE_CHANGED: {
Wonsik Kim969167d2014-06-24 16:33:17 +0900586 String inputId = (String) msg.obj;
587 int state = msg.arg1;
Wonsik Kim187423c2014-06-25 14:12:48 +0900588 mListener.onStateChanged(inputId, state);
589 break;
590 }
591 case HARDWARE_DEVICE_ADDED: {
592 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
593 mListener.onHardwareDeviceAdded(info);
594 break;
595 }
596 case HARDWARE_DEVICE_REMOVED: {
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900597 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
598 mListener.onHardwareDeviceRemoved(info);
Wonsik Kim187423c2014-06-25 14:12:48 +0900599 break;
600 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900601 case HDMI_CEC_DEVICE_ADDED: {
Wonsik Kim187423c2014-06-25 14:12:48 +0900602 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
603 mListener.onHdmiCecDeviceAdded(info);
604 break;
605 }
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900606 case HDMI_CEC_DEVICE_REMOVED: {
Wonsik Kim187423c2014-06-25 14:12:48 +0900607 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
608 mListener.onHdmiCecDeviceRemoved(info);
Wonsik Kim969167d2014-06-24 16:33:17 +0900609 break;
610 }
611 default: {
612 Slog.w(TAG, "Unhandled message: " + msg);
613 break;
614 }
615 }
616 }
617 }
Wonsik Kim187423c2014-06-25 14:12:48 +0900618
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900619 // Listener implementations for HdmiControlService
620
621 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
622 @Override
623 public void onReceived(HdmiHotplugEvent event) {
624 synchronized (mLock) {
625 mHdmiStateMap.put(event.getPort(), event.isConnected());
626 String inputId = findInputIdForHdmiPortLocked(event.getPort());
627 if (inputId == null) {
628 return;
629 }
630 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
631 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
632 }
633 }
634 }
635
Wonsik Kim187423c2014-06-25 14:12:48 +0900636 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
637 @Override
638 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
639 mHandler.obtainMessage(
Ji-Hwan Lee4f9f57c2014-07-19 22:20:31 +0900640 activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED
641 : ListenerHandler.HDMI_CEC_DEVICE_REMOVED,
Wonsik Kim187423c2014-06-25 14:12:48 +0900642 0, 0, deviceInfo).sendToTarget();
643 }
644 }
Jinsuk Kim7474fac2014-07-18 17:22:26 +0900645
646 private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub {
647 @Override
648 public void onChanged(HdmiCecDeviceInfo device) throws RemoteException {
649 // TODO: Build a channel Uri for the TvInputInfo associated with the logical device
650 // and send an intent to TV app
651 }
652 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000653}