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