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.Context; 20 import android.hardware.radio.RadioManager; 21 import android.hardware.radio.RadioMetadata; 22 import android.hardware.radio.RadioTuner; 23 import android.support.annotation.Nullable; 24 import android.util.Log; 25 import com.android.car.radio.service.RadioRds; 26 import com.android.car.radio.service.RadioStation; 27 28 import java.util.ArrayList; 29 import java.util.List; 30 31 /** 32 * A class that will scan for valid radio stations in the background and store those stations into 33 * the radio database so that available stations for a particular radio band can be pre-populated 34 * for the user. 35 */ 36 public class RadioBackgroundScanner extends RadioTuner.Callback { 37 private static final String TAG = "Em.BackgroundScanner"; 38 private static final int INVALID_RADIO_CHANNEL = -1; 39 40 private RadioTuner mRadioTuner; 41 42 private final RadioManager mRadioManager; 43 private final RadioManager.AmBandConfig mAmConfig; 44 private final RadioManager.FmBandConfig mFmConfig; 45 private final RadioManager.ModuleProperties mRadioModule; 46 private final RadioStorage mRadioStorage; 47 48 private int mCurrentChannel; 49 private int mCurrentBand; 50 private int mStartingChannel = INVALID_RADIO_CHANNEL; 51 52 private List<RadioStation> mScannedStations = new ArrayList<>(); 53 RadioBackgroundScanner(Context context, RadioManager radioManager, RadioManager.AmBandConfig amConfig, RadioManager.FmBandConfig fmConfig, RadioManager.ModuleProperties module)54 public RadioBackgroundScanner(Context context, RadioManager radioManager, 55 RadioManager.AmBandConfig amConfig, RadioManager.FmBandConfig fmConfig, 56 RadioManager.ModuleProperties module) { 57 mRadioManager = radioManager; 58 mAmConfig = amConfig; 59 mFmConfig = fmConfig; 60 mRadioModule = module; 61 62 mRadioStorage = RadioStorage.getInstance(context); 63 } 64 65 /** 66 * Notify this {@link RadioBackgroundScanner} that the current radio band has changed and 67 * a new scan should start. 68 */ onRadioBandChanged(int radioBand)69 public void onRadioBandChanged(int radioBand) { 70 mStartingChannel = INVALID_RADIO_CHANNEL; 71 mCurrentBand = radioBand; 72 mScannedStations.clear(); 73 74 RadioManager.BandConfig config = getRadioConfig(radioBand); 75 76 if (config == null) { 77 Log.w(TAG, "Cannot create config for radio band: " + radioBand); 78 return; 79 } 80 81 if (mRadioTuner != null) { 82 mRadioTuner.setConfiguration(config); 83 } else { 84 mRadioTuner = mRadioManager.openTuner(mRadioModule.getId(), config, true, 85 this /* callback */, null /* handler */); 86 } 87 } 88 89 /** 90 * Returns the proper {@link android.hardware.radio.RadioManager.BandConfig} for the given 91 * radio band. {@code null} is returned if the band is not suppored. 92 */ 93 @Nullable getRadioConfig(int selectedRadioBand)94 private RadioManager.BandConfig getRadioConfig(int selectedRadioBand) { 95 switch (selectedRadioBand) { 96 case RadioManager.BAND_AM: 97 return mAmConfig; 98 case RadioManager.BAND_FM: 99 return mFmConfig; 100 101 // TODO: Support BAND_FM_HD and BAND_AM_HD. 102 103 default: 104 return null; 105 } 106 } 107 108 /** 109 * Replaces the given station in {@link #mScannedStations} or adds it to the end of the list if 110 * it does not exist. 111 */ addOrReplaceInScannedStations(RadioStation station)112 private void addOrReplaceInScannedStations(RadioStation station) { 113 int index = mScannedStations.indexOf(station); 114 115 if (Log.isLoggable(TAG, Log.VERBOSE)) { 116 Log.v(TAG, "Storing pre-scanned station: " + station); 117 } 118 119 if (index == -1) { 120 mScannedStations.add(station); 121 } else { 122 mScannedStations.set(index, station); 123 } 124 } 125 126 @Override onProgramInfoChanged(RadioManager.ProgramInfo info)127 public void onProgramInfoChanged(RadioManager.ProgramInfo info) { 128 if (Log.isLoggable(TAG, Log.VERBOSE)) { 129 Log.v(TAG, "onProgramInfoChanged(); info: " + info); 130 } 131 132 if (info == null) { 133 return; 134 } 135 136 mCurrentChannel = info.getChannel(); 137 138 if (mStartingChannel == INVALID_RADIO_CHANNEL) { 139 mStartingChannel = mCurrentChannel; 140 141 if (Log.isLoggable(TAG, Log.DEBUG)) { 142 Log.d(TAG, "Starting scan from channel: " + mStartingChannel); 143 } 144 } 145 146 // Stop scanning if we have looped back to the starting station. 147 if (mStartingChannel == mCurrentChannel) { 148 if (Log.isLoggable(TAG, Log.DEBUG)) { 149 Log.d(TAG, String.format("Looped back around to starting channel %s; storing " 150 + "%d pre-scanned stations", mStartingChannel, mScannedStations.size())); 151 } 152 153 if (Log.isLoggable(TAG, Log.VERBOSE)) { 154 for (RadioStation station : mScannedStations) { 155 Log.v(TAG, station.toString()); 156 } 157 } 158 159 mStartingChannel = INVALID_RADIO_CHANNEL; 160 161 // Close the RadioTuner so that this class no longer receives any callbacks and store 162 // all scanned statiosn into the database. 163 mRadioTuner.close(); 164 mRadioTuner = null; 165 mRadioStorage.storePreScannedStations(mCurrentBand, mScannedStations); 166 return; 167 } 168 169 RadioMetadata metadata = info.getMetadata(); 170 171 // If there is no metadata, then directly store the radio information into the database. 172 // Otherwise, onMetadataChanged() can handle the storage. 173 if (metadata != null) { 174 onMetadataChanged(metadata); 175 } else { 176 RadioStation station = new RadioStation(mCurrentChannel, 0 /* subChannelNumber */, 177 mCurrentBand, null /* rds */); 178 addOrReplaceInScannedStations(station); 179 } 180 181 // Initialize another seek to the next valid station. 182 mRadioTuner.scan(RadioTuner.DIRECTION_UP, true); 183 } 184 185 @Override onMetadataChanged(RadioMetadata metadata)186 public void onMetadataChanged(RadioMetadata metadata) { 187 if (metadata == null) { 188 return; 189 } 190 191 String stationInfo = metadata.getString(RadioMetadata.METADATA_KEY_RDS_PS); 192 RadioRds rds = new RadioRds(stationInfo, null /* songArtist */, null /* songTitle */); 193 194 RadioStation station = new RadioStation(mCurrentChannel, 0 /* subChannelNumber */, 195 mCurrentBand, rds); 196 addOrReplaceInScannedStations(station); 197 } 198 199 @Override onConfigurationChanged(RadioManager.BandConfig config)200 public void onConfigurationChanged(RadioManager.BandConfig config) { 201 if (config == null) { 202 return; 203 } 204 205 mCurrentBand = config.getType(); 206 } 207 } 208