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