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