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; 18 19 import android.app.FragmentManager; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.support.v17.leanback.app.GuidedStepFragment; 23 import android.support.v17.leanback.widget.GuidanceStylist.Guidance; 24 import android.support.v17.leanback.widget.GuidedAction; 25 import android.support.v17.leanback.widget.GuidedActionsStylist; 26 import android.util.LongSparseArray; 27 28 import com.android.tv.R; 29 import com.android.tv.TvApplication; 30 import com.android.tv.data.Channel; 31 import com.android.tv.data.ChannelDataManager; 32 import com.android.tv.data.Program; 33 import com.android.tv.dvr.DvrDataManager; 34 import com.android.tv.dvr.DvrManager; 35 import com.android.tv.dvr.data.ScheduledRecording; 36 import com.android.tv.dvr.data.SeasonEpisodeNumber; 37 import com.android.tv.dvr.data.SeriesRecording; 38 import com.android.tv.dvr.data.SeriesRecording.ChannelOption; 39 import com.android.tv.dvr.recorder.SeriesRecordingScheduler; 40 41 import java.util.ArrayList; 42 import java.util.Collections; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Set; 46 47 /** 48 * Fragment for DVR series recording settings. 49 */ 50 public class DvrSeriesSettingsFragment extends GuidedStepFragment 51 implements DvrDataManager.SeriesRecordingListener { 52 private static final String TAG = "SeriesSettingsFragment"; 53 private static final boolean DEBUG = false; 54 55 private static final long ACTION_ID_PRIORITY = 10; 56 private static final long ACTION_ID_CHANNEL = 11; 57 58 private static final long SUB_ACTION_ID_CHANNEL_ALL = 102; 59 // Each channel's action id = SUB_ACTION_ID_CHANNEL_ONE_BASE + channel id 60 private static final long SUB_ACTION_ID_CHANNEL_ONE_BASE = 500; 61 62 private DvrDataManager mDvrDataManager; 63 private SeriesRecording mSeriesRecording; 64 private long mSeriesRecordingId; 65 @ChannelOption int mChannelOption; 66 private long mSelectedChannelId; 67 private int mBackStackCount; 68 private boolean mShowViewScheduleOptionInDialog; 69 private Program mCurrentProgram; 70 71 private String mFragmentTitle; 72 private String mProrityActionTitle; 73 private String mProrityActionHighestText; 74 private String mProrityActionLowestText; 75 private String mChannelsActionTitle; 76 private String mChannelsActionAllText; 77 private LongSparseArray<Channel> mId2Channel = new LongSparseArray<>(); 78 private List<Channel> mChannels = new ArrayList<>(); 79 private List<Program> mPrograms; 80 81 private GuidedAction mPriorityGuidedAction; 82 private GuidedAction mChannelsGuidedAction; 83 84 @Override 85 public void onAttach(Context context) { 86 super.onAttach(context); 87 mBackStackCount = getFragmentManager().getBackStackEntryCount(); 88 mDvrDataManager = TvApplication.getSingletons(context).getDvrDataManager(); 89 mSeriesRecordingId = getArguments().getLong(DvrSeriesSettingsActivity.SERIES_RECORDING_ID); 90 mSeriesRecording = mDvrDataManager.getSeriesRecording(mSeriesRecordingId); 91 if (mSeriesRecording == null) { 92 getActivity().finish(); 93 return; 94 } 95 mShowViewScheduleOptionInDialog = getArguments().getBoolean( 96 DvrSeriesSettingsActivity.SHOW_VIEW_SCHEDULE_OPTION_IN_DIALOG); 97 mCurrentProgram = getArguments().getParcelable(DvrSeriesSettingsActivity.CURRENT_PROGRAM); 98 mDvrDataManager.addSeriesRecordingListener(this); 99 mPrograms = (List<Program>) BigArguments.getArgument( 100 DvrSeriesSettingsActivity.PROGRAM_LIST); 101 BigArguments.reset(); 102 if (mPrograms == null) { 103 getActivity().finish(); 104 return; 105 } 106 Set<Long> channelIds = new HashSet<>(); 107 ChannelDataManager channelDataManager = 108 TvApplication.getSingletons(context).getChannelDataManager(); 109 for (Program program : mPrograms) { 110 long channelId = program.getChannelId(); 111 if (channelIds.add(channelId)) { 112 Channel channel = channelDataManager.getChannel(channelId); 113 if (channel != null) { 114 mId2Channel.put(channel.getId(), channel); 115 mChannels.add(channel); 116 } 117 } 118 } 119 mChannelOption = mSeriesRecording.getChannelOption(); 120 mSelectedChannelId = Channel.INVALID_ID; 121 if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE) { 122 Channel channel = channelDataManager.getChannel(mSeriesRecording.getChannelId()); 123 if (channel != null) { 124 mSelectedChannelId = channel.getId(); 125 } else { 126 mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; 127 } 128 } 129 mChannels.sort(Channel.CHANNEL_NUMBER_COMPARATOR); 130 mFragmentTitle = getString(R.string.dvr_series_settings_title); 131 mProrityActionTitle = getString(R.string.dvr_series_settings_priority); 132 mProrityActionHighestText = getString(R.string.dvr_series_settings_priority_highest); 133 mProrityActionLowestText = getString(R.string.dvr_series_settings_priority_lowest); 134 mChannelsActionTitle = getString(R.string.dvr_series_settings_channels); 135 mChannelsActionAllText = getString(R.string.dvr_series_settings_channels_all); 136 } 137 138 @Override 139 public void onResume() { 140 super.onResume(); 141 // To avoid the order of series's priority has changed, but series doesn't get update. 142 updatePriorityGuidedAction(); 143 } 144 145 @Override 146 public void onDetach() { 147 super.onDetach(); 148 mDvrDataManager.removeSeriesRecordingListener(this); 149 } 150 151 @Override 152 public void onDestroy() { 153 if (getFragmentManager().getBackStackEntryCount() == mBackStackCount && getArguments() 154 .getBoolean(DvrSeriesSettingsActivity.REMOVE_EMPTY_SERIES_RECORDING)) { 155 mDvrDataManager.checkAndRemoveEmptySeriesRecording(mSeriesRecordingId); 156 } 157 super.onDestroy(); 158 } 159 160 @Override 161 public Guidance onCreateGuidance(Bundle savedInstanceState) { 162 String breadcrumb = mSeriesRecording.getTitle(); 163 String title = mFragmentTitle; 164 return new Guidance(title, null, breadcrumb, null); 165 } 166 167 @Override 168 public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) { 169 mPriorityGuidedAction = new GuidedAction.Builder(getActivity()) 170 .id(ACTION_ID_PRIORITY) 171 .title(mProrityActionTitle) 172 .build(); 173 actions.add(mPriorityGuidedAction); 174 175 mChannelsGuidedAction = new GuidedAction.Builder(getActivity()) 176 .id(ACTION_ID_CHANNEL) 177 .title(mChannelsActionTitle) 178 .subActions(buildChannelSubAction()) 179 .build(); 180 actions.add(mChannelsGuidedAction); 181 updateChannelsGuidedAction(false); 182 } 183 184 @Override 185 public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) { 186 actions.add(new GuidedAction.Builder(getActivity()) 187 .clickAction(GuidedAction.ACTION_ID_OK) 188 .build()); 189 actions.add(new GuidedAction.Builder(getActivity()) 190 .clickAction(GuidedAction.ACTION_ID_CANCEL) 191 .build()); 192 } 193 194 @Override 195 public void onGuidedActionClicked(GuidedAction action) { 196 long actionId = action.getId(); 197 if (actionId == GuidedAction.ACTION_ID_OK) { 198 if (mChannelOption != mSeriesRecording.getChannelOption() 199 || mSeriesRecording.isStopped() 200 || (mChannelOption == SeriesRecording.OPTION_CHANNEL_ONE 201 && mSeriesRecording.getChannelId() != mSelectedChannelId)) { 202 SeriesRecording.Builder builder = SeriesRecording.buildFrom(mSeriesRecording) 203 .setChannelOption(mChannelOption) 204 .setState(SeriesRecording.STATE_SERIES_NORMAL); 205 if (mSelectedChannelId != Channel.INVALID_ID) { 206 builder.setChannelId(mSelectedChannelId); 207 } 208 DvrManager dvrManager = TvApplication.getSingletons(getContext()).getDvrManager(); 209 dvrManager.updateSeriesRecording(builder.build()); 210 if (mCurrentProgram != null && (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL 211 || mSelectedChannelId == mCurrentProgram.getChannelId())) { 212 dvrManager.addSchedule(mCurrentProgram); 213 } 214 updateSchedulesToSeries(); 215 showConfirmDialog(); 216 } else { 217 showConfirmDialog(); 218 } 219 } else if (actionId == GuidedAction.ACTION_ID_CANCEL) { 220 finishGuidedStepFragments(); 221 } else if (actionId == ACTION_ID_PRIORITY) { 222 FragmentManager fragmentManager = getFragmentManager(); 223 DvrPrioritySettingsFragment fragment = new DvrPrioritySettingsFragment(); 224 Bundle args = new Bundle(); 225 args.putLong(DvrPrioritySettingsFragment.COME_FROM_SERIES_RECORDING_ID, 226 mSeriesRecording.getId()); 227 fragment.setArguments(args); 228 GuidedStepFragment.add(fragmentManager, fragment, R.id.dvr_settings_view_frame); 229 } 230 } 231 232 @Override 233 public boolean onSubGuidedActionClicked(GuidedAction action) { 234 long actionId = action.getId(); 235 if (actionId == SUB_ACTION_ID_CHANNEL_ALL) { 236 mChannelOption = SeriesRecording.OPTION_CHANNEL_ALL; 237 mSelectedChannelId = Channel.INVALID_ID; 238 updateChannelsGuidedAction(true); 239 return true; 240 } else if (actionId > SUB_ACTION_ID_CHANNEL_ONE_BASE) { 241 mChannelOption = SeriesRecording.OPTION_CHANNEL_ONE; 242 mSelectedChannelId = actionId - SUB_ACTION_ID_CHANNEL_ONE_BASE; 243 updateChannelsGuidedAction(true); 244 return true; 245 } 246 return false; 247 } 248 249 @Override 250 public GuidedActionsStylist onCreateButtonActionsStylist() { 251 return new DvrGuidedActionsStylist(true); 252 } 253 254 private void updateChannelsGuidedAction(boolean notifyActionChanged) { 255 if (mChannelOption == SeriesRecording.OPTION_CHANNEL_ALL) { 256 mChannelsGuidedAction.setDescription(mChannelsActionAllText); 257 } else if (mId2Channel.get(mSelectedChannelId) != null){ 258 mChannelsGuidedAction.setDescription(mId2Channel.get(mSelectedChannelId) 259 .getDisplayText()); 260 } 261 if (notifyActionChanged) { 262 notifyActionChanged(findActionPositionById(ACTION_ID_CHANNEL)); 263 } 264 } 265 266 private void updatePriorityGuidedAction() { 267 int totalSeriesCount = 0; 268 int priorityOrder = 0; 269 for (SeriesRecording seriesRecording : mDvrDataManager.getSeriesRecordings()) { 270 if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL 271 || seriesRecording.getId() == mSeriesRecording.getId()) { 272 ++totalSeriesCount; 273 } 274 if (seriesRecording.getState() == SeriesRecording.STATE_SERIES_NORMAL 275 && seriesRecording.getId() != mSeriesRecording.getId() 276 && seriesRecording.getPriority() > mSeriesRecording.getPriority()) { 277 ++priorityOrder; 278 } 279 } 280 if (priorityOrder == 0) { 281 mPriorityGuidedAction.setDescription(mProrityActionHighestText); 282 } else if (priorityOrder >= totalSeriesCount - 1) { 283 mPriorityGuidedAction.setDescription(mProrityActionLowestText); 284 } else { 285 mPriorityGuidedAction.setDescription(getString( 286 R.string.dvr_series_settings_priority_rank, priorityOrder + 1)); 287 } 288 notifyActionChanged(findActionPositionById(ACTION_ID_PRIORITY)); 289 } 290 291 private void updateSchedulesToSeries() { 292 List<Program> recordingCandidates = new ArrayList<>(); 293 Set<SeasonEpisodeNumber> scheduledEpisodes = new HashSet<>(); 294 for (ScheduledRecording r : mDvrDataManager.getScheduledRecordings(mSeriesRecordingId)) { 295 if (r.getState() != ScheduledRecording.STATE_RECORDING_FAILED 296 && r.getState() != ScheduledRecording.STATE_RECORDING_CLIPPED) { 297 scheduledEpisodes.add(new SeasonEpisodeNumber( 298 r.getSeriesRecordingId(), r.getSeasonNumber(), r.getEpisodeNumber())); 299 } 300 } 301 for (Program program : mPrograms) { 302 // Removes current programs and scheduled episodes out, matches the channel option. 303 if (program.getStartTimeUtcMillis() >= System.currentTimeMillis() 304 && mSeriesRecording.matchProgram(program) 305 && !scheduledEpisodes.contains(new SeasonEpisodeNumber( 306 mSeriesRecordingId, program.getSeasonNumber(), program.getEpisodeNumber()))) { 307 recordingCandidates.add(program); 308 } 309 } 310 if (recordingCandidates.isEmpty()) { 311 return; 312 } 313 List<Program> programsToSchedule = SeriesRecordingScheduler.pickOneProgramPerEpisode( 314 mDvrDataManager, Collections.singletonList(mSeriesRecording), recordingCandidates) 315 .get(mSeriesRecordingId); 316 if (!programsToSchedule.isEmpty()) { 317 TvApplication.getSingletons(getContext()).getDvrManager() 318 .addScheduleToSeriesRecording(mSeriesRecording, programsToSchedule); 319 } 320 } 321 322 private List<GuidedAction> buildChannelSubAction() { 323 List<GuidedAction> channelSubActions = new ArrayList<>(); 324 channelSubActions.add(new GuidedAction.Builder(getActivity()) 325 .id(SUB_ACTION_ID_CHANNEL_ALL) 326 .title(mChannelsActionAllText) 327 .build()); 328 for (Channel channel : mChannels) { 329 channelSubActions.add(new GuidedAction.Builder(getActivity()) 330 .id(SUB_ACTION_ID_CHANNEL_ONE_BASE + channel.getId()) 331 .title(channel.getDisplayText()) 332 .build()); 333 } 334 return channelSubActions; 335 } 336 337 private void showConfirmDialog() { 338 DvrUiHelper.StartSeriesScheduledDialogActivity(getContext(), mSeriesRecording, 339 mShowViewScheduleOptionInDialog, mPrograms); 340 finishGuidedStepFragments(); 341 } 342 343 @Override 344 public void onSeriesRecordingAdded(SeriesRecording... seriesRecordings) { } 345 346 @Override 347 public void onSeriesRecordingRemoved(SeriesRecording... seriesRecordings) { 348 for (SeriesRecording series : seriesRecordings) { 349 if (series.getId() == mSeriesRecording.getId()) { 350 finishGuidedStepFragments(); 351 return; 352 } 353 } 354 } 355 356 @Override 357 public void onSeriesRecordingChanged(SeriesRecording... seriesRecordings) { 358 for (SeriesRecording seriesRecording : seriesRecordings) { 359 if (seriesRecording.getId() == mSeriesRecordingId) { 360 mSeriesRecording = seriesRecording; 361 updatePriorityGuidedAction(); 362 return; 363 } 364 } 365 } 366 }