Home | History | Annotate | Download | only in list
      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.dvr.ui.list;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.Context;
     21 import android.media.tv.TvInputInfo;
     22 import android.os.Build;
     23 import android.support.v17.leanback.widget.ClassPresenterSelector;
     24 import android.util.ArrayMap;
     25 import android.util.Log;
     26 
     27 import com.android.tv.ApplicationSingletons;
     28 import com.android.tv.R;
     29 import com.android.tv.TvApplication;
     30 import com.android.tv.common.SoftPreconditions;
     31 import com.android.tv.data.Program;
     32 import com.android.tv.dvr.DvrDataManager;
     33 import com.android.tv.dvr.DvrManager;
     34 import com.android.tv.dvr.data.ScheduledRecording;
     35 import com.android.tv.dvr.data.SeriesRecording;
     36 import com.android.tv.dvr.ui.list.SchedulesHeaderRow.SeriesRecordingHeaderRow;
     37 import com.android.tv.util.Utils;
     38 
     39 import java.util.ArrayList;
     40 import java.util.Collections;
     41 import java.util.Iterator;
     42 import java.util.List;
     43 import java.util.Map;
     44 
     45 /**
     46  * An adapter for series schedule row.
     47  */
     48 @TargetApi(Build.VERSION_CODES.N)
     49 class SeriesScheduleRowAdapter extends ScheduleRowAdapter {
     50     private static final String TAG = "SeriesRowAdapter";
     51     private static final boolean DEBUG = false;
     52 
     53     private final SeriesRecording mSeriesRecording;
     54     private final String mInputId;
     55     private final DvrManager mDvrManager;
     56     private final DvrDataManager mDataManager;
     57     private final Map<Long, Program> mPrograms = new ArrayMap<>();
     58     private SeriesRecordingHeaderRow mHeaderRow;
     59 
     60     public SeriesScheduleRowAdapter(Context context, ClassPresenterSelector classPresenterSelector,
     61             SeriesRecording seriesRecording) {
     62         super(context, classPresenterSelector);
     63         mSeriesRecording = seriesRecording;
     64         TvInputInfo input = Utils.getTvInputInfoForInputId(context, mSeriesRecording.getInputId());
     65         if (SoftPreconditions.checkNotNull(input) != null) {
     66             mInputId = input.getId();
     67         } else {
     68             mInputId = null;
     69         }
     70         ApplicationSingletons singletons = TvApplication.getSingletons(context);
     71         mDvrManager = singletons.getDvrManager();
     72         mDataManager = singletons.getDvrDataManager();
     73         setHasStableIds(true);
     74     }
     75 
     76     @Override
     77     public void start() {
     78         setPrograms(Collections.emptyList());
     79     }
     80 
     81     @Override
     82     public void stop() {
     83         super.stop();
     84     }
     85 
     86     /**
     87      * Sets the programs to show.
     88      */
     89     public void setPrograms(List<Program> programs) {
     90         if (programs == null) {
     91             programs = Collections.emptyList();
     92         }
     93         clear();
     94         mPrograms.clear();
     95         List<Program> sortedPrograms = new ArrayList<>(programs);
     96         Collections.sort(sortedPrograms);
     97         List<EpisodicProgramRow> rows = new ArrayList<>();
     98         mHeaderRow = new SeriesRecordingHeaderRow(mSeriesRecording.getTitle(),
     99                 null, sortedPrograms.size(), mSeriesRecording, programs);
    100         for (Program program : sortedPrograms) {
    101             ScheduledRecording schedule =
    102                     mDataManager.getScheduledRecordingForProgramId(program.getId());
    103             if (schedule != null && !willBeKept(schedule)) {
    104                 schedule = null;
    105             }
    106             rows.add(new EpisodicProgramRow(mInputId, program, schedule, mHeaderRow));
    107             mPrograms.put(program.getId(), program);
    108         }
    109         mHeaderRow.setDescription(getDescription());
    110         add(mHeaderRow);
    111         for (EpisodicProgramRow row : rows) {
    112             add(row);
    113         }
    114         sendNextUpdateMessage(System.currentTimeMillis());
    115     }
    116 
    117     private String getDescription() {
    118         int conflicts = 0;
    119         for (long programId : mPrograms.keySet()) {
    120             if (mDvrManager.isConflicting(
    121                     mDataManager.getScheduledRecordingForProgramId(programId))) {
    122                 ++conflicts;
    123             }
    124         }
    125         return conflicts == 0 ? null : getContext().getResources().getQuantityString(
    126                 R.plurals.dvr_series_schedules_header_description, conflicts, conflicts);
    127     }
    128 
    129     @Override
    130     public long getId(int position) {
    131         Object obj = get(position);
    132         if (obj instanceof EpisodicProgramRow) {
    133             return ((EpisodicProgramRow) obj).getProgram().getId();
    134         }
    135         if (obj instanceof SeriesRecordingHeaderRow) {
    136             return 0;
    137         }
    138         return super.getId(position);
    139     }
    140 
    141     @Override
    142     public void onScheduledRecordingAdded(ScheduledRecording schedule) {
    143         if (DEBUG) Log.d(TAG, "onScheduledRecordingAdded: " + schedule);
    144         int index = findRowIndexByProgramId(schedule.getProgramId());
    145         if (index != -1) {
    146             EpisodicProgramRow row = (EpisodicProgramRow) get(index);
    147             if (!row.isStartRecordingRequested()) {
    148                 setScheduleToRow(row, schedule);
    149                 notifyArrayItemRangeChanged(index, 1);
    150             }
    151         }
    152     }
    153 
    154     @Override
    155     public void onScheduledRecordingRemoved(ScheduledRecording schedule) {
    156         if (DEBUG) Log.d(TAG, "onScheduledRecordingRemoved: " + schedule);
    157         int index = findRowIndexByProgramId(schedule.getProgramId());
    158         if (index != -1) {
    159             EpisodicProgramRow row = (EpisodicProgramRow) get(index);
    160             row.setSchedule(null);
    161             notifyArrayItemRangeChanged(index, 1);
    162         }
    163     }
    164 
    165     @Override
    166     public void onScheduledRecordingUpdated(ScheduledRecording schedule, boolean conflictChange) {
    167         if (DEBUG) Log.d(TAG, "onScheduledRecordingUpdated: " + schedule);
    168         int index = findRowIndexByProgramId(schedule.getProgramId());
    169         if (index != -1) {
    170             EpisodicProgramRow row = (EpisodicProgramRow) get(index);
    171             if (conflictChange && isStartOrStopRequested()) {
    172                 // Delay the conflict update until it gets the response of the start/stop request.
    173                 // The purpose is to avoid the intermediate conflict change.
    174                 addPendingUpdate(row);
    175                 return;
    176             }
    177             if (row.isStopRecordingRequested()) {
    178                 // Wait until the recording is finished
    179                 if (schedule.getState() == ScheduledRecording.STATE_RECORDING_FINISHED
    180                         || schedule.getState() == ScheduledRecording.STATE_RECORDING_CLIPPED
    181                         || schedule.getState() == ScheduledRecording.STATE_RECORDING_FAILED) {
    182                     row.setStopRecordingRequested(false);
    183                     if (!isStartOrStopRequested()) {
    184                         executePendingUpdate();
    185                     }
    186                     row.setSchedule(null);
    187                 }
    188             } else if (row.isStartRecordingRequested()) {
    189                 // When the start recording was requested, we give the highest priority. So it is
    190                 // guaranteed that the state will be changed from NOT_STARTED to the other state.
    191                 // Update the row with the next state not to show the intermediate state to avoid
    192                 // blinking.
    193                 if (schedule.getState() != ScheduledRecording.STATE_RECORDING_NOT_STARTED) {
    194                     row.setStartRecordingRequested(false);
    195                     if (!isStartOrStopRequested()) {
    196                         executePendingUpdate();
    197                     }
    198                     setScheduleToRow(row, schedule);
    199                 }
    200             } else {
    201                 setScheduleToRow(row, schedule);
    202             }
    203             notifyArrayItemRangeChanged(index, 1);
    204         }
    205     }
    206 
    207     public void onSeriesRecordingUpdated(SeriesRecording seriesRecording) {
    208         if (seriesRecording.getId() == mSeriesRecording.getId()) {
    209             mHeaderRow.setSeriesRecording(seriesRecording);
    210             notifyArrayItemRangeChanged(0, 1);
    211         }
    212     }
    213 
    214     private void setScheduleToRow(ScheduleRow row, ScheduledRecording schedule) {
    215         if (schedule != null && willBeKept(schedule)) {
    216             row.setSchedule(schedule);
    217         } else {
    218             row.setSchedule(null);
    219         }
    220     }
    221 
    222     private int findRowIndexByProgramId(long programId) {
    223         for (int i = 0; i < size(); i++) {
    224             Object item = get(i);
    225             if (item instanceof EpisodicProgramRow) {
    226                 if (((EpisodicProgramRow) item).getProgram().getId() == programId) {
    227                     return i;
    228                 }
    229             }
    230         }
    231         return -1;
    232     }
    233 
    234     @Override
    235     public void notifyArrayItemRangeChanged(int positionStart, int itemCount) {
    236         mHeaderRow.setDescription(getDescription());
    237         super.notifyArrayItemRangeChanged(0, 1);
    238         super.notifyArrayItemRangeChanged(positionStart, itemCount);
    239     }
    240 
    241     @Override
    242     protected void handleUpdateRow(long currentTimeMs) {
    243         for (Iterator<Program> iter = mPrograms.values().iterator(); iter.hasNext(); ) {
    244             Program program = iter.next();
    245             if (program.getEndTimeUtcMillis() <= currentTimeMs) {
    246                 // Remove the old program.
    247                 removeItems(findRowIndexByProgramId(program.getId()), 1);
    248                 iter.remove();
    249             } else if (program.getStartTimeUtcMillis() < currentTimeMs) {
    250                 // Change the button "START RECORDING"
    251                 notifyItemRangeChanged(findRowIndexByProgramId(program.getId()), 1);
    252             }
    253         }
    254     }
    255 
    256     /**
    257      * Should take the current time argument which is the time when the programs are checked in
    258      * handler.
    259      */
    260     @Override
    261     protected long getNextTimerMs(long currentTimeMs) {
    262         long earliest = Long.MAX_VALUE;
    263         for (Program program : mPrograms.values()) {
    264             if (earliest > program.getStartTimeUtcMillis()
    265                     && program.getStartTimeUtcMillis() >= currentTimeMs) {
    266                 // Need the button from "CREATE SCHEDULE" to "START RECORDING"
    267                 earliest = program.getStartTimeUtcMillis();
    268             } else if (earliest > program.getEndTimeUtcMillis()) {
    269                 // Need to remove the row.
    270                 earliest = program.getEndTimeUtcMillis();
    271             }
    272         }
    273         return earliest;
    274     }
    275 }
    276