TvInputHardwareManager.java revision c22dbb69194c8e8fe2a32326d1f37a738cad0904
1/*
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}
309