1 /* 2 * Copyright (C) 2015 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.tvinput; 18 19 import android.app.job.JobInfo; 20 import android.app.job.JobScheduler; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.media.tv.TvInputService; 24 import android.net.Uri; 25 import android.util.Log; 26 27 import com.android.tv.common.feature.CommonFeatures; 28 import com.android.tv.common.flags.TunerFlags; 29 import com.android.tv.tuner.tvinput.datamanager.ChannelDataManager; 30 import com.android.tv.tuner.tvinput.factory.TunerRecordingSessionFactory; 31 import com.android.tv.tuner.tvinput.factory.TunerSessionFactory; 32 33 import com.google.common.cache.CacheBuilder; 34 import com.google.common.cache.CacheLoader; 35 import com.google.common.cache.LoadingCache; 36 import com.google.common.cache.RemovalListener; 37 38 import dagger.android.AndroidInjection; 39 40 import java.util.Collections; 41 import java.util.Set; 42 import java.util.WeakHashMap; 43 import java.util.concurrent.TimeUnit; 44 45 import javax.inject.Inject; 46 47 /** {@link BaseTunerTvInputService} serves TV channels coming from a tuner device. */ 48 public class BaseTunerTvInputService extends TvInputService { 49 private static final String TAG = "BaseTunerTvInputService"; 50 private static final boolean DEBUG = false; 51 52 private static final int DVR_STORAGE_CLEANUP_JOB_ID = 100; 53 54 private final Set<Session> mTunerSessions = Collections.newSetFromMap(new WeakHashMap<>()); 55 private final Set<RecordingSession> mTunerRecordingSession = 56 Collections.newSetFromMap(new WeakHashMap<>()); 57 @Inject TunerSessionFactory mTunerSessionFactory; 58 @Inject TunerRecordingSessionFactory mTunerRecordingSessionFactory; 59 @Inject TunerFlags mTunerFlags; 60 61 LoadingCache<String, ChannelDataManager> mChannelDataManagers; 62 RemovalListener<String, ChannelDataManager> mChannelDataManagerRemovalListener = 63 notification -> { 64 ChannelDataManager cdm = notification.getValue(); 65 if (cdm != null) { 66 cdm.release(); 67 } 68 }; 69 70 @Override onCreate()71 public void onCreate() { 72 if (getApplicationContext().getSystemService(Context.TV_INPUT_SERVICE) == null) { 73 Log.wtf(TAG, "Stopping because device does not have a TvInputManager"); 74 this.stopSelf(); 75 return; 76 } 77 AndroidInjection.inject(this); 78 super.onCreate(); 79 if (DEBUG) Log.d(TAG, "onCreate"); 80 mChannelDataManagers = 81 CacheBuilder.newBuilder() 82 .weakValues() 83 .removalListener(mChannelDataManagerRemovalListener) 84 .build( 85 new CacheLoader<String, ChannelDataManager>() { 86 @Override 87 public ChannelDataManager load(String inputId) { 88 return createChannelDataManager(inputId); 89 } 90 }); 91 if (CommonFeatures.DVR.isEnabled(this)) { 92 JobScheduler jobScheduler = 93 (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); 94 JobInfo pendingJob = jobScheduler.getPendingJob(DVR_STORAGE_CLEANUP_JOB_ID); 95 if (pendingJob != null) { 96 // storage cleaning job is already scheduled. 97 } else { 98 JobInfo job = 99 new JobInfo.Builder( 100 DVR_STORAGE_CLEANUP_JOB_ID, 101 new ComponentName(this, TunerStorageCleanUpService.class)) 102 .setPersisted(true) 103 .setPeriodic(TimeUnit.DAYS.toMillis(1)) 104 .build(); 105 jobScheduler.schedule(job); 106 } 107 } 108 } 109 createChannelDataManager(String inputId)110 private ChannelDataManager createChannelDataManager(String inputId) { 111 return new ChannelDataManager(getApplicationContext(), inputId); 112 } 113 114 @Override onDestroy()115 public void onDestroy() { 116 if (DEBUG) Log.d(TAG, "onDestroy"); 117 super.onDestroy(); 118 mChannelDataManagers.invalidateAll(); 119 } 120 121 @Override onCreateRecordingSession(String inputId)122 public RecordingSession onCreateRecordingSession(String inputId) { 123 RecordingSession session = 124 mTunerRecordingSessionFactory.create( 125 inputId, this::onReleased, mChannelDataManagers.getUnchecked(inputId)); 126 mTunerRecordingSession.add(session); 127 return session; 128 } 129 130 @Override onCreateSession(String inputId)131 public Session onCreateSession(String inputId) { 132 if (DEBUG) Log.d(TAG, "onCreateSession"); 133 try { 134 // TODO(b/65445352): Support multiple TunerSessions for multiple tuners 135 if (!mTunerSessions.isEmpty()) { 136 Log.d(TAG, "abort creating an session"); 137 return null; 138 } 139 140 final Session session = 141 mTunerSessionFactory.create( 142 mChannelDataManagers.getUnchecked(inputId), 143 this::onReleased, 144 this::getRecordingUri); 145 mTunerSessions.add(session); 146 session.setOverlayViewEnabled(true); 147 return session; 148 } catch (RuntimeException e) { 149 // There are no available DVB devices. 150 Log.e(TAG, "Creating a session for " + inputId + " failed.", e); 151 return null; 152 } 153 } 154 getRecordingUri(Uri channelUri)155 private Uri getRecordingUri(Uri channelUri) { 156 for (RecordingSession session : mTunerRecordingSession) { 157 if (mTunerFlags.useExoplayerV2()) { 158 TunerRecordingSessionExoV2 tunerSession = (TunerRecordingSessionExoV2) session; 159 if (tunerSession.getChannelUri().equals(channelUri)) { 160 return tunerSession.getRecordingUri(); 161 } 162 } else { 163 TunerRecordingSession tunerSession = (TunerRecordingSession) session; 164 if (tunerSession.getChannelUri().equals(channelUri)) { 165 return tunerSession.getRecordingUri(); 166 } 167 } 168 } 169 return null; 170 } 171 onReleased(Session session)172 private void onReleased(Session session) { 173 mTunerSessions.remove(session); 174 mChannelDataManagers.cleanUp(); 175 } 176 onReleased(RecordingSession session)177 private void onReleased(RecordingSession session) { 178 mTunerRecordingSession.remove(session); 179 } 180 } 181