Home | History | Annotate | Download | only in ui
      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 }