blob: b95b0f068d9ee6f56889cb904d3a175ff7f2a8f1 [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
19import android.content.Context;
20import android.os.IBinder;
21import android.os.RemoteException;
22import android.tv.ITvInputHardware;
23import android.tv.ITvInputHardwareCallback;
24import android.tv.TvInputHardwareInfo;
25import android.tv.TvStreamConfig;
26import android.util.Slog;
27import android.util.SparseArray;
28import android.view.KeyEvent;
29import android.view.Surface;
30
31import java.util.ArrayList;
32import java.util.HashSet;
33import java.util.List;
34import java.util.Set;
35
36/**
37 * A helper class for TvInputManagerService to handle TV input hardware.
38 *
39 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
40 * calls to tv_input HAL module.
41 *
42 * @hide
43 */
44class TvInputHardwareManager implements TvInputHal.Callback {
45 private static final String TAG = TvInputHardwareManager.class.getSimpleName();
46 private final TvInputHal mHal = new TvInputHal(this);
47 private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
48 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
49 private final Context mContext;
50 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
51
52 private final Object mLock = new Object();
53
54 public TvInputHardwareManager(Context context) {
55 mContext = context;
56 // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
57 // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
58 mHal.init();
59 }
60
61 @Override
62 public void onDeviceAvailable(
63 TvInputHardwareInfo info, TvStreamConfig[] configs) {
64 synchronized (mLock) {
65 Connection connection = new Connection(info);
66 connection.updateConfigsLocked(configs);
67 mConnections.put(info.getDeviceId(), connection);
68 buildInfoListLocked();
69 // TODO: notify if necessary
70 }
71 }
72
73 private void buildInfoListLocked() {
74 mInfoList.clear();
75 for (int i = 0; i < mConnections.size(); ++i) {
76 mInfoList.add(mConnections.valueAt(i).getInfoLocked());
77 }
78 }
79
80 @Override
81 public void onDeviceUnavailable(int deviceId) {
82 synchronized (mLock) {
83 Connection connection = mConnections.get(deviceId);
84 if (connection == null) {
85 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
86 return;
87 }
88 connection.resetLocked(null, null, null, null);
89 mConnections.remove(deviceId);
90 buildInfoListLocked();
91 // TODO: notify if necessary
92 }
93 }
94
95 @Override
96 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
97 synchronized (mLock) {
98 Connection connection = mConnections.get(deviceId);
99 if (connection == null) {
100 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
101 + deviceId);
102 return;
103 }
104 connection.updateConfigsLocked(configs);
105 try {
106 connection.getCallbackLocked().onStreamConfigChanged(configs);
107 } catch (RemoteException e) {
108 Slog.e(TAG, "onStreamConfigurationChanged: " + e);
109 }
110 }
111 }
112
113 public List<TvInputHardwareInfo> getHardwareList() {
114 synchronized (mLock) {
115 return mInfoList;
116 }
117 }
118
119 /**
120 * Create a TvInputHardware object with a specific deviceId. One service at a time can access
121 * the object, and if more than one process attempts to create hardware with the same deviceId,
122 * the latest service will get the object and all the other hardware are released. The
123 * release is notified via ITvInputHardwareCallback.onReleased().
124 */
125 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
126 int callingUid, int resolvedUserId) {
127 if (callback == null) {
128 throw new NullPointerException();
129 }
130 synchronized (mLock) {
131 Connection connection = mConnections.get(deviceId);
132 if (connection == null) {
133 Slog.e(TAG, "Invalid deviceId : " + deviceId);
134 return null;
135 }
136 if (connection.getCallingUidLocked() != callingUid
137 || connection.getResolvedUserIdLocked() != resolvedUserId) {
138 TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
139 try {
140 callback.asBinder().linkToDeath(connection, 0);
141 } catch (RemoteException e) {
142 hardware.release();
143 return null;
144 }
145 connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
146 }
147 return connection.getHardwareLocked();
148 }
149 }
150
151 /**
152 * Release the specified hardware.
153 */
154 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
155 int resolvedUserId) {
156 synchronized (mLock) {
157 Connection connection = mConnections.get(deviceId);
158 if (connection == null) {
159 Slog.e(TAG, "Invalid deviceId : " + deviceId);
160 return;
161 }
162 if (connection.getHardwareLocked() != hardware
163 || connection.getCallingUidLocked() != callingUid
164 || connection.getResolvedUserIdLocked() != resolvedUserId) {
165 return;
166 }
167 connection.resetLocked(null, null, null, null);
168 }
169 }
170
171 private class Connection implements IBinder.DeathRecipient {
172 private final TvInputHardwareInfo mInfo;
173 private TvInputHardwareImpl mHardware = null;
174 private ITvInputHardwareCallback mCallback;
175 private TvStreamConfig[] mConfigs = null;
176 private Integer mCallingUid = null;
177 private Integer mResolvedUserId = null;
178
179 public Connection(TvInputHardwareInfo info) {
180 mInfo = info;
181 }
182
183 // *Locked methods assume TvInputHardwareManager.mLock is held.
184
185 public void resetLocked(TvInputHardwareImpl hardware,
186 ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
187 if (mHardware != null) {
188 try {
189 mCallback.onReleased();
190 } catch (RemoteException e) {
191 Slog.e(TAG, "Connection::resetHardware: " + e);
192 }
193 mHardware.release();
194 }
195 mHardware = hardware;
196 mCallback = callback;
197 mCallingUid = callingUid;
198 mResolvedUserId = resolvedUserId;
199
200 if (mHardware != null && mCallback != null) {
201 try {
202 mCallback.onStreamConfigChanged(getConfigsLocked());
203 } catch (RemoteException e) {
204 Slog.e(TAG, "Connection::resetHardware: " + e);
205 }
206 }
207 }
208
209 public void updateConfigsLocked(TvStreamConfig[] configs) {
210 mConfigs = configs;
211 }
212
213 public TvInputHardwareInfo getInfoLocked() {
214 return mInfo;
215 }
216
217 public ITvInputHardware getHardwareLocked() {
218 return mHardware;
219 }
220
221 public ITvInputHardwareCallback getCallbackLocked() {
222 return mCallback;
223 }
224
225 public TvStreamConfig[] getConfigsLocked() {
226 return mConfigs;
227 }
228
229 public int getCallingUidLocked() {
230 return mCallingUid;
231 }
232
233 public int getResolvedUserIdLocked() {
234 return mResolvedUserId;
235 }
236
237 @Override
238 public void binderDied() {
239 synchronized (mLock) {
240 resetLocked(null, null, null, null);
241 }
242 }
243 }
244
245 private class TvInputHardwareImpl extends ITvInputHardware.Stub {
246 private final TvInputHardwareInfo mInfo;
247 private boolean mReleased = false;
248 private final Object mImplLock = new Object();
249
250 public TvInputHardwareImpl(TvInputHardwareInfo info) {
251 mInfo = info;
252 }
253
254 public void release() {
255 synchronized (mImplLock) {
256 mReleased = true;
257 }
258 }
259
260 @Override
261 public boolean setSurface(Surface surface, TvStreamConfig config)
262 throws RemoteException {
263 synchronized (mImplLock) {
264 if (mReleased) {
265 throw new IllegalStateException("Device already released.");
266 }
267 if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
268 if (surface != null) {
269 // Set "Active Source" for HDMI.
270 // TODO(hdmi): mHdmiClient.deviceSelect(...);
271 mActiveHdmiSources.add(mInfo.getDeviceId());
272 } else {
273 mActiveHdmiSources.remove(mInfo.getDeviceId());
274 if (mActiveHdmiSources.size() == 0) {
275 // Tell HDMI that no HDMI source is active
276 // TODO(hdmi): mHdmiClient.portSelect(null);
277 }
278 }
279 }
280 return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
281 }
282 }
283
284 @Override
285 public void setVolume(float volume) throws RemoteException {
286 synchronized (mImplLock) {
287 if (mReleased) {
288 throw new IllegalStateException("Device already released.");
289 }
290 }
291 // TODO
292 }
293
294 @Override
295 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
296 synchronized (mImplLock) {
297 if (mReleased) {
298 throw new IllegalStateException("Device already released.");
299 }
300 }
301 if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
302 return false;
303 }
304 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
305 return false;
306 }
307 }
308}