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.car.radio; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorSet; 21 import android.animation.ObjectAnimator; 22 import android.content.Context; 23 import android.content.res.Resources; 24 import android.graphics.Canvas; 25 import android.os.Bundle; 26 import android.support.car.ui.PagedListView; 27 import android.support.v4.app.Fragment; 28 import android.support.v7.widget.RecyclerView; 29 import android.util.Log; 30 import android.view.LayoutInflater; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import com.android.car.radio.service.RadioStation; 34 35 import java.util.List; 36 37 /** 38 * A fragment that is responsible for displaying a list of the user's saved radio stations. 39 */ 40 public class RadioPresetsFragment extends Fragment implements 41 FragmentWithFade, 42 PresetsAdapter.OnPresetItemClickListener, 43 RadioAnimationManager.OnExitCompleteListener, 44 RadioController.RadioStationChangeListener, 45 RadioStorage.PresetsChangeListener { 46 private static final String TAG = "PresetsFragment"; 47 private static final int ANIM_DURATION_MS = 200; 48 49 private View mRootView; 50 private View mCurrentRadioCard; 51 52 private RadioStorage mRadioStorage; 53 private RadioController mRadioController; 54 private RadioAnimationManager mAnimManager; 55 56 private PresetListExitListener mPresetListExitListener; 57 58 private PagedListView mPresetsList; 59 private final PresetsAdapter mPresetsAdapter = new PresetsAdapter(); 60 61 /** 62 * An interface that will be notified when the user has indicated that they would like to 63 * exit the preset list. 64 */ 65 public interface PresetListExitListener { 66 /** 67 * Method to be called when the preset list should be closed and the main radio display 68 * should be shown. 69 */ 70 void OnPresetListExit(); 71 } 72 73 /** 74 * Sets the {@link RadioController} that is responsible for updating the UI of this fragment 75 * with the information of the current radio station. 76 */ 77 private void setRadioController(RadioController radioController) { 78 mRadioController = radioController; 79 } 80 81 /** 82 * Sets the lsitener that will be notified when the preset list should be closed. 83 */ 84 public void setPresetListExitListener(PresetListExitListener listener) { 85 mPresetListExitListener = listener; 86 } 87 88 @Override 89 public View onCreateView(LayoutInflater inflater, ViewGroup container, 90 Bundle savedInstanceState) { 91 mRootView = inflater.inflate(R.layout.radio_presets_list, container, false); 92 Context context = getContext(); 93 94 mPresetsAdapter.setOnPresetItemClickListener(this); 95 96 mCurrentRadioCard = mRootView.findViewById(R.id.current_radio_station_card); 97 98 mAnimManager = new RadioAnimationManager(getContext(), mRootView); 99 mAnimManager.playEnterAnimation(); 100 101 // Clicking on the current radio station card will close the activity and return to the 102 // main radio screen. 103 mCurrentRadioCard.setOnClickListener(v -> { 104 mAnimManager.playExitAnimation(RadioPresetsFragment.this /* listener */); 105 }); 106 107 mPresetsList = (PagedListView) mRootView.findViewById(R.id.presets_list); 108 mPresetsList.setLightMode(); 109 mPresetsList.setDefaultItemDecoration(new ItemSpacingDecoration(context)); 110 mPresetsList.setAdapter(mPresetsAdapter); 111 mPresetsList.getLayoutManager().setOffsetRows(false); 112 mPresetsList.getRecyclerView().addOnScrollListener(new PresetListScrollListener( 113 context, mRootView, mCurrentRadioCard, mPresetsList)); 114 115 mRadioStorage = RadioStorage.getInstance(context); 116 mRadioStorage.addPresetsChangeListener(this); 117 setPresetsOnList(mRadioStorage.getPresets()); 118 119 return mRootView; 120 } 121 122 @Override 123 public void fadeOutContent() { 124 ObjectAnimator containerAlphaAnimator = 125 ObjectAnimator.ofFloat(mCurrentRadioCard, View.ALPHA, 0f); 126 containerAlphaAnimator.setDuration(ANIM_DURATION_MS); 127 containerAlphaAnimator.addListener(new Animator.AnimatorListener() { 128 @Override 129 public void onAnimationStart(Animator animation) {} 130 131 @Override 132 public void onAnimationEnd(Animator animation) { 133 mCurrentRadioCard.setVisibility(View.GONE); 134 } 135 136 @Override 137 public void onAnimationCancel(Animator animation) {} 138 139 @Override 140 public void onAnimationRepeat(Animator animation) {} 141 }); 142 143 ObjectAnimator presetListAlphaAnimator = 144 ObjectAnimator.ofFloat(mPresetsList, View.ALPHA, 0f); 145 presetListAlphaAnimator.setDuration(ANIM_DURATION_MS); 146 presetListAlphaAnimator.addListener(new Animator.AnimatorListener() { 147 @Override 148 public void onAnimationStart(Animator animation) {} 149 150 @Override 151 public void onAnimationEnd(Animator animation) { 152 mPresetsList.setVisibility(View.GONE); 153 } 154 155 @Override 156 public void onAnimationCancel(Animator animation) {} 157 158 @Override 159 public void onAnimationRepeat(Animator animation) {} 160 }); 161 162 AnimatorSet animatorSet = new AnimatorSet(); 163 animatorSet.playTogether(containerAlphaAnimator, presetListAlphaAnimator); 164 animatorSet.start(); 165 } 166 167 @Override 168 public void fadeInContent() { 169 // Only the current radio card needs to be faded in as that is the only part 170 // of the fragment that will peek over the manual tuner. 171 ObjectAnimator containerAlphaAnimator = 172 ObjectAnimator.ofFloat(mCurrentRadioCard, View.ALPHA, 1f); 173 containerAlphaAnimator.setDuration(ANIM_DURATION_MS); 174 containerAlphaAnimator.addListener(new Animator.AnimatorListener() { 175 @Override 176 public void onAnimationStart(Animator animation) { 177 mCurrentRadioCard.setVisibility(View.VISIBLE); 178 } 179 180 @Override 181 public void onAnimationEnd(Animator animation) {} 182 183 @Override 184 public void onAnimationCancel(Animator animation) {} 185 186 @Override 187 public void onAnimationRepeat(Animator animation) {} 188 }); 189 190 ObjectAnimator presetListAlphaAnimator = 191 ObjectAnimator.ofFloat(mPresetsList, View.ALPHA, 1f); 192 presetListAlphaAnimator.setDuration(ANIM_DURATION_MS); 193 presetListAlphaAnimator.addListener(new Animator.AnimatorListener() { 194 @Override 195 public void onAnimationStart(Animator animation) { 196 mPresetsList.setVisibility(View.VISIBLE); 197 } 198 199 @Override 200 public void onAnimationEnd(Animator animation) {} 201 202 @Override 203 public void onAnimationCancel(Animator animation) {} 204 205 @Override 206 public void onAnimationRepeat(Animator animation) {} 207 }); 208 209 AnimatorSet animatorSet = new AnimatorSet(); 210 animatorSet.playTogether(containerAlphaAnimator, presetListAlphaAnimator); 211 animatorSet.start(); 212 } 213 214 @Override 215 public void onStart() { 216 super.onStart(); 217 mRadioController.initialize(mRootView); 218 mRadioController.setShouldColorStatusBar(true); 219 mRadioController.setRadioStationChangeListener(this); 220 221 mPresetsAdapter.setActiveRadioStation(mRadioController.getCurrentRadioStation()); 222 } 223 224 @Override 225 public void onDestroy() { 226 mRadioStorage.removePresetsChangeListener(this); 227 mPresetListExitListener = null; 228 super.onDestroy(); 229 } 230 231 @Override 232 public void onExitAnimationComplete() { 233 if (mPresetListExitListener != null) { 234 mPresetListExitListener.OnPresetListExit(); 235 } 236 } 237 238 @Override 239 public void onPresetItemClicked(RadioStation station) { 240 mRadioController.tuneToRadioChannel(station); 241 } 242 243 @Override 244 public void onRadioStationChanged(RadioStation station) { 245 if (Log.isLoggable(TAG, Log.DEBUG)) { 246 Log.d(TAG, "onRadioStationChanged(): " + station); 247 } 248 249 mPresetsAdapter.setActiveRadioStation(station); 250 } 251 252 @Override 253 public void onPresetsRefreshed() { 254 if (Log.isLoggable(TAG, Log.DEBUG)) { 255 Log.d(TAG, "onPresetsRefreshed()"); 256 } 257 258 setPresetsOnList(mRadioStorage.getPresets()); 259 } 260 261 /** 262 * Sets the given list of presets into the PagedListView {@link #mPresetsList}. 263 */ 264 private void setPresetsOnList(List<RadioStation> presets) { 265 if (Log.isLoggable(TAG, Log.DEBUG)) { 266 Log.d(TAG, "setPresetsOnList(). # of presets: " + presets.size()); 267 } 268 269 if (Log.isLoggable(TAG, Log.VERBOSE)) { 270 for (RadioStation radioStation : presets) { 271 Log.v(TAG, "\t" + radioStation); 272 } 273 } 274 275 mPresetsAdapter.setPresets(presets); 276 } 277 278 /** 279 * A {@link android.support.car.ui.PagedListView.Decoration} that draws a line between 280 * the items. 281 */ 282 public static class ItemSpacingDecoration extends PagedListView.Decoration { 283 private final int mLineStart; 284 285 public ItemSpacingDecoration(Context context) { 286 super(context); 287 Resources res = context.getResources(); 288 mLineStart = res.getDimensionPixelSize(R.dimen.stream_card_keyline_3); 289 } 290 291 @Override 292 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 293 View presetCard = parent.findViewById(R.id.preset_card); 294 295 if (presetCard == null) { 296 return; 297 } 298 299 int left = mLineStart + presetCard.getLeft(); 300 int right = presetCard.getRight(); 301 int childCount = parent.getChildCount(); 302 303 for (int i = 0; i < childCount; i++) { 304 View child = parent.getChildAt(i); 305 int bottom = child.getBottom(); 306 int top = bottom - mDividerHeight; 307 308 // Draw a divider line between each item. No need to draw the line for the last 309 // item. 310 if (i != childCount - 1) { 311 c.drawRect(left, top, right, bottom, mPaint); 312 } 313 } 314 } 315 } 316 317 /** 318 * Returns a new instance of the {@link RadioPresetsFragment}. 319 * 320 * @param radioController The {@link RadioController} that is responsible for updating the UI 321 * of the returned fragment. 322 */ 323 public static RadioPresetsFragment newInstance(RadioController radioController) { 324 RadioPresetsFragment fragment = new RadioPresetsFragment(); 325 fragment.setRadioController(radioController); 326 327 return fragment; 328 } 329 } 330