165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/*
265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Copyright (C) 2016 The Android Open Source Project
365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Licensed under the Apache License, Version 2.0 (the "License");
565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * you may not use this file except in compliance with the License.
665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * You may obtain a copy of the License at
765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *      http://www.apache.org/licenses/LICENSE-2.0
965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko *
1065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Unless required by applicable law or agreed to in writing, software
1165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * distributed under the License is distributed on an "AS IS" BASIS,
1265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * See the License for the specific language governing permissions and
1465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * limitations under the License.
1565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
1665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkopackage com.android.tv.tuner.source;
1865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport android.content.Context;
2065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
2165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.AutoCloseableUtils;
2265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.common.SoftPreconditions;
2365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.TunerHal;
2465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.data.TunerChannel;
2565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport com.android.tv.tuner.tvinput.EventDetector;
2665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
2765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashMap;
2865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.HashSet;
2965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Iterator;
3065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Map;
3165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoimport java.util.Set;
3265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
3365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko/**
3465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * Manages {@link TunerTsStreamer} for playback and recording.
3565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko * The class hides handling of {@link TunerHal} from other classes.
36d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko * This class is used by {@link TsDataSourceManager}. Don't use this class directly.
3765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko */
3865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalkoclass TunerTsStreamerManager {
3965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator
4065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from
4165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    // the same session.
4265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Object mCancelLock = new Object();
4365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final StreamerFinder mStreamerFinder = new StreamerFinder();
4465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final Map<Integer, TsStreamerCreator> mCreators = new HashMap<>();
456ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    private final Map<Integer, EventDetector.EventListener> mListeners = new HashMap<>();
46d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    private final Map<TsDataSource, TunerTsStreamer> mSourceToStreamerMap = new HashMap<>();
4765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private final TunerHalManager mTunerHalManager = new TunerHalManager();
4865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private static TunerTsStreamerManager sInstance;
4965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
5065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
5165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Returns the singleton instance for the class
5265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * @return TunerTsStreamerManager
5365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
5465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    static synchronized TunerTsStreamerManager getInstance() {
5565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        if (sInstance == null) {
5665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            sInstance = new TunerTsStreamerManager();
5765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
5865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return sInstance;
5965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
6065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
6165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private TunerTsStreamerManager() { }
6265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
63d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    synchronized TsDataSource createDataSource(
6465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Context context, TunerChannel channel, EventDetector.EventListener listener,
6565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            int sessionId, boolean reuse) {
6665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        TsStreamerCreator creator;
6765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCancelLock) {
6865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mStreamerFinder.containsLocked(channel)) {
6965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreamerFinder.appendSessionLocked(channel, sessionId);
7065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                TunerTsStreamer streamer =  mStreamerFinder.getStreamerLocked(channel);
71d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                TsDataSource source = streamer.createDataSource();
726ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mListeners.put(sessionId, listener);
736ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                streamer.registerListener(listener);
7465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mSourceToStreamerMap.put(source, streamer);
7565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return source;
7665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
7765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            creator = new TsStreamerCreator(context, channel, listener);
7865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mCreators.put(sessionId, creator);
7965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
8065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        TunerTsStreamer streamer = creator.create(sessionId, reuse);
8165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCancelLock) {
8265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mCreators.remove(sessionId);
8365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (streamer == null) {
8465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return null;
8565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
8665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (!creator.isCancelledLocked()) {
8765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreamerFinder.putLocked(channel, sessionId, streamer);
88d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                TsDataSource source = streamer.createDataSource();
896ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                mListeners.put(sessionId, listener);
9065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mSourceToStreamerMap.put(source, streamer);
9165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return source;
9265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
9365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
9465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // Created streamer was cancelled by a new tune request.
9565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        streamer.stopStream();
9665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        TunerHal hal = streamer.getTunerHal();
9765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        hal.setHasPendingTune(false);
9865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
9965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        return null;
10065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
10165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
102d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko    synchronized void releaseDataSource(TsDataSource source, int sessionId,
10365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            boolean reuse) {
10465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        TunerTsStreamer streamer;
10565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCancelLock) {
10665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            streamer = mSourceToStreamerMap.get(source);
10765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSourceToStreamerMap.remove(source);
10865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (streamer == null) {
10965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return;
11065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
1116ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            EventDetector.EventListener listener = mListeners.remove(sessionId);
1126ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            streamer.unregisterListener(listener);
11365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            TunerChannel channel = streamer.getChannel();
11465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            SoftPreconditions.checkState(channel != null);
11565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamerFinder.removeSessionLocked(channel, sessionId);
11665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mStreamerFinder.containsLocked(channel)) {
11765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return;
11865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
11965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
12065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        streamer.stopStream();
12165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        TunerHal hal = streamer.getTunerHal();
12265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        hal.setHasPendingTune(false);
12365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerHalManager.releaseTunerHal(hal, sessionId, reuse);
12465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
12565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
12665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    void setHasPendingTune(int sessionId) {
12765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        synchronized (mCancelLock) {
12865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko           if (mCreators.containsKey(sessionId)) {
12965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko               mCreators.get(sessionId).cancelLocked();
13065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko           }
13165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
13265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
13365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
1346ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    /**
1356ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     * Add tuner hal into TunerHalManager for test.
1366ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko     */
1376ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    void addTunerHal(TunerHal tunerHal, int sessionId) {
1386ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        mTunerHalManager.addTunerHal(tunerHal, sessionId);
1396ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko    }
1406ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
14165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    synchronized void release(int sessionId) {
14265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        mTunerHalManager.releaseCachedHal(sessionId);
14365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
14465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
14565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private class StreamerFinder {
14665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final Map<TunerChannel, Set<Integer>> mSessions = new HashMap<>();
14765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final Map<TunerChannel, TunerTsStreamer> mStreamers = new HashMap<>();
14865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
14965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
15065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) {
15165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Set<Integer> sessions = new HashSet<>();
15265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            sessions.add(sessionId);
15365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mSessions.put(channel, sessions);
15465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mStreamers.put(channel, streamer);
15565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
15665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
15765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
15865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void appendSessionLocked(TunerChannel channel, int sessionId) {
15965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (mSessions.containsKey(channel)) {
16065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mSessions.get(channel).add(sessionId);
16165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
16265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
16365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
16465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
16565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void removeSessionLocked(TunerChannel channel, int sessionId) {
16665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Set<Integer> sessions = mSessions.get(channel);
16765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            sessions.remove(sessionId);
16865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (sessions.size() == 0) {
16965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mSessions.remove(channel);
17065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mStreamers.remove(channel);
17165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
17265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
17365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
17565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private boolean containsLocked(TunerChannel channel) {
17665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return mSessions.containsKey(channel);
17765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
17865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
17965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
18065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private TunerTsStreamer getStreamerLocked(TunerChannel channel) {
18165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null;
18265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
18365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
18465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
18565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
18665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same
18765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * session. The class supports the cancellation in creating new {@link TunerTsStreamer}.
18865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
18965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private class TsStreamerCreator {
19065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final Context mContext;
19165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final TunerChannel mChannel;
19265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final EventDetector.EventListener mEventListener;
19365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // mCancelled will be {@code true} if a new tune request for the same session
19465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // cancels create().
19565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private boolean mCancelled;
19665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private TunerHal mTunerHal;
19765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
19865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private TsStreamerCreator(Context context, TunerChannel channel,
19965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                EventDetector.EventListener listener) {
20065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mContext = context;
20165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mChannel = channel;
20265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mEventListener = listener;
20365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
20465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
20565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private TunerTsStreamer create(int sessionId, boolean reuse) {
20665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId);
20765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (hal == null) {
20865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return null;
20965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
21065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            boolean canceled = false;
21165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            synchronized (mCancelLock) {
21265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (!mCancelled) {
21365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mTunerHal = hal;
21465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                } else {
21565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    canceled = true;
21665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
21765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
21865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (!canceled) {
219d41f0075a7d2ea826204e81fcec57d0aa57171a9Nick Chalko                TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext);
22065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (tsStreamer.startStream(mChannel)) {
22165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return tsStreamer;
22265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
22365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                synchronized (mCancelLock) {
22465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mTunerHal = null;
22565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
22665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
22765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            hal.setHasPendingTune(false);
22865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Since TunerTsStreamer is not properly created, closes TunerHal.
22965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // And do not re-use TunerHal when it is not cancelled.
23065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse);
23165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return null;
23265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
23365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
23465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
23565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void cancelLocked() {
23665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mCancelled) {
23765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    return;
23865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
23965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mCancelled = true;
24065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                if (mTunerHal != null) {
24165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                    mTunerHal.setHasPendingTune(true);
24265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                }
24365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
24565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        // @GuardedBy("mCancelLock")
24665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private boolean isCancelledLocked() {
24765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return mCancelled;
24865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
24965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
25065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
25165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    /**
25265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * Supports sharing {@link TunerHal} among multiple sessions.
25365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     * The class also supports session affinity for {@link TunerHal} allocation.
25465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko     */
25565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    private class TunerHalManager {
25665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private final Map<Integer, TunerHal> mTunerHals = new HashMap<>();
25765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
25865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private TunerHal getOrCreateTunerHal(Context context, int sessionId) {
25965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Handles session affinity.
26065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            TunerHal hal = mTunerHals.get(sessionId);
26165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (hal != null) {
26265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mTunerHals.remove(sessionId);
26365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return hal;
26465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
26565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            // Finds a TunerHal which is cached for other sessions.
26665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            Iterator it = mTunerHals.keySet().iterator();
26765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (it.hasNext()) {
26865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                Integer key = (Integer) it.next();
26965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                hal = mTunerHals.get(key);
27065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mTunerHals.remove(key);
27165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return hal;
27265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
27365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            return TunerHal.createInstance(context);
27465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
27565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
27665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) {
2776ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            if (!reuse || !hal.isReusable()) {
27865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                AutoCloseableUtils.closeQuietly(hal);
27965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                return;
28065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
28165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            TunerHal cachedHal = mTunerHals.get(sessionId);
28265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (cachedHal != hal) {
28365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mTunerHals.put(sessionId, hal);
2846ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                if (cachedHal != null) {
2856ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                    AutoCloseableUtils.closeQuietly(cachedHal);
2866ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko                }
28765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
28865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
28965fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko
29065fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        private void releaseCachedHal(int sessionId) {
29165fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            TunerHal hal = mTunerHals.get(sessionId);
29265fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (hal != null) {
29365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                mTunerHals.remove(sessionId);
29465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
29565fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            if (hal != null) {
29665fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko                AutoCloseableUtils.closeQuietly(hal);
29765fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko            }
29865fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko        }
2996ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko
3006ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        private void addTunerHal(TunerHal tunerHal, int sessionId) {
3016ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko            mTunerHals.put(sessionId, tunerHal);
3026ebde20b03db4c0d57f67acaac11832b610b966bNick Chalko        }
30365fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko    }
30465fda1eaa94968bb55d5ded10dcb0b3f37fb05f2Nick Chalko}