TvInputHardwareManager.java revision e7ae0ce53b6e1ddee3e456d2a69eebcd5a196b1f
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.media.tv.ITvInputHardware;
21import android.media.tv.ITvInputHardwareCallback;
22import android.media.tv.TvInputHardwareInfo;
23import android.media.tv.TvStreamConfig;
24import android.os.IBinder;
25import android.os.RemoteException;
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    private boolean checkUidChangedLocked(
120            Connection connection, int callingUid, int resolvedUserId) {
121        Integer connectionCallingUid = connection.getCallingUidLocked();
122        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
123        if (connectionCallingUid == null || connectionResolvedUserId == null) {
124            return true;
125        }
126        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
127            return true;
128        }
129        return false;
130    }
131
132    /**
133     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
134     * the object, and if more than one process attempts to create hardware with the same deviceId,
135     * the latest service will get the object and all the other hardware are released. The
136     * release is notified via ITvInputHardwareCallback.onReleased().
137     */
138    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
139            int callingUid, int resolvedUserId) {
140        if (callback == null) {
141            throw new NullPointerException();
142        }
143        synchronized (mLock) {
144            Connection connection = mConnections.get(deviceId);
145            if (connection == null) {
146                Slog.e(TAG, "Invalid deviceId : " + deviceId);
147                return null;
148            }
149            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
150                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
151                try {
152                    callback.asBinder().linkToDeath(connection, 0);
153                } catch (RemoteException e) {
154                    hardware.release();
155                    return null;
156                }
157                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
158            }
159            return connection.getHardwareLocked();
160        }
161    }
162
163    /**
164     * Release the specified hardware.
165     */
166    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
167            int resolvedUserId) {
168        synchronized (mLock) {
169            Connection connection = mConnections.get(deviceId);
170            if (connection == null) {
171                Slog.e(TAG, "Invalid deviceId : " + deviceId);
172                return;
173            }
174            if (connection.getHardwareLocked() != hardware
175                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
176                return;
177            }
178            connection.resetLocked(null, null, null, null);
179        }
180    }
181
182    private class Connection implements IBinder.DeathRecipient {
183        private final TvInputHardwareInfo mInfo;
184        private TvInputHardwareImpl mHardware = null;
185        private ITvInputHardwareCallback mCallback;
186        private TvStreamConfig[] mConfigs = null;
187        private Integer mCallingUid = null;
188        private Integer mResolvedUserId = null;
189
190        public Connection(TvInputHardwareInfo info) {
191            mInfo = info;
192        }
193
194        // *Locked methods assume TvInputHardwareManager.mLock is held.
195
196        public void resetLocked(TvInputHardwareImpl hardware,
197                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
198            if (mHardware != null) {
199                try {
200                    mCallback.onReleased();
201                } catch (RemoteException e) {
202                    Slog.e(TAG, "Connection::resetHardware: " + e);
203                }
204                mHardware.release();
205            }
206            mHardware = hardware;
207            mCallback = callback;
208            mCallingUid = callingUid;
209            mResolvedUserId = resolvedUserId;
210
211            if (mHardware != null && mCallback != null) {
212                try {
213                    mCallback.onStreamConfigChanged(getConfigsLocked());
214                } catch (RemoteException e) {
215                    Slog.e(TAG, "Connection::resetHardware: " + e);
216                }
217            }
218        }
219
220        public void updateConfigsLocked(TvStreamConfig[] configs) {
221            mConfigs = configs;
222        }
223
224        public TvInputHardwareInfo getInfoLocked() {
225            return mInfo;
226        }
227
228        public ITvInputHardware getHardwareLocked() {
229            return mHardware;
230        }
231
232        public ITvInputHardwareCallback getCallbackLocked() {
233            return mCallback;
234        }
235
236        public TvStreamConfig[] getConfigsLocked() {
237            return mConfigs;
238        }
239
240        public Integer getCallingUidLocked() {
241            return mCallingUid;
242        }
243
244        public Integer getResolvedUserIdLocked() {
245            return mResolvedUserId;
246        }
247
248        @Override
249        public void binderDied() {
250            synchronized (mLock) {
251                resetLocked(null, null, null, null);
252            }
253        }
254    }
255
256    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
257        private final TvInputHardwareInfo mInfo;
258        private boolean mReleased = false;
259        private final Object mImplLock = new Object();
260
261        public TvInputHardwareImpl(TvInputHardwareInfo info) {
262            mInfo = info;
263        }
264
265        public void release() {
266            synchronized (mImplLock) {
267                mReleased = true;
268            }
269        }
270
271        @Override
272        public boolean setSurface(Surface surface, TvStreamConfig config)
273                throws RemoteException {
274            synchronized (mImplLock) {
275                if (mReleased) {
276                    throw new IllegalStateException("Device already released.");
277                }
278                if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
279                    if (surface != null) {
280                        // Set "Active Source" for HDMI.
281                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
282                        mActiveHdmiSources.add(mInfo.getDeviceId());
283                    } else {
284                        mActiveHdmiSources.remove(mInfo.getDeviceId());
285                        if (mActiveHdmiSources.size() == 0) {
286                            // Tell HDMI that no HDMI source is active
287                            // TODO(hdmi): mHdmiClient.portSelect(null);
288                        }
289                    }
290                }
291                return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
292            }
293        }
294
295        @Override
296        public void setVolume(float volume) throws RemoteException {
297            synchronized (mImplLock) {
298                if (mReleased) {
299                    throw new IllegalStateException("Device already released.");
300                }
301            }
302            // TODO
303        }
304
305        @Override
306        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
307            synchronized (mImplLock) {
308                if (mReleased) {
309                    throw new IllegalStateException("Device already released.");
310                }
311            }
312            if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
313                return false;
314            }
315            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
316            return false;
317        }
318    }
319}
320