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