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