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