Home | History | Annotate | Download | only in dvr
      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 
     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      */
     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      */
     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      */
    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      */
    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      */
    129     public void removeScheduledRecording(ScheduledRecording scheduledRecording) {
    130         Log.i(TAG, "Removing " + scheduledRecording);
    131         mDataManager.removeScheduledRecording(scheduledRecording);
    132     }
    133 
    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      */
    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
    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      */
    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      */
    200     public boolean canRecord(String inputId) {
    201         TvInputInfo info = mDvrSessionManager.getTvInputInfo(inputId);
    202         return info != null && info.getTunerCount() > 0;
    203     }
    204 
    205     @WorkerThread
    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
    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 {
    224         void onStopRecordingRequested(ScheduledRecording scheduledRecording);
    225     }
    226 }
    227