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.annotation.NonNull; 23 import android.content.Context; 24 import android.hardware.radio.RadioManager.ProgramInfo; 25 import android.os.Bundle; 26 import android.support.v4.app.Fragment; 27 import android.util.Log; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 32 import androidx.car.widget.DayNightStyle; 33 import androidx.car.widget.PagedListView; 34 35 import com.android.car.broadcastradio.support.Program; 36 import com.android.car.radio.storage.RadioStorage; 37 38 import java.util.List; 39 40 /** 41 * A fragment that is responsible for displaying a list of the user's saved radio stations. 42 */ 43 public class RadioPresetsFragment extends Fragment implements 44 FragmentWithFade, 45 RadioAnimationManager.OnExitCompleteListener, 46 RadioController.ProgramInfoChangeListener, 47 RadioStorage.PresetsChangeListener { 48 private static final String TAG = "PresetsFragment"; 49 private static final int ANIM_DURATION_MS = 200; 50 51 private View mCurrentRadioCard; 52 53 private RadioStorage mRadioStorage; 54 private RadioController mRadioController; 55 private RadioAnimationManager mAnimManager; 56 57 private PresetListExitListener mPresetListExitListener; 58 59 private PagedListView mPresetsList; 60 private final PresetsAdapter mPresetsAdapter = new PresetsAdapter(); 61 62 /** 63 * An interface that will be notified when the user has indicated that they would like to 64 * exit the preset list. 65 */ 66 public interface PresetListExitListener { 67 /** 68 * Method to be called when the preset list should be closed and the main radio display 69 * should be shown. 70 */ OnPresetListExit()71 void OnPresetListExit(); 72 } 73 74 /** 75 * Sets the {@link RadioController} that is responsible for updating the UI of this fragment 76 * with the information of the current radio station. 77 */ setRadioController(RadioController radioController)78 private void setRadioController(RadioController radioController) { 79 mRadioController = radioController; 80 } 81 82 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)83 public View onCreateView(LayoutInflater inflater, ViewGroup container, 84 Bundle savedInstanceState) { 85 return inflater.inflate(R.layout.radio_presets_list, container, false); 86 } 87 88 @Override onViewCreated(View view, Bundle savedInstanceState)89 public void onViewCreated(View view, Bundle savedInstanceState) { 90 Context context = getContext(); 91 92 mPresetsAdapter.setOnPresetItemClickListener(mRadioController::tune); 93 mPresetsAdapter.setOnPresetItemFavoriteListener(this::handlePresetItemFavoriteChanged); 94 95 mCurrentRadioCard = view.findViewById(R.id.current_radio_station_card); 96 97 mAnimManager = new RadioAnimationManager(getContext(), view); 98 mAnimManager.playEnterAnimation(); 99 100 // Clicking on the current radio station card will close the activity and return to the 101 // main radio screen. 102 mCurrentRadioCard.setOnClickListener( 103 v -> mAnimManager.playExitAnimation(RadioPresetsFragment.this /* listener */)); 104 105 mPresetsList = view.findViewById(R.id.presets_list); 106 mPresetsList.setDayNightStyle(DayNightStyle.ALWAYS_LIGHT); 107 mPresetsList.setAdapter(mPresetsAdapter); 108 mPresetsList.getRecyclerView().addOnScrollListener(new PresetListScrollListener( 109 context, view, mCurrentRadioCard, mPresetsList)); 110 111 mRadioStorage = RadioStorage.getInstance(context); 112 mRadioStorage.addPresetsChangeListener(this); 113 setPresetsOnList(mRadioStorage.getPresets()); 114 } 115 116 @Override fadeOutContent()117 public void fadeOutContent() { 118 ObjectAnimator containerAlphaAnimator = 119 ObjectAnimator.ofFloat(mCurrentRadioCard, View.ALPHA, 0f); 120 containerAlphaAnimator.setDuration(ANIM_DURATION_MS); 121 containerAlphaAnimator.addListener(new Animator.AnimatorListener() { 122 @Override 123 public void onAnimationStart(Animator animation) {} 124 125 @Override 126 public void onAnimationEnd(Animator animation) { 127 mCurrentRadioCard.setVisibility(View.GONE); 128 } 129 130 @Override 131 public void onAnimationCancel(Animator animation) {} 132 133 @Override 134 public void onAnimationRepeat(Animator animation) {} 135 }); 136 137 ObjectAnimator presetListAlphaAnimator = 138 ObjectAnimator.ofFloat(mPresetsList, View.ALPHA, 0f); 139 presetListAlphaAnimator.setDuration(ANIM_DURATION_MS); 140 presetListAlphaAnimator.addListener(new Animator.AnimatorListener() { 141 @Override 142 public void onAnimationStart(Animator animation) {} 143 144 @Override 145 public void onAnimationEnd(Animator animation) { 146 mPresetsList.setVisibility(View.GONE); 147 } 148 149 @Override 150 public void onAnimationCancel(Animator animation) {} 151 152 @Override 153 public void onAnimationRepeat(Animator animation) {} 154 }); 155 156 AnimatorSet animatorSet = new AnimatorSet(); 157 animatorSet.playTogether(containerAlphaAnimator, presetListAlphaAnimator); 158 animatorSet.start(); 159 } 160 161 @Override fadeInContent()162 public void fadeInContent() { 163 // Only the current radio card needs to be faded in as that is the only part 164 // of the fragment that will peek over the manual tuner. 165 ObjectAnimator containerAlphaAnimator = 166 ObjectAnimator.ofFloat(mCurrentRadioCard, View.ALPHA, 1f); 167 containerAlphaAnimator.setDuration(ANIM_DURATION_MS); 168 containerAlphaAnimator.addListener(new Animator.AnimatorListener() { 169 @Override 170 public void onAnimationStart(Animator animation) { 171 mCurrentRadioCard.setVisibility(View.VISIBLE); 172 } 173 174 @Override 175 public void onAnimationEnd(Animator animation) {} 176 177 @Override 178 public void onAnimationCancel(Animator animation) {} 179 180 @Override 181 public void onAnimationRepeat(Animator animation) {} 182 }); 183 184 ObjectAnimator presetListAlphaAnimator = 185 ObjectAnimator.ofFloat(mPresetsList, View.ALPHA, 1f); 186 presetListAlphaAnimator.setDuration(ANIM_DURATION_MS); 187 presetListAlphaAnimator.addListener(new Animator.AnimatorListener() { 188 @Override 189 public void onAnimationStart(Animator animation) { 190 mPresetsList.setVisibility(View.VISIBLE); 191 } 192 193 @Override 194 public void onAnimationEnd(Animator animation) {} 195 196 @Override 197 public void onAnimationCancel(Animator animation) {} 198 199 @Override 200 public void onAnimationRepeat(Animator animation) {} 201 }); 202 203 AnimatorSet animatorSet = new AnimatorSet(); 204 animatorSet.playTogether(containerAlphaAnimator, presetListAlphaAnimator); 205 animatorSet.start(); 206 } 207 208 @Override onStart()209 public void onStart() { 210 super.onStart(); 211 mRadioController.initialize(getView()); 212 mRadioController.setShouldColorStatusBar(true); 213 mRadioController.addProgramInfoChangeListener(this); 214 215 // TODO(b/73950974): use callback only 216 ProgramInfo info = mRadioController.getCurrentProgramInfo(); 217 if (info != null) { 218 mPresetsAdapter.setActiveProgram(Program.fromProgramInfo(info)); 219 } 220 } 221 222 @Override onDestroy()223 public void onDestroy() { 224 mRadioStorage.removePresetsChangeListener(this); 225 mRadioController.removeProgramInfoChangeListener(this); 226 mPresetListExitListener = null; 227 super.onDestroy(); 228 } 229 230 @Override onExitAnimationComplete()231 public void onExitAnimationComplete() { 232 if (mPresetListExitListener != null) { 233 mPresetListExitListener.OnPresetListExit(); 234 } 235 } 236 237 238 @Override onProgramInfoChanged(@onNull ProgramInfo info)239 public void onProgramInfoChanged(@NonNull ProgramInfo info) { 240 mPresetsAdapter.setPresets(mRadioStorage.getPresets()); 241 mPresetsAdapter.setActiveProgram(Program.fromProgramInfo(info)); 242 } 243 244 @Override onPresetsRefreshed()245 public void onPresetsRefreshed() { 246 if (Log.isLoggable(TAG, Log.DEBUG)) { 247 Log.d(TAG, "onPresetsRefreshed()"); 248 } 249 } 250 handlePresetItemFavoriteChanged(Program program, boolean saveAsFavorite)251 private void handlePresetItemFavoriteChanged(Program program, boolean saveAsFavorite) { 252 if (saveAsFavorite) { 253 mRadioStorage.storePreset(program); 254 } else { 255 mRadioStorage.removePreset(program.getSelector()); 256 } 257 } 258 259 /** 260 * Sets the given list of presets into the PagedListView {@link #mPresetsList}. 261 */ setPresetsOnList(List<Program> presets)262 private void setPresetsOnList(List<Program> presets) { 263 if (Log.isLoggable(TAG, Log.DEBUG)) { 264 Log.d(TAG, "setPresetsOnList(). # of presets: " + presets.size()); 265 } 266 267 mPresetsAdapter.setPresets(presets); 268 } 269 270 /** 271 * Returns a new instance of the {@link RadioPresetsFragment}. 272 * 273 * @param radioController The {@link RadioController} that is responsible for updating the UI 274 * of the returned fragment. 275 */ newInstance(RadioController radioController, PresetListExitListener existListener)276 public static RadioPresetsFragment newInstance(RadioController radioController, 277 PresetListExitListener existListener) { 278 RadioPresetsFragment fragment = new RadioPresetsFragment(); 279 fragment.setRadioController(radioController); 280 fragment.mPresetListExitListener = existListener; 281 282 return fragment; 283 } 284 } 285