1 /*
2  * Copyright (C) 2016 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 
17 package com.android.tv.tuner.source;
18 
19 import android.content.Context;
20 
21 import com.android.tv.common.AutoCloseableUtils;
22 import com.android.tv.common.SoftPreconditions;
23 import com.android.tv.tuner.TunerHal;
24 import com.android.tv.tuner.data.TunerChannel;
25 import com.android.tv.tuner.tvinput.EventDetector;
26 
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Set;
32 
33 /**
34  * Manages {@link TunerTsStreamer} for playback and recording.
35  * The class hides handling of {@link TunerHal} from other classes.
36  * This class is used by {@link TsDataSourceManager}. Don't use this class directly.
37  */
38 class TunerTsStreamerManager {
39     // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator
40     // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from
41     // the same session.
42     private final Object mCancelLock = new Object();
43     private final StreamerFinder mStreamerFinder = new StreamerFinder();
44     private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>();
45     private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>();
46     private final TunerHalManager mTunerHalManager = new TunerHalManager();
47     private static TunerTsStreamerManager sInstance;
48 
49     /**
50      * Returns the singleton instance for the class
51      * @return TunerTsStreamerManager
52      */
getInstance()53     static synchronized TunerTsStreamerManager getInstance() {
54         if (sInstance == null) {
55             sInstance = new TunerTsStreamerManager();
56         }
57         return sInstance;
58     }
59 
TunerTsStreamerManager()60     private TunerTsStreamerManager() { }
61 
createDataSource( Context context, TunerChannel channel, EventDetector.EventListener listener, int sessionId, boolean reuse)62     synchronized TsDataSource createDataSource(
63             Context context, TunerChannel channel, EventDetector.EventListener listener,
64             int sessionId, boolean reuse) {
65         TsStreamerCreator creator;
66         synchronized (mCancelLock) {
67             if (mStreamerFinder.containsLocked(channel)) {
68                 mStreamerFinder.appendSessionLocked(channel, sessionId);
69                 TunerTsStreamer streamer =  mStreamerFinder.getStreamerLocked(channel);
70                 TsDataSource source = streamer.createDataSource();
71                 mSourceToStreamerMap.put(source, streamer);
72                 return source;
73             }
74             creator = new TsStreamerCreator(context, channel, listener);
75             mCreators.put(sessionId, creator);
76         }
77         TunerTsStreamer streamer = creator.create(sessionId, reuse);
78         synchronized (mCancelLock) {
79             mCreators.remove(sessionId);
80             if (streamer == null) {
81                 return null;
82             }
83             if (!creator.isCancelledLocked()) {
84                 mStreamerFinder.putLocked(channel, sessionId, streamer);
85                 TsDataSource source = streamer.createDataSource();
86                 mSourceToStreamerMap.put(source, streamer);
87                 return source;
88             }
89         }
90         // Created streamer was cancelled by a new tune request.
91         streamer.stopStream();
92         TunerHal hal = streamer.getTunerHal();
93         hal.setHasPendingTune(false);
94         mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
95         return null;
96     }
97 
releaseDataSource(TsDataSource source, int sessionId, boolean reuse)98     synchronized void releaseDataSource(TsDataSource source, int sessionId,
99             boolean reuse) {
100         TunerTsStreamer streamer;
101         synchronized (mCancelLock) {
102             streamer = mSourceToStreamerMap.get(source);
103             mSourceToStreamerMap.remove(source);
104             if (streamer == null) {
105                 return;
106             }
107             TunerChannel channel = streamer.getChannel();
108             SoftPreconditions.checkState(channel != null);
109             mStreamerFinder.removeSessionLocked(channel, sessionId);
110             if (mStreamerFinder.containsLocked(channel)) {
111                 return;
112             }
113         }
114         streamer.stopStream();
115         TunerHal hal = streamer.getTunerHal();
116         hal.setHasPendingTune(false);
117         mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
118     }
119 
setHasPendingTune(int sessionId)120     void setHasPendingTune(int sessionId) {
121         synchronized (mCancelLock) {
122            if (mCreators.containsKey(sessionId)) {
123                mCreators.get(sessionId).cancelLocked();
124            }
125         }
126     }
127 
release(int sessionId)128     synchronized void release(int sessionId) {
129         mTunerHalManager.releaseCachedHal(sessionId);
130     }
131 
132     private class StreamerFinder {
133         private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>();
134         private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>();
135 
136         // @GuardedBy("mCancelLock")
putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer)137         private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) {
138             Set<Integer> sessions = new HashSet<>();
139             sessions.add(sessionId);
140             mSessions.put(channel, sessions);
141             mStreamers.put(channel, streamer);
142         }
143 
144         // @GuardedBy("mCancelLock")
appendSessionLocked(TunerChannel channel, int sessionId)145         private void appendSessionLocked(TunerChannel channel, int sessionId) {
146             if (mSessions.containsKey(channel)) {
147                 mSessions.get(channel).add(sessionId);
148             }
149         }
150 
151         // @GuardedBy("mCancelLock")
removeSessionLocked(TunerChannel channel, int sessionId)152         private void removeSessionLocked(TunerChannel channel, int sessionId) {
153             Set<Integer> sessions = mSessions.get(channel);
154             sessions.remove(sessionId);
155             if (sessions.size() == 0) {
156                 mSessions.remove(channel);
157                 mStreamers.remove(channel);
158             }
159         }
160 
161         // @GuardedBy("mCancelLock")
containsLocked(TunerChannel channel)162         private boolean containsLocked(TunerChannel channel) {
163             return mSessions.containsKey(channel);
164         }
165 
166         // @GuardedBy("mCancelLock")
getStreamerLocked(TunerChannel channel)167         private TunerTsStreamer getStreamerLocked(TunerChannel channel) {
168             return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null;
169         }
170     }
171 
172     /**
173      * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same
174      * session. The class supports the cancellation in creating new {@link TunerTsStreamer}.
175      */
176     private class TsStreamerCreator {
177         private final Context mContext;
178         private final TunerChannel mChannel;
179         private final EventDetector.EventListener mEventListener;
180         // mCancelled will be {@code true} if a new tune request for the same session
181         // cancels create().
182         private boolean mCancelled;
183         private TunerHal mTunerHal;
184 
TsStreamerCreator(Context context, TunerChannel channel, EventDetector.EventListener listener)185         private TsStreamerCreator(Context context, TunerChannel channel,
186                 EventDetector.EventListener listener) {
187             mContext = context;
188             mChannel = channel;
189             mEventListener = listener;
190         }
191 
create(int sessionId, boolean reuse)192         private TunerTsStreamer create(int sessionId, boolean reuse) {
193             TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId);
194             if (hal == null) {
195                 return null;
196             }
197             boolean canceled = false;
198             synchronized (mCancelLock) {
199                 if (!mCancelled) {
200                     mTunerHal = hal;
201                 } else {
202                     canceled = true;
203                 }
204             }
205             if (!canceled) {
206                 TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext);
207                 if (tsStreamer.startStream(mChannel)) {
208                     return tsStreamer;
209                 }
210                 synchronized (mCancelLock) {
211                     mTunerHal = null;
212                 }
213             }
214             hal.setHasPendingTune(false);
215             // Since TunerTsStreamer is not properly created, closes TunerHal.
216             // And do not re-use TunerHal when it is not cancelled.
217             mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse);
218             return null;
219         }
220 
221         // @GuardedBy("mCancelLock")
cancelLocked()222         private void cancelLocked() {
223                 if (mCancelled) {
224                     return;
225                 }
226                 mCancelled = true;
227                 if (mTunerHal != null) {
228                     mTunerHal.setHasPendingTune(true);
229                 }
230         }
231 
232         // @GuardedBy("mCancelLock")
isCancelledLocked()233         private boolean isCancelledLocked() {
234                 return mCancelled;
235         }
236     }
237 
238     /**
239      * Supports sharing {@link TunerHal} among multiple sessions.
240      * The class also supports session affinity for {@link TunerHal} allocation.
241      */
242     private class TunerHalManager {
243         private final Map<Integer, TunerHal> mTunerHals = new HashMap<>();
244 
getOrCreateTunerHal(Context context, int sessionId)245         private TunerHal getOrCreateTunerHal(Context context, int sessionId) {
246             // Handles session affinity.
247             TunerHal hal = mTunerHals.get(sessionId);
248             if (hal != null) {
249                 mTunerHals.remove(sessionId);
250                 return hal;
251             }
252             // Finds a TunerHal which is cached for other sessions.
253             Iterator it = mTunerHals.keySet().iterator();
254             if (it.hasNext()) {
255                 Integer key = (Integer) it.next();
256                 hal = mTunerHals.get(key);
257                 mTunerHals.remove(key);
258                 return hal;
259             }
260             return TunerHal.createInstance(context);
261         }
262 
releaseTunerHal(TunerHal hal, int sessionId, boolean reuse)263         private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
264             if (!reuse) {
265                 AutoCloseableUtils.closeQuietly(hal);
266                 return;
267             }
268             TunerHal cachedHal = mTunerHals.get(sessionId);
269             if (cachedHal != hal) {
270                 mTunerHals.put(sessionId, hal);
271             }
272             if (cachedHal != null && cachedHal != hal) {
273                 AutoCloseableUtils.closeQuietly(cachedHal);
274             }
275         }
276 
releaseCachedHal(int sessionId)277         private void releaseCachedHal(int sessionId) {
278             TunerHal hal = mTunerHals.get(sessionId);
279             if (hal != null) {
280                 mTunerHals.remove(sessionId);
281             }
282             if (hal != null) {
283                 AutoCloseableUtils.closeQuietly(hal);
284             }
285         }
286     }
287 }