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.content.Context;
     20 import android.media.tv.TvInputManager;
     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.text.TextUtils;
     27 import android.view.ViewGroup.LayoutParams;
     28 import android.widget.Toast;
     29 import com.android.tv.R;
     30 import com.android.tv.TvSingletons;
     31 import com.android.tv.common.SoftPreconditions;
     32 import com.android.tv.dvr.DvrDataManager;
     33 import com.android.tv.dvr.DvrManager;
     34 import com.android.tv.dvr.DvrWatchedPositionManager;
     35 import com.android.tv.dvr.data.RecordedProgram;
     36 import com.android.tv.dvr.data.SeriesRecording;
     37 import com.android.tv.ui.GuidedActionsStylistWithDivider;
     38 import com.android.tv.util.Utils;
     39 import java.util.ArrayList;
     40 import java.util.Collections;
     41 import java.util.HashSet;
     42 import java.util.List;
     43 import java.util.Set;
     44 import java.util.concurrent.TimeUnit;
     45 
     46 /** Fragment for DVR series recording settings. */
     47 public class DvrSeriesDeletionFragment extends GuidedStepFragment {
     48     private static final long WATCHED_TIME_UNIT_THRESHOLD = TimeUnit.MINUTES.toMillis(2);
     49 
     50     // Since recordings' IDs are used as its check actions' IDs, which are random positive numbers,
     51     // negative values are used by other actions to prevent duplicated IDs.
     52     private static final long ACTION_ID_SELECT_WATCHED = -110;
     53     private static final long ACTION_ID_SELECT_ALL = -111;
     54     private static final long ACTION_ID_DELETE = -112;
     55 
     56     private DvrDataManager mDvrDataManager;
     57     private DvrWatchedPositionManager mDvrWatchedPositionManager;
     58     private List<RecordedProgram> mRecordings;
     59     private final Set<Long> mWatchedRecordings = new HashSet<>();
     60     private boolean mAllSelected;
     61     private long mSeriesRecordingId;
     62     private int mOneLineActionHeight;
     63 
     64     @Override
     65     public void onAttach(Context context) {
     66         super.onAttach(context);
     67         mSeriesRecordingId =
     68                 getArguments().getLong(DvrSeriesDeletionActivity.SERIES_RECORDING_ID, -1);
     69         SoftPreconditions.checkArgument(mSeriesRecordingId != -1);
     70         mDvrDataManager = TvSingletons.getSingletons(context).getDvrDataManager();
     71         mDvrWatchedPositionManager =
     72                 TvSingletons.getSingletons(context).getDvrWatchedPositionManager();
     73         mRecordings = mDvrDataManager.getRecordedPrograms(mSeriesRecordingId);
     74         mOneLineActionHeight =
     75                 getResources()
     76                         .getDimensionPixelSize(
     77                                 R.dimen.dvr_settings_one_line_action_container_height);
     78         if (mRecordings.isEmpty()) {
     79             Toast.makeText(
     80                             getActivity(),
     81                             getString(R.string.dvr_series_deletion_no_recordings),
     82                             Toast.LENGTH_LONG)
     83                     .show();
     84             finishGuidedStepFragments();
     85             return;
     86         }
     87         Collections.sort(mRecordings, RecordedProgram.EPISODE_COMPARATOR);
     88     }
     89 
     90     @Override
     91     public Guidance onCreateGuidance(Bundle savedInstanceState) {
     92         String breadcrumb = null;
     93         SeriesRecording series = mDvrDataManager.getSeriesRecording(mSeriesRecordingId);
     94         if (series != null) {
     95             breadcrumb = series.getTitle();
     96         }
     97         return new Guidance(
     98                 getString(R.string.dvr_series_deletion_title),
     99                 getString(R.string.dvr_series_deletion_description),
    100                 breadcrumb,
    101                 null);
    102     }
    103 
    104     @Override
    105     public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
    106         actions.add(
    107                 new GuidedAction.Builder(getActivity())
    108                         .id(ACTION_ID_SELECT_WATCHED)
    109                         .title(getString(R.string.dvr_series_select_watched))
    110                         .build());
    111         actions.add(
    112                 new GuidedAction.Builder(getActivity())
    113                         .id(ACTION_ID_SELECT_ALL)
    114                         .title(getString(R.string.dvr_series_select_all))
    115                         .build());
    116         actions.add(GuidedActionsStylistWithDivider.createDividerAction(getContext()));
    117         for (RecordedProgram recording : mRecordings) {
    118             long watchedPositionMs =
    119                     mDvrWatchedPositionManager.getWatchedPosition(recording.getId());
    120             String title = recording.getEpisodeDisplayTitle(getContext());
    121             if (TextUtils.isEmpty(title)) {
    122                 title =
    123                         TextUtils.isEmpty(recording.getTitle())
    124                                 ? getString(R.string.channel_banner_no_title)
    125                                 : recording.getTitle();
    126             }
    127             String description;
    128             if (watchedPositionMs != TvInputManager.TIME_SHIFT_INVALID_TIME) {
    129                 description = getWatchedString(watchedPositionMs, recording.getDurationMillis());
    130                 mWatchedRecordings.add(recording.getId());
    131             } else {
    132                 description = getString(R.string.dvr_series_never_watched);
    133             }
    134             actions.add(
    135                     new GuidedAction.Builder(getActivity())
    136                             .id(recording.getId())
    137                             .title(title)
    138                             .description(description)
    139                             .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID)
    140                             .build());
    141         }
    142     }
    143 
    144     @Override
    145     public void onCreateButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
    146         actions.add(
    147                 new GuidedAction.Builder(getActivity())
    148                         .id(ACTION_ID_DELETE)
    149                         .title(getString(R.string.dvr_detail_delete))
    150                         .build());
    151         actions.add(
    152                 new GuidedAction.Builder(getActivity())
    153                         .clickAction(GuidedAction.ACTION_ID_CANCEL)
    154                         .build());
    155     }
    156 
    157     @Override
    158     public void onGuidedActionClicked(GuidedAction action) {
    159         long actionId = action.getId();
    160         if (actionId == ACTION_ID_DELETE) {
    161             List<Long> idsToDelete = new ArrayList<>();
    162             for (GuidedAction guidedAction : getActions()) {
    163                 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID
    164                         && guidedAction.isChecked()) {
    165                     idsToDelete.add(guidedAction.getId());
    166                 }
    167             }
    168             if (!idsToDelete.isEmpty()) {
    169                 DvrManager dvrManager = TvSingletons.getSingletons(getActivity()).getDvrManager();
    170                 dvrManager.removeRecordedPrograms(idsToDelete);
    171             }
    172             Toast.makeText(
    173                             getContext(),
    174                             getResources()
    175                                     .getQuantityString(
    176                                             R.plurals.dvr_msg_episodes_deleted,
    177                                             idsToDelete.size(),
    178                                             idsToDelete.size(),
    179                                             mRecordings.size()),
    180                             Toast.LENGTH_LONG)
    181                     .show();
    182             finishGuidedStepFragments();
    183         } else if (actionId == GuidedAction.ACTION_ID_CANCEL) {
    184             finishGuidedStepFragments();
    185         } else if (actionId == ACTION_ID_SELECT_WATCHED) {
    186             for (GuidedAction guidedAction : getActions()) {
    187                 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) {
    188                     long recordingId = guidedAction.getId();
    189                     if (mWatchedRecordings.contains(recordingId)) {
    190                         guidedAction.setChecked(true);
    191                     } else {
    192                         guidedAction.setChecked(false);
    193                     }
    194                     notifyActionChanged(findActionPositionById(recordingId));
    195                 }
    196             }
    197             mAllSelected = updateSelectAllState();
    198         } else if (actionId == ACTION_ID_SELECT_ALL) {
    199             mAllSelected = !mAllSelected;
    200             for (GuidedAction guidedAction : getActions()) {
    201                 if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) {
    202                     guidedAction.setChecked(mAllSelected);
    203                     notifyActionChanged(findActionPositionById(guidedAction.getId()));
    204                 }
    205             }
    206             updateSelectAllState(action, mAllSelected);
    207         } else {
    208             mAllSelected = updateSelectAllState();
    209         }
    210     }
    211 
    212     @Override
    213     public GuidedActionsStylist onCreateButtonActionsStylist() {
    214         return new DvrGuidedActionsStylist(true);
    215     }
    216 
    217     @Override
    218     public GuidedActionsStylist onCreateActionsStylist() {
    219         return new GuidedActionsStylistWithDivider() {
    220             @Override
    221             public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
    222                 super.onBindViewHolder(vh, action);
    223                 if (action.getId() == ACTION_DIVIDER) {
    224                     return;
    225                 }
    226                 LayoutParams lp = vh.itemView.getLayoutParams();
    227                 if (action.getCheckSetId() != GuidedAction.CHECKBOX_CHECK_SET_ID) {
    228                     lp.height = mOneLineActionHeight;
    229                 } else {
    230                     vh.itemView.setLayoutParams(
    231                             new LayoutParams(lp.width, LayoutParams.WRAP_CONTENT));
    232                 }
    233             }
    234         };
    235     }
    236 
    237     private String getWatchedString(long watchedPositionMs, long durationMs) {
    238         if (durationMs > WATCHED_TIME_UNIT_THRESHOLD) {
    239             return getResources()
    240                     .getString(
    241                             R.string.dvr_series_watched_info_minutes,
    242                             Math.max(1, Utils.getRoundOffMinsFromMs(watchedPositionMs)),
    243                             Utils.getRoundOffMinsFromMs(durationMs));
    244         } else {
    245             return getResources()
    246                     .getString(
    247                             R.string.dvr_series_watched_info_seconds,
    248                             Math.max(1, TimeUnit.MILLISECONDS.toSeconds(watchedPositionMs)),
    249                             TimeUnit.MILLISECONDS.toSeconds(durationMs));
    250         }
    251     }
    252 
    253     private boolean updateSelectAllState() {
    254         for (GuidedAction guidedAction : getActions()) {
    255             if (guidedAction.getCheckSetId() == GuidedAction.CHECKBOX_CHECK_SET_ID) {
    256                 if (!guidedAction.isChecked()) {
    257                     if (mAllSelected) {
    258                         updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), false);
    259                     }
    260                     return false;
    261                 }
    262             }
    263         }
    264         if (!mAllSelected) {
    265             updateSelectAllState(findActionById(ACTION_ID_SELECT_ALL), true);
    266         }
    267         return true;
    268     }
    269 
    270     private void updateSelectAllState(GuidedAction selectAll, boolean select) {
    271         selectAll.setTitle(
    272                 select
    273                         ? getString(R.string.dvr_series_deselect_all)
    274                         : getString(R.string.dvr_series_select_all));
    275         notifyActionChanged(findActionPositionById(ACTION_ID_SELECT_ALL));
    276     }
    277 }
    278