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.dvr; 18 19 import android.content.ContentResolver; 20 import android.content.Context; 21 import android.media.tv.TvInputInfo; 22 import android.os.Handler; 23 import android.support.annotation.MainThread; 24 import android.support.annotation.NonNull; 25 import android.support.annotation.WorkerThread; 26 import android.util.Log; 27 import android.util.Range; 28 import android.widget.Toast; 29 30 import com.android.tv.ApplicationSingletons; 31 import com.android.tv.TvApplication; 32 import com.android.tv.common.SoftPreconditions; 33 import com.android.tv.common.feature.CommonFeatures; 34 import com.android.tv.common.recording.RecordedProgram; 35 import com.android.tv.data.Channel; 36 import com.android.tv.data.ChannelDataManager; 37 import com.android.tv.data.Program; 38 import com.android.tv.util.AsyncDbTask; 39 import com.android.tv.util.Utils; 40 41 import java.util.Collections; 42 import java.util.HashMap; 43 import java.util.List; 44 import java.util.Map; 45 import java.util.Map.Entry; 46 47 /** 48 * DVR manager class to add and remove recordings. UI can modify recording list through this class, 49 * instead of modifying them directly through {@link DvrDataManager}. 50 */ 51 @MainThread 52 public class DvrManager { 53 private final static String TAG = "DvrManager"; 54 private final WritableDvrDataManager mDataManager; 55 private final ChannelDataManager mChannelDataManager; 56 private final DvrSessionManager mDvrSessionManager; 57 // @GuardedBy("mListener") 58 private final Map<Listener, Handler> mListener = new HashMap<>(); 59 private final Context mAppContext; 60 DvrManager(Context context)61 public DvrManager(Context context) { 62 SoftPreconditions.checkFeatureEnabled(context, CommonFeatures.DVR, TAG); 63 ApplicationSingletons appSingletons = TvApplication.getSingletons(context); 64 mDataManager = (WritableDvrDataManager) appSingletons.getDvrDataManager(); 65 mAppContext = context.getApplicationContext(); 66 mChannelDataManager = appSingletons.getChannelDataManager(); 67 mDvrSessionManager = appSingletons.getDvrSessionManger(); 68 } 69 70 /** 71 * Schedules a recording for {@code program} instead of the list of recording that conflict. 72 * @param program the program to record 73 * @param recordingsToOverride the possible empty list of recordings that will not be recorded 74 */ addSchedule(Program program, List<ScheduledRecording> recordingsToOverride)75 public void addSchedule(Program program, List<ScheduledRecording> recordingsToOverride) { 76 Log.i(TAG, 77 "Adding scheduled recording of " + program + " instead of " + recordingsToOverride); 78 Collections.sort(recordingsToOverride, ScheduledRecording.PRIORITY_COMPARATOR); 79 Channel c = mChannelDataManager.getChannel(program.getChannelId()); 80 long priority = recordingsToOverride.isEmpty() ? Long.MAX_VALUE 81 : recordingsToOverride.get(0).getPriority() - 1; 82 ScheduledRecording r = ScheduledRecording.builder(program) 83 .setPriority(priority) 84 .setChannelId(c.getId()) 85 .build(); 86 mDataManager.addScheduledRecording(r); 87 } 88 89 /** 90 * Adds a recording schedule with a time range. 91 */ addSchedule(Channel channel, long startTime, long endTime)92 public void addSchedule(Channel channel, long startTime, long endTime) { 93 Log.i(TAG, "Adding scheduled recording of channel" + channel + " starting at " + 94 Utils.toTimeString(startTime) + " and ending at " + Utils.toTimeString(endTime)); 95 //TODO: handle error cases 96 ScheduledRecording r = ScheduledRecording.builder(startTime, endTime) 97 .setChannelId(channel.getId()) 98 .build(); 99 mDataManager.addScheduledRecording(r); 100 } 101 102 /** 103 * Adds a season recording schedule based on {@code program}. 104 */ addSeasonSchedule(Program program)105 public void addSeasonSchedule(Program program) { 106 Log.i(TAG, "Adding season recording of " + program); 107 // TODO: implement 108 } 109 110 /** 111 * Stops the currently recorded program 112 */ stopRecording(final ScheduledRecording recording)113 public void stopRecording(final ScheduledRecording recording) { 114 synchronized (mListener) { 115 for (final Entry<Listener, Handler> entry : mListener.entrySet()) { 116 entry.getValue().post(new Runnable() { 117 @Override 118 public void run() { 119 entry.getKey().onStopRecordingRequested(recording); 120 } 121 }); 122 } 123 } 124 } 125 126 /** 127 * Removes a scheduled recording or an existing recording. 128 */ removeScheduledRecording(ScheduledRecording scheduledRecording)129 public void removeScheduledRecording(ScheduledRecording scheduledRecording) { 130 Log.i(TAG, "Removing " + scheduledRecording); 131 mDataManager.removeScheduledRecording(scheduledRecording); 132 } 133 removeRecordedProgram(final RecordedProgram recordedProgram)134 public void removeRecordedProgram(final RecordedProgram recordedProgram) { 135 // TODO(dvr): implement 136 Log.i(TAG, "To delete " + recordedProgram 137 + "\nyou should manually delete video data at" 138 + "\nadb shell rm -rf " + recordedProgram.getDataUri() 139 ); 140 Toast.makeText(mAppContext, "Deleting recorded programs is not fully implemented yet", 141 Toast.LENGTH_SHORT).show(); 142 new AsyncDbTask<Void, Void, Void>() { 143 @Override 144 protected Void doInBackground(Void... params) { 145 ContentResolver resolver = mAppContext.getContentResolver(); 146 resolver.delete(recordedProgram.getUri(), null, null); 147 return null; 148 } 149 }.execute(); 150 } 151 152 /** 153 * Returns priority ordered list of all scheduled recording that will not be recorded if 154 * this program is. 155 * 156 * <p>Any empty list means there is no conflicts. If there is conflict the program must be 157 * scheduled to record with a Priority lower than the first Recording in the list returned. 158 */ getScheduledRecordingsThatConflict(Program program)159 public List<ScheduledRecording> getScheduledRecordingsThatConflict(Program program) { 160 //TODO(DVR): move to scheduler. 161 //TODO(DVR): deal with more than one DvrInputService 162 List<ScheduledRecording> overLap = mDataManager.getRecordingsThatOverlapWith(getPeriod(program)); 163 if (!overLap.isEmpty()) { 164 // TODO(DVR): ignore shows that already won't record. 165 Channel channel = mChannelDataManager.getChannel(program.getChannelId()); 166 if (channel != null) { 167 TvInputInfo info = mDvrSessionManager.getTvInputInfo(channel.getInputId()); 168 if (info == null) { 169 Log.w(TAG, 170 "Could not find a recording TvInputInfo for " + channel.getInputId()); 171 return overLap; 172 } 173 int remove = Math.max(0, info.getTunerCount() - 1); 174 if (remove >= overLap.size()) { 175 return Collections.EMPTY_LIST; 176 } 177 overLap = overLap.subList(remove, overLap.size() - 1); 178 } 179 } 180 return overLap; 181 } 182 183 @NonNull getPeriod(Program program)184 private static Range getPeriod(Program program) { 185 return new Range(program.getStartTimeUtcMillis(), program.getEndTimeUtcMillis()); 186 } 187 188 /** 189 * Checks whether {@code channel} can be tuned without any conflict with existing recordings 190 * in progress. If there is any conflict, {@code outConflictRecordings} will be filled. 191 */ canTuneTo(Channel channel, List<ScheduledRecording> outConflictScheduledRecordings)192 public boolean canTuneTo(Channel channel, List<ScheduledRecording> outConflictScheduledRecordings) { 193 // TODO: implement 194 return true; 195 } 196 197 /** 198 * Returns true is the inputId supports recording. 199 */ canRecord(String inputId)200 public boolean canRecord(String inputId) { 201 TvInputInfo info = mDvrSessionManager.getTvInputInfo(inputId); 202 return info != null && info.getTunerCount() > 0; 203 } 204 205 @WorkerThread addListener(Listener listener, @NonNull Handler handler)206 void addListener(Listener listener, @NonNull Handler handler) { 207 SoftPreconditions.checkNotNull(handler); 208 synchronized (mListener) { 209 mListener.put(listener, handler); 210 } 211 } 212 213 @WorkerThread removeListener(Listener listener)214 void removeListener(Listener listener) { 215 synchronized (mListener) { 216 mListener.remove(listener); 217 } 218 } 219 220 /** 221 * Listener internally used inside dvr package. 222 */ 223 interface Listener { onStopRecordingRequested(ScheduledRecording scheduledRecording)224 void onStopRecordingRequested(ScheduledRecording scheduledRecording); 225 } 226 } 227