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.annotation.Nullable; 20 import android.content.Intent; 21 import android.hardware.radio.ProgramSelector; 22 import android.hardware.radio.RadioManager; 23 import android.os.Bundle; 24 import android.support.v4.app.Fragment; 25 import android.support.v4.app.FragmentManager; 26 import android.util.Log; 27 import android.util.Pair; 28 29 import androidx.car.drawer.CarDrawerActivity; 30 import androidx.car.drawer.CarDrawerAdapter; 31 import androidx.car.drawer.DrawerItemViewHolder; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * The main activity for the radio. This activity initializes the radio controls and listener for 38 * radio changes. 39 */ 40 public class CarRadioActivity extends CarDrawerActivity implements 41 RadioPresetsFragment.PresetListExitListener, 42 MainRadioFragment.RadioPresetListClickListener, 43 ManualTunerFragment.ManualTunerCompletionListener { 44 private static final String TAG = "Em.RadioActivity"; 45 private static final String MANUAL_TUNER_BACKSTACK = "MANUAL_TUNER_BACKSTACK"; 46 private static final String CONTENT_FRAGMENT_TAG = "CONTENT_FRAGMENT_TAG"; 47 48 private static final List<Pair<Integer, String>> SUPPORTED_RADIO_BANDS = new ArrayList<>(); 49 50 /** 51 * Intent action for notifying that the radio state has changed. 52 */ 53 private static final String ACTION_RADIO_APP_STATE_CHANGE 54 = "android.intent.action.RADIO_APP_STATE_CHANGE"; 55 56 /** 57 * Boolean Intent extra indicating if the radio is the currently in the foreground. 58 */ 59 private static final String EXTRA_RADIO_APP_FOREGROUND 60 = "android.intent.action.RADIO_APP_STATE"; 61 62 /** 63 * Whether or not it is safe to make transactions on the 64 * {@link android.support.v4.app.FragmentManager}. This variable prevents a possible exception 65 * when calling commit() on the FragmentManager. 66 * 67 * <p>The default value is {@code true} because it is only after 68 * {@link #onSaveInstanceState(Bundle)} has been called that fragment commits are not allowed. 69 */ 70 private boolean mAllowFragmentCommits = true; 71 72 private RadioController mRadioController; 73 74 @Override 75 protected void onCreate(Bundle savedInstanceState) { 76 SUPPORTED_RADIO_BANDS.add( 77 new Pair<>(RadioManager.BAND_AM, getString(R.string.radio_am_text))); 78 SUPPORTED_RADIO_BANDS.add( 79 new Pair<>(RadioManager.BAND_FM, getString(R.string.radio_fm_text))); 80 81 super.onCreate(savedInstanceState); 82 setToolbarElevation(0f); 83 84 mRadioController = new RadioController(this); 85 setContentFragment( 86 MainRadioFragment.newInstance(mRadioController, this /* clickListener */)); 87 88 } 89 90 @Override 91 protected CarDrawerAdapter getRootAdapter() { 92 return new RadioDrawerAdapter(); 93 } 94 95 @Override 96 public void onPresetListClicked() { 97 setContentFragment( 98 RadioPresetsFragment.newInstance(mRadioController, this /* existListener */)); 99 } 100 101 @Override 102 public void OnPresetListExit() { 103 setContentFragment( 104 MainRadioFragment.newInstance(mRadioController, this /* clickListener */)); 105 } 106 107 private void startManualTuner() { 108 if (!mAllowFragmentCommits || getSupportFragmentManager().getBackStackEntryCount() > 0) { 109 return; 110 } 111 112 Fragment currentFragment = getCurrentFragment(); 113 if (currentFragment instanceof FragmentWithFade) { 114 ((FragmentWithFade) currentFragment).fadeOutContent(); 115 } 116 117 ManualTunerFragment tunerFragment = 118 ManualTunerFragment.newInstance(mRadioController.getCurrentRadioBand()); 119 tunerFragment.setManualTunerCompletionListener(this); 120 121 getSupportFragmentManager().beginTransaction() 122 .setCustomAnimations(R.anim.slide_up, R.anim.slide_down, 123 R.anim.slide_up, R.anim.slide_down) 124 .add(getContentContainerId(), tunerFragment) 125 .addToBackStack(MANUAL_TUNER_BACKSTACK) 126 .commit(); 127 } 128 129 @Override 130 public void onStationSelected(ProgramSelector sel) { 131 maybeDismissManualTuner(); 132 133 Fragment fragment = getCurrentFragment(); 134 if (fragment instanceof FragmentWithFade) { 135 ((FragmentWithFade) fragment).fadeInContent(); 136 } 137 138 if (sel != null) { 139 mRadioController.tune(sel); 140 } 141 } 142 143 @Override 144 protected void onStart() { 145 super.onStart(); 146 147 if (Log.isLoggable(TAG, Log.DEBUG)) { 148 Log.d(TAG, "onStart"); 149 } 150 151 // Fragment commits are not allowed once the Activity's state has been saved. Once 152 // onStart() has been called, the FragmentManager should now allow commits. 153 mAllowFragmentCommits = true; 154 155 mRadioController.start(); 156 157 Intent broadcast = new Intent(ACTION_RADIO_APP_STATE_CHANGE); 158 broadcast.putExtra(EXTRA_RADIO_APP_FOREGROUND, true); 159 sendBroadcast(broadcast); 160 } 161 162 @Override 163 protected void onStop() { 164 super.onStop(); 165 166 if (Log.isLoggable(TAG, Log.DEBUG)) { 167 Log.d(TAG, "onStop"); 168 } 169 170 Intent broadcast = new Intent(ACTION_RADIO_APP_STATE_CHANGE); 171 broadcast.putExtra(EXTRA_RADIO_APP_FOREGROUND, false); 172 sendBroadcast(broadcast); 173 } 174 175 @Override 176 protected void onDestroy() { 177 super.onDestroy(); 178 179 if (Log.isLoggable(TAG, Log.DEBUG)) { 180 Log.d(TAG, "onDestroy"); 181 } 182 183 mRadioController.shutdown(); 184 } 185 186 @Override 187 public void onSaveInstanceState(Bundle outState) { 188 // A transaction can only be committed with this method prior to its containing activity 189 // saving its state. 190 mAllowFragmentCommits = false; 191 super.onSaveInstanceState(outState); 192 } 193 194 /** 195 * Checks if the manual tuner is currently being displayed. If it is, then dismiss it. 196 */ 197 private void maybeDismissManualTuner() { 198 FragmentManager fragmentManager = getSupportFragmentManager(); 199 if (fragmentManager.getBackStackEntryCount() > 0) { 200 // A station can only be selected if the manual tuner fragment has been shown; so, remove 201 // that here. 202 getSupportFragmentManager().popBackStack(); 203 } 204 } 205 206 private void setContentFragment(Fragment fragment) { 207 if (!mAllowFragmentCommits) { 208 return; 209 } 210 211 getSupportFragmentManager().beginTransaction() 212 .replace(getContentContainerId(), fragment, CONTENT_FRAGMENT_TAG) 213 .commitNow(); 214 } 215 216 /** 217 * Returns the fragment that is currently being displayed as the content view. Note that this 218 * is not necessarily the fragment that is visible. The manual tuner fragment can be displayed 219 * on top of this content fragment. 220 */ 221 @Nullable 222 private Fragment getCurrentFragment() { 223 return getSupportFragmentManager().findFragmentByTag(CONTENT_FRAGMENT_TAG); 224 } 225 226 /** 227 * An adapter that is responsible for populating the Radio drawer with the available bands to 228 * select, as well as the option for opening the manual tuner. 229 */ 230 private class RadioDrawerAdapter extends CarDrawerAdapter { 231 private final List<String> mDrawerOptions = 232 new ArrayList<>(SUPPORTED_RADIO_BANDS.size() + 1); 233 234 RadioDrawerAdapter() { 235 super(CarRadioActivity.this, false /* showDisabledListOnEmpty */); 236 setTitle(getString(R.string.app_name)); 237 // The ordering of options is hardcoded. The click handler below depends on it. 238 for (Pair<Integer, String> band : SUPPORTED_RADIO_BANDS) { 239 mDrawerOptions.add(band.second); 240 } 241 mDrawerOptions.add(getString(R.string.manual_tuner_drawer_entry)); 242 } 243 244 @Override 245 protected int getActualItemCount() { 246 return mDrawerOptions.size(); 247 } 248 249 @Override 250 public void populateViewHolder(DrawerItemViewHolder holder, int position) { 251 holder.getTitle().setText(mDrawerOptions.get(position)); 252 } 253 254 @Override 255 public void onItemClick(int position) { 256 getDrawerController().closeDrawer(); 257 if (position < SUPPORTED_RADIO_BANDS.size()) { 258 mRadioController.switchBand(SUPPORTED_RADIO_BANDS.get(position).first); 259 } else if (position == SUPPORTED_RADIO_BANDS.size()) { 260 startManualTuner(); 261 } else { 262 Log.w(TAG, "Unexpected position: " + position); 263 } 264 } 265 } 266 } 267