1 /* 2 * Copyright (C) 2023 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.google.android.car.kitchensink.radio; 18 19 import static android.media.AudioAttributes.USAGE_MEDIA; 20 import static android.media.AudioManager.GET_DEVICES_INPUTS; 21 22 import android.annotation.IntDef; 23 import android.content.Context; 24 import android.hardware.radio.ProgramSelector; 25 import android.hardware.radio.RadioManager; 26 import android.hardware.radio.RadioTuner; 27 import android.media.AudioAttributes; 28 import android.media.AudioDeviceInfo; 29 import android.media.AudioManager; 30 import android.media.HwAudioSource; 31 import android.os.Bundle; 32 import android.os.Handler; 33 import android.os.Looper; 34 import android.util.Log; 35 import android.util.SparseArray; 36 import android.view.LayoutInflater; 37 import android.view.View; 38 import android.view.ViewGroup; 39 import android.widget.Button; 40 import android.widget.RadioButton; 41 import android.widget.RadioGroup; 42 import android.widget.TextView; 43 44 import androidx.fragment.app.Fragment; 45 import androidx.viewpager2.widget.ViewPager2; 46 47 import com.google.android.car.kitchensink.R; 48 import com.google.android.material.tabs.TabLayout; 49 import com.google.android.material.tabs.TabLayoutMediator; 50 51 import java.lang.annotation.Retention; 52 import java.lang.annotation.RetentionPolicy; 53 import java.util.ArrayList; 54 import java.util.List; 55 import java.util.Map; 56 57 public final class RadioTestFragment extends Fragment { 58 59 public static final String FRAGMENT_NAME = "Radio"; 60 61 private static final int TUNER_TYPE_INVALID = 0; 62 private static final int TUNER_TYPE_AM_FM = 1; 63 private static final int TUNER_TYPE_DAB = 2; 64 @IntDef(prefix = { "TUNER_TYPE_" }, value = { 65 TUNER_TYPE_INVALID , 66 TUNER_TYPE_AM_FM, 67 TUNER_TYPE_DAB, 68 }) 69 @Retention(RetentionPolicy.SOURCE) 70 private @interface TunerType {} 71 72 private static final String TAG = RadioTestFragment.class.getSimpleName(); 73 private static final int INVALID_MODULE_ID = -1; 74 75 private Handler mHandler; 76 private Context mContext; 77 78 private AudioAttributes mMusicAudioAttribute; 79 private HwAudioSource mHwAudioSource; 80 81 private RadioManager mRadioManager; 82 private RadioManager.BandConfig mFmBandConfig; 83 private RadioManager.BandConfig mAmBandConfig; 84 private SparseArray<String> mDabFrequencyToLabelMap; 85 private RadioTuner mFmAmRadioTuner; 86 private RadioTuner mDabRadioTuner; 87 88 private List<RadioManager.ModuleProperties> mModules; 89 private int mFirstAmFmModuleId = INVALID_MODULE_ID; 90 private int mFirstDabModuleId = INVALID_MODULE_ID; 91 92 private TextView mOpenTunerWarning; 93 private TextView mPlayingStatus; 94 private TabLayout mTunerTabLayout; 95 private TabLayoutMediator mTabLayoutMediator; 96 private ViewPager2 mTunerViewPager; 97 private RadioTunerTabAdapter mTunerTabAdapter; 98 99 @Override onCreate(Bundle savedInstanceState)100 public void onCreate(Bundle savedInstanceState) { 101 super.onCreate(savedInstanceState); 102 mContext = getContext(); 103 mHandler = new Handler(Looper.getMainLooper()); 104 } 105 106 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)107 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) { 108 Log.i(TAG, "onCreateView"); 109 View view = inflater.inflate(R.layout.radio, container, /* attachToRoot= */ false); 110 111 RadioGroup tunerTypeSelection = view.findViewById(R.id.selection_tuner_type); 112 RadioButton amFmTunerButton = view.findViewById(R.id.button_am_fm_type_tuner); 113 RadioButton dabTunerButton = view.findViewById(R.id.button_dab_type_tuner); 114 Button openTunerButton = view.findViewById(R.id.button_radio_open); 115 mOpenTunerWarning = view.findViewById(R.id.warning_open_tuner); 116 117 mTunerTabLayout = view.findViewById(R.id.tabs_tadio_tuner); 118 mTunerViewPager = view.findViewById(R.id.view_pager_radio_tuner); 119 mTunerTabAdapter = new RadioTunerTabAdapter(this); 120 mTunerViewPager.setAdapter(mTunerTabAdapter); 121 122 initializeRadioPlayer(); 123 View hwAudioSourceNotFound = view.findViewById(R.id.hw_audio_source_not_found); 124 View hwAudioSourceStart = view.findViewById(R.id.hw_audio_source_start); 125 View hwAudioSourceStop = view.findViewById(R.id.hw_audio_source_stop); 126 mPlayingStatus = view.findViewById(R.id.text_radio_playing_state); 127 if (mHwAudioSource == null) { 128 hwAudioSourceNotFound.setVisibility(View.VISIBLE); 129 hwAudioSourceStart.setVisibility(View.GONE); 130 hwAudioSourceStop.setVisibility(View.GONE); 131 } else { 132 hwAudioSourceNotFound.setVisibility(View.GONE); 133 hwAudioSourceStart.setVisibility(View.VISIBLE); 134 hwAudioSourceStop.setVisibility(View.VISIBLE); 135 view.findViewById(R.id.hw_audio_source_start).setOnClickListener( 136 v -> handleHwAudioSourceStart()); 137 view.findViewById(R.id.hw_audio_source_stop).setOnClickListener( 138 v -> handleHwAudioSourceStop()); 139 } 140 141 connectRadio(); 142 143 if (mFirstDabModuleId != INVALID_MODULE_ID) { 144 dabTunerButton.setVisibility(View.VISIBLE); 145 tunerTypeSelection.check(R.id.button_dab_type_tuner); 146 } 147 if (mFirstAmFmModuleId != INVALID_MODULE_ID) { 148 amFmTunerButton.setVisibility(View.VISIBLE); 149 tunerTypeSelection.check(R.id.button_am_fm_type_tuner); 150 } else if (mFirstDabModuleId == INVALID_MODULE_ID) { 151 openTunerButton.setVisibility(View.INVISIBLE); 152 } 153 154 openTunerButton.setOnClickListener(v -> { 155 mOpenTunerWarning.setText(getString(R.string.empty)); 156 int selectedButtonId = tunerTypeSelection.getCheckedRadioButtonId(); 157 String tabTitle; 158 RadioTunerFragment tunerFragment; 159 if (selectedButtonId == R.id.button_am_fm_type_tuner) { 160 tabTitle = getString(R.string.radio_fm_am_tuner); 161 if (mFmAmRadioTuner != null) { 162 mOpenTunerWarning.setText(getString(R.string.radio_error, 163 "FM/AM tuner exists, cannot open a new one")); 164 return; 165 } 166 tunerFragment = new AmFmTunerFragment(mRadioManager, 167 mModules.get(mFirstAmFmModuleId).getId(), mFmBandConfig, mAmBandConfig, 168 mHandler, new TunerListener(tabTitle, TUNER_TYPE_AM_FM)); 169 mFmAmRadioTuner = tunerFragment.getRadioTuner(); 170 if (mFmAmRadioTuner == null) { 171 mOpenTunerWarning.setText(getString(R.string.radio_error, 172 "Cannot open new AM/FM tuner")); 173 return; 174 } 175 } else if (selectedButtonId == R.id.button_dab_type_tuner) { 176 tabTitle = getString(R.string.radio_dab_tuner); 177 if (mDabRadioTuner != null) { 178 mOpenTunerWarning.setText(getString(R.string.radio_error, 179 "DAB tuner exists, cannot open a new one")); 180 return; 181 } 182 tunerFragment = new DabTunerFragment(mRadioManager, mDabFrequencyToLabelMap, 183 mModules.get(mFirstDabModuleId).getId(), mHandler, 184 new TunerListener(tabTitle, TUNER_TYPE_DAB)); 185 mDabRadioTuner = tunerFragment.getRadioTuner(); 186 if (mDabRadioTuner == null) { 187 mOpenTunerWarning.setText(getString(R.string.radio_error, 188 "Cannot open new DAB tuner")); 189 return; 190 } 191 } else { 192 return; 193 } 194 195 mTunerTabAdapter.addFragment(tunerFragment, tabTitle); 196 mTunerTabAdapter.notifyDataSetChanged(); 197 mTabLayoutMediator = new TabLayoutMediator(mTunerTabLayout, mTunerViewPager, 198 (tab, pos) -> tab.setText(mTunerTabAdapter.getPageTitle(pos))); 199 mTabLayoutMediator.attach(); 200 }); 201 202 return view; 203 } 204 205 @Override onDestroyView()206 public void onDestroyView() { 207 Log.i(TAG, "onDestroyView"); 208 super.onDestroyView(); 209 } 210 connectRadio()211 private void connectRadio() { 212 mRadioManager = mContext.getSystemService(RadioManager.class); 213 if (mRadioManager == null) { 214 mOpenTunerWarning.setText(getString(R.string.radio_error, 215 "RadioManager is not found")); 216 return; 217 } 218 219 mModules = new ArrayList<>(); 220 int status = mRadioManager.listModules(mModules); 221 if (status != RadioManager.STATUS_OK) { 222 mOpenTunerWarning.setText(getString(R.string.radio_error, 223 "Couldn't get radio module list")); 224 return; 225 } 226 227 if (mModules.size() == 0) { 228 mOpenTunerWarning.setText(getString(R.string.radio_error, 229 "No radio modules on this device")); 230 return; 231 } 232 233 RadioManager.AmBandDescriptor amBandDescriptor = null; 234 RadioManager.FmBandDescriptor fmBandDescriptor = null; 235 for (int moduleIndex = 0; moduleIndex < mModules.size(); moduleIndex++) { 236 RadioManager.ModuleProperties moduleProperties = mModules.get(moduleIndex); 237 if (mFirstAmFmModuleId == INVALID_MODULE_ID && (moduleProperties 238 .isProgramIdentifierSupported(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) 239 || moduleProperties.isProgramIdentifierSupported( 240 ProgramSelector.IDENTIFIER_TYPE_RDS_PI) 241 || moduleProperties.isProgramIdentifierSupported( 242 ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT))) { 243 for (RadioManager.BandDescriptor band : moduleProperties.getBands()) { 244 int bandType = band.getType(); 245 if (bandType == RadioManager.BAND_AM || bandType == RadioManager.BAND_AM_HD) { 246 amBandDescriptor = (RadioManager.AmBandDescriptor) band; 247 } 248 if (bandType == RadioManager.BAND_FM || bandType == RadioManager.BAND_FM_HD) { 249 fmBandDescriptor = (RadioManager.FmBandDescriptor) band; 250 } 251 } 252 if (amBandDescriptor != null && fmBandDescriptor != null) { 253 mFirstAmFmModuleId = moduleIndex; 254 mFmBandConfig = new RadioManager.FmBandConfig.Builder(fmBandDescriptor).build(); 255 mAmBandConfig = new RadioManager.AmBandConfig.Builder(amBandDescriptor).build(); 256 } 257 } 258 if (mFirstDabModuleId == INVALID_MODULE_ID 259 && (moduleProperties.isProgramIdentifierSupported( 260 ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) 261 || moduleProperties.isProgramIdentifierSupported( 262 ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT))) { 263 Map<String, Integer> dabFrequencyTable = moduleProperties.getDabFrequencyTable(); 264 if (dabFrequencyTable != null) { 265 mDabFrequencyToLabelMap = RadioTestFragmentUtils.getDabFrequencyToLabelMap( 266 dabFrequencyTable); 267 mFirstDabModuleId = moduleIndex; 268 } 269 } 270 } 271 } 272 initializeRadioPlayer()273 private void initializeRadioPlayer() { 274 mMusicAudioAttribute = new AudioAttributes.Builder().setUsage(USAGE_MEDIA).build(); 275 AudioDeviceInfo tuner = findTunerDevice(mContext); 276 if (tuner != null) { 277 mHwAudioSource = new HwAudioSource.Builder().setAudioAttributes(mMusicAudioAttribute) 278 .setAudioDeviceInfo(findTunerDevice(mContext)).build(); 279 } 280 } 281 findTunerDevice(Context context)282 private AudioDeviceInfo findTunerDevice(Context context) { 283 AudioManager am = context.getSystemService(AudioManager.class); 284 AudioDeviceInfo[] devices = am.getDevices(GET_DEVICES_INPUTS); 285 for (AudioDeviceInfo device : devices) { 286 if (device.getType() == AudioDeviceInfo.TYPE_FM_TUNER) { 287 return device; 288 } 289 } 290 return null; 291 } 292 handleHwAudioSourceStart()293 private void handleHwAudioSourceStart() { 294 if (mHwAudioSource != null && !mHwAudioSource.isPlaying()) { 295 mHwAudioSource.start(); 296 mPlayingStatus.setText(getString(R.string.radio_play)); 297 } 298 } 299 handleHwAudioSourceStop()300 private void handleHwAudioSourceStop() { 301 if (mHwAudioSource != null && mHwAudioSource.isPlaying()) { 302 mHwAudioSource.stop(); 303 mPlayingStatus.setText(getString(R.string.radio_stop)); 304 } 305 } 306 307 final class TunerListener { 308 private final String mTunerTabTitle; 309 @TunerType 310 private final int mTunerType; TunerListener(String tunerTabTitle, @TunerType int tunerType)311 TunerListener(String tunerTabTitle, @TunerType int tunerType) { 312 mTunerTabTitle = tunerTabTitle; 313 mTunerType = tunerType; 314 } onTunerClosed()315 public void onTunerClosed() { 316 if (mTunerTabAdapter != null) { 317 mTunerTabAdapter.removeFragment(mTunerTabTitle); 318 mTunerTabAdapter.notifyDataSetChanged(); 319 switch (mTunerType) { 320 case TUNER_TYPE_AM_FM: 321 mFmAmRadioTuner = null; 322 break; 323 case TUNER_TYPE_DAB: 324 mDabRadioTuner = null; 325 break; 326 default: 327 Log.e(TAG, "Unsupported tuner type " + mTunerType); 328 break; 329 } 330 handleHwAudioSourceStop(); 331 mOpenTunerWarning.setText(getString(R.string.empty)); 332 } 333 } 334 onTunerPlay()335 public void onTunerPlay() { 336 handleHwAudioSourceStart(); 337 } 338 } 339 } 340