Home | History | Annotate | Download | only in radio
      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 
     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      */
     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
     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      */
    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
    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
    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
    200     public void onConfigurationChanged(RadioManager.BandConfig config) {
    201         if (config == null) {
    202             return;
    203         }
    204 
    205         mCurrentBand = config.getType();
    206     }
    207 }
    208