/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.tuner.source; import android.content.Context; import com.android.tv.common.AutoCloseableUtils; import com.android.tv.common.SoftPreconditions; import com.android.tv.tuner.TunerHal; import com.android.tv.tuner.data.TunerChannel; import com.android.tv.tuner.tvinput.EventDetector; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * Manages {@link TunerTsStreamer} for playback and recording. * The class hides handling of {@link TunerHal} from other classes. * This class is used by {@link TsDataSourceManager}. Don't use this class directly. */ class TunerTsStreamerManager { // The lock will protect mStreamerFinder, mSourceToStreamerMap and some part of TsStreamCreator // to support timely {@link TunerTsStreamer} cancellation due to a new tune request from // the same session. private final Object mCancelLock = new Object(); private final StreamerFinder mStreamerFinder = new StreamerFinder(); private final Map mCreators = new HashMap<>(); private final Map mListeners = new HashMap<>(); private final Map mSourceToStreamerMap = new HashMap<>(); private final TunerHalManager mTunerHalManager = new TunerHalManager(); private static TunerTsStreamerManager sInstance; /** * Returns the singleton instance for the class * @return TunerTsStreamerManager */ static synchronized TunerTsStreamerManager getInstance() { if (sInstance == null) { sInstance = new TunerTsStreamerManager(); } return sInstance; } private TunerTsStreamerManager() { } synchronized TsDataSource createDataSource( Context context, TunerChannel channel, EventDetector.EventListener listener, int sessionId, boolean reuse) { TsStreamerCreator creator; synchronized (mCancelLock) { if (mStreamerFinder.containsLocked(channel)) { mStreamerFinder.appendSessionLocked(channel, sessionId); TunerTsStreamer streamer = mStreamerFinder.getStreamerLocked(channel); TsDataSource source = streamer.createDataSource(); mListeners.put(sessionId, listener); streamer.registerListener(listener); mSourceToStreamerMap.put(source, streamer); return source; } creator = new TsStreamerCreator(context, channel, listener); mCreators.put(sessionId, creator); } TunerTsStreamer streamer = creator.create(sessionId, reuse); synchronized (mCancelLock) { mCreators.remove(sessionId); if (streamer == null) { return null; } if (!creator.isCancelledLocked()) { mStreamerFinder.putLocked(channel, sessionId, streamer); TsDataSource source = streamer.createDataSource(); mListeners.put(sessionId, listener); mSourceToStreamerMap.put(source, streamer); return source; } } // Created streamer was cancelled by a new tune request. streamer.stopStream(); TunerHal hal = streamer.getTunerHal(); hal.setHasPendingTune(false); mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); return null; } synchronized void releaseDataSource(TsDataSource source, int sessionId, boolean reuse) { TunerTsStreamer streamer; synchronized (mCancelLock) { streamer = mSourceToStreamerMap.get(source); mSourceToStreamerMap.remove(source); if (streamer == null) { return; } EventDetector.EventListener listener = mListeners.remove(sessionId); streamer.unregisterListener(listener); TunerChannel channel = streamer.getChannel(); SoftPreconditions.checkState(channel != null); mStreamerFinder.removeSessionLocked(channel, sessionId); if (mStreamerFinder.containsLocked(channel)) { return; } } streamer.stopStream(); TunerHal hal = streamer.getTunerHal(); hal.setHasPendingTune(false); mTunerHalManager.releaseTunerHal(hal, sessionId, reuse); } void setHasPendingTune(int sessionId) { synchronized (mCancelLock) { if (mCreators.containsKey(sessionId)) { mCreators.get(sessionId).cancelLocked(); } } } /** * Add tuner hal into TunerHalManager for test. */ void addTunerHal(TunerHal tunerHal, int sessionId) { mTunerHalManager.addTunerHal(tunerHal, sessionId); } synchronized void release(int sessionId) { mTunerHalManager.releaseCachedHal(sessionId); } private class StreamerFinder { private final Map> mSessions = new HashMap<>(); private final Map mStreamers = new HashMap<>(); // @GuardedBy("mCancelLock") private void putLocked(TunerChannel channel, int sessionId, TunerTsStreamer streamer) { Set sessions = new HashSet<>(); sessions.add(sessionId); mSessions.put(channel, sessions); mStreamers.put(channel, streamer); } // @GuardedBy("mCancelLock") private void appendSessionLocked(TunerChannel channel, int sessionId) { if (mSessions.containsKey(channel)) { mSessions.get(channel).add(sessionId); } } // @GuardedBy("mCancelLock") private void removeSessionLocked(TunerChannel channel, int sessionId) { Set sessions = mSessions.get(channel); sessions.remove(sessionId); if (sessions.size() == 0) { mSessions.remove(channel); mStreamers.remove(channel); } } // @GuardedBy("mCancelLock") private boolean containsLocked(TunerChannel channel) { return mSessions.containsKey(channel); } // @GuardedBy("mCancelLock") private TunerTsStreamer getStreamerLocked(TunerChannel channel) { return mStreamers.containsKey(channel) ? mStreamers.get(channel) : null; } } /** * {@link TunerTsStreamer} creation can be cancelled by a new tune request for the same * session. The class supports the cancellation in creating new {@link TunerTsStreamer}. */ private class TsStreamerCreator { private final Context mContext; private final TunerChannel mChannel; private final EventDetector.EventListener mEventListener; // mCancelled will be {@code true} if a new tune request for the same session // cancels create(). private boolean mCancelled; private TunerHal mTunerHal; private TsStreamerCreator(Context context, TunerChannel channel, EventDetector.EventListener listener) { mContext = context; mChannel = channel; mEventListener = listener; } private TunerTsStreamer create(int sessionId, boolean reuse) { TunerHal hal = mTunerHalManager.getOrCreateTunerHal(mContext, sessionId); if (hal == null) { return null; } boolean canceled = false; synchronized (mCancelLock) { if (!mCancelled) { mTunerHal = hal; } else { canceled = true; } } if (!canceled) { TunerTsStreamer tsStreamer = new TunerTsStreamer(hal, mEventListener, mContext); if (tsStreamer.startStream(mChannel)) { return tsStreamer; } synchronized (mCancelLock) { mTunerHal = null; } } hal.setHasPendingTune(false); // Since TunerTsStreamer is not properly created, closes TunerHal. // And do not re-use TunerHal when it is not cancelled. mTunerHalManager.releaseTunerHal(hal, sessionId, mCancelled && reuse); return null; } // @GuardedBy("mCancelLock") private void cancelLocked() { if (mCancelled) { return; } mCancelled = true; if (mTunerHal != null) { mTunerHal.setHasPendingTune(true); } } // @GuardedBy("mCancelLock") private boolean isCancelledLocked() { return mCancelled; } } /** * Supports sharing {@link TunerHal} among multiple sessions. * The class also supports session affinity for {@link TunerHal} allocation. */ private class TunerHalManager { private final Map mTunerHals = new HashMap<>(); private TunerHal getOrCreateTunerHal(Context context, int sessionId) { // Handles session affinity. TunerHal hal = mTunerHals.get(sessionId); if (hal != null) { mTunerHals.remove(sessionId); return hal; } // Finds a TunerHal which is cached for other sessions. Iterator it = mTunerHals.keySet().iterator(); if (it.hasNext()) { Integer key = (Integer) it.next(); hal = mTunerHals.get(key); mTunerHals.remove(key); return hal; } return TunerHal.createInstance(context); } private void releaseTunerHal(TunerHal hal, int sessionId, boolean reuse) { if (!reuse || !hal.isReusable()) { AutoCloseableUtils.closeQuietly(hal); return; } TunerHal cachedHal = mTunerHals.get(sessionId); if (cachedHal != hal) { mTunerHals.put(sessionId, hal); if (cachedHal != null) { AutoCloseableUtils.closeQuietly(cachedHal); } } } private void releaseCachedHal(int sessionId) { TunerHal hal = mTunerHals.get(sessionId); if (hal != null) { mTunerHals.remove(sessionId); } if (hal != null) { AutoCloseableUtils.closeQuietly(hal); } } private void addTunerHal(TunerHal tunerHal, int sessionId) { mTunerHals.put(sessionId, tunerHal); } } }