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 android.hardware.radio.Flags;
20 import android.hardware.radio.ProgramSelector;
21 import android.hardware.radio.RadioManager;
22 import android.hardware.radio.RadioTuner;
23 import android.os.Handler;
24 import android.view.View;
25 import android.widget.Button;
26 import android.widget.EditText;
27 import android.widget.RadioButton;
28 import android.widget.RadioGroup;
29 import android.widget.Switch;
30 
31 import com.android.car.broadcastradio.support.platform.ProgramSelectorExt;
32 
33 import com.google.android.car.kitchensink.R;
34 
35 public final class AmFmTunerFragment extends RadioTunerFragment {
36 
37     private int mLastFmFrequency;
38     private int mLastAmFrequency;
39     private boolean mIsFmBand = true;
40 
41     private RadioButton mFmRadioButton;
42     private RadioButton mAmRadioButton;
43     private Switch mFmHdSwitch;
44     private Switch mAmHdSwitch;
45     private EditText mFrequencyInput;
46     private RadioGroup mAmFmBandSelection;
47 
AmFmTunerFragment(RadioManager radioManager, int moduleId, RadioManager.BandConfig fmBandConfig, RadioManager.BandConfig amBandConfig, Handler handler, RadioTestFragment.TunerListener tunerListener)48     AmFmTunerFragment(RadioManager radioManager, int moduleId,
49                       RadioManager.BandConfig fmBandConfig, RadioManager.BandConfig amBandConfig,
50                       Handler handler, RadioTestFragment.TunerListener tunerListener) {
51         super(radioManager, moduleId, handler, tunerListener);
52         mLastFmFrequency = fmBandConfig.getLowerLimit();
53         mLastAmFrequency = amBandConfig.getLowerLimit();
54     }
55 
56     @Override
setupTunerView(View view)57     void setupTunerView(View view) {
58         mFmHdSwitch = view.findViewById(R.id.toggle_fm_hd_state);
59         mAmHdSwitch = view.findViewById(R.id.toggle_am_hd_state);
60         mFrequencyInput = view.findViewById(R.id.input_am_fm_frequency);
61         mAmFmBandSelection = view.findViewById(R.id.button_fm_am_selection);
62         mFmRadioButton = view.findViewById(R.id.button_radio_fm);
63         mAmRadioButton = view.findViewById(R.id.button_radio_am);
64         Button tuneButton = view.findViewById(R.id.button_radio_tune);
65         Button stepUpButton = view.findViewById(R.id.button_radio_step_up);
66         Button stepDownButton = view.findViewById(R.id.button_radio_step_down);
67 
68         mFmHdSwitch.setVisibility(View.VISIBLE);
69         view.findViewById(R.id.text_fm_hd_state).setVisibility(View.VISIBLE);
70         view.findViewById(R.id.layout_tune).setVisibility(View.VISIBLE);
71         view.findViewById(R.id.layout_step).setVisibility(View.VISIBLE);
72 
73         if (Flags.hdRadioImproved()) {
74             if (mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_AM)) {
75                 mAmHdSwitch.setVisibility(View.VISIBLE);
76                 view.findViewById(R.id.text_am_hd_state).setVisibility(View.VISIBLE);
77                 mAmHdSwitch.setChecked(!mRadioTuner.isConfigFlagSet(
78                         RadioManager.CONFIG_FORCE_ANALOG_AM));
79             }
80             if (!mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG_FM)) {
81                 mFmHdSwitch.setVisibility(View.INVISIBLE);
82                 view.findViewById(R.id.text_fm_hd_state).setVisibility(View.INVISIBLE);
83             } else {
84                 mFmHdSwitch.setChecked(!mRadioTuner.isConfigFlagSet(
85                         RadioManager.CONFIG_FORCE_ANALOG_FM));
86             }
87         } else {
88             if (!mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_FORCE_ANALOG)) {
89                 mFmHdSwitch.setVisibility(View.INVISIBLE);
90                 view.findViewById(R.id.text_fm_hd_state).setVisibility(View.INVISIBLE);
91             } else {
92                 mFmHdSwitch.setChecked(!mRadioTuner.isConfigFlagSet(
93                         RadioManager.CONFIG_FORCE_ANALOG));
94             }
95         }
96         mFmHdSwitch.setOnCheckedChangeListener(
97                 (buttonView, isChecked) -> handleHdEnable(/* isFm= */ true, isChecked));
98         mAmHdSwitch.setOnCheckedChangeListener(
99                 (buttonView, isChecked) -> handleHdEnable(/* isFm= */ false, isChecked));
100         tuneButton.setOnClickListener((v) -> tuneToInputStation());
101         mFmRadioButton.setOnClickListener((v) -> handleBandSwitching(/* isFm= */ true));
102         mAmRadioButton.setOnClickListener((v) -> handleBandSwitching(/* isFm= */ false));
103         if (mIsFmBand) {
104             mFmRadioButton.setChecked(true);
105             mFrequencyInput.setText(ProgramSelectorExt.formatAmFmFrequency(mLastFmFrequency,
106                     ProgramSelectorExt.NAME_NO_MODULATION));
107         } else {
108             mAmRadioButton.setChecked(true);
109             mFrequencyInput.setText(ProgramSelectorExt.formatAmFmFrequency(mLastAmFrequency,
110                     ProgramSelectorExt.NAME_NO_MODULATION));
111         }
112         stepUpButton.setOnClickListener((v) -> handleStep(RadioTuner.DIRECTION_UP));
113         stepDownButton.setOnClickListener((v) -> handleStep(RadioTuner.DIRECTION_DOWN));
114 
115         mProgramInfoAdapter = new AmFmProgramInfoAdapter(getContext(), R.layout.program_info_item,
116                 new RadioManager.ProgramInfo[]{}, this);
117     }
118 
tuneToInputStation()119     private void tuneToInputStation() {
120         int selectedButtonId = mAmFmBandSelection.getCheckedRadioButtonId();
121         ProgramSelector sel;
122         try {
123             double frequencyInput = Double.parseDouble(mFrequencyInput.getText().toString());
124             switch (selectedButtonId) {
125                 case R.id.button_radio_fm:
126                     int fmFrequency = (int) Math.round(frequencyInput * 1_000);
127                     sel = ProgramSelector.createAmFmSelector(RadioManager.BAND_FM, fmFrequency);
128                     break;
129                 case R.id.button_radio_am:
130                     int amFrequency = (int) Math.round(frequencyInput);
131                     sel = ProgramSelector.createAmFmSelector(RadioManager.BAND_AM, amFrequency);
132                     break;
133                 default:
134                     mTuningTextView.setText(getString(R.string.radio_error,
135                             "Unsupported input selector type"));
136                     return;
137             }
138         } catch (Exception e) {
139             mTuningTextView.setText(getString(R.string.radio_error, e.getMessage()));
140             return;
141         }
142         handleTune(sel);
143     }
144 
handleBandSwitching(boolean isFm)145     private void handleBandSwitching(boolean isFm) {
146         if (isFm) {
147             mFrequencyInput.setText(ProgramSelectorExt.formatAmFmFrequency(mLastFmFrequency,
148                     ProgramSelectorExt.NAME_NO_MODULATION));
149         } else {
150             mFrequencyInput.setText(ProgramSelectorExt.formatAmFmFrequency(mLastAmFrequency,
151                     ProgramSelectorExt.NAME_NO_MODULATION));
152         }
153         mIsFmBand = isFm;
154         ProgramSelector initialSel = isFm ? ProgramSelector.createAmFmSelector(
155                 RadioManager.BAND_FM, mLastFmFrequency)
156                 : ProgramSelector.createAmFmSelector(RadioManager.BAND_AM, mLastAmFrequency);
157         handleTune(initialSel);
158     }
159 
handleStep(int direction)160     private void handleStep(int direction) {
161         if (mRadioTuner == null) {
162             mTuningTextView.setText(getString(R.string.radio_error, NULL_TUNER_WARNING));
163             return;
164         }
165         mTuningTextView.setText(getString(R.string.radio_status, TUNING_TEXT));
166         try {
167             mRadioTuner.step(direction, /* skipSubChannel= */ false);
168         } catch (Exception e) {
169             mTuningTextView.setText(getString(R.string.radio_error, e.getMessage()));
170         }
171         mListener.onTunerPlay();
172     }
173 
handleHdEnable(boolean isFm, boolean hdEnabled)174     private void handleHdEnable(boolean isFm, boolean hdEnabled) {
175         if (mRadioTuner == null) {
176             mTuningTextView.setText(getString(R.string.radio_error, NULL_TUNER_WARNING));
177             return;
178         }
179         mTuningTextView.setText(getString(R.string.empty));
180         int configFlag;
181         if (Flags.hdRadioImproved()) {
182             configFlag = isFm ? RadioManager.CONFIG_FORCE_ANALOG_FM
183                     : RadioManager.CONFIG_FORCE_ANALOG_AM;
184         } else {
185             configFlag = RadioManager.CONFIG_FORCE_ANALOG;
186         }
187         try {
188             mRadioTuner.setConfigFlag(configFlag, !hdEnabled);
189         } catch (Exception e) {
190             mTuningTextView.setText(getString(R.string.radio_error, e.getMessage()));
191         }
192     }
193 
194     @Override
getChannelName(RadioManager.ProgramInfo info)195     CharSequence getChannelName(RadioManager.ProgramInfo info) {
196         CharSequence channelText = null;
197         if (info != null) {
198             int primaryIdType = info.getSelector().getPrimaryId().getType();
199             if (primaryIdType == ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY
200                     || primaryIdType == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
201                 channelText = ProgramSelectorExt.getDisplayName(info.getSelector(), /* flags= */ 0);
202                 long amFmFrequency = ProgramSelectorExt.getFrequency(info.getSelector());
203                 mIsFmBand = ProgramSelectorExt.isFmFrequency(amFmFrequency);
204                 if (mIsFmBand) {
205                     mLastFmFrequency = (int) amFmFrequency;
206                 } else if (ProgramSelectorExt.isAmFrequency(amFmFrequency)) {
207                     mLastAmFrequency = (int) amFmFrequency;
208                 }
209             }
210         }
211         if (mViewCreated) {
212             if (channelText == null) {
213                 channelText = getString(R.string.radio_na);
214             }
215             if (mIsFmBand) {
216                 if (!mFmRadioButton.isChecked()) {
217                     mFmRadioButton.setChecked(true);
218                 }
219             } else {
220                 if (!mAmRadioButton.isChecked()) {
221                     mAmRadioButton.setChecked(true);
222                 }
223             }
224         }
225         return channelText;
226     }
227 
228     @Override
updateConfigFlag(int flag, boolean value)229     void updateConfigFlag(int flag, boolean value) {
230         if (flag == RadioManager.CONFIG_FORCE_ANALOG) {
231             mFmHdSwitch.setChecked(!value);
232             mAmHdSwitch.setChecked(!value);
233         } else if (Flags.hdRadioImproved()) {
234             if (flag == RadioManager.CONFIG_FORCE_ANALOG_FM) {
235                 mFmHdSwitch.setChecked(!value);
236             } else if (flag == RadioManager.CONFIG_FORCE_ANALOG_AM) {
237                 mAmHdSwitch.setChecked(!value);
238             }
239         }
240     }
241 }
242