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