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.content.SharedPreferences;
     21 import android.hardware.radio.RadioManager;
     22 import android.os.AsyncTask;
     23 import android.os.SystemProperties;
     24 import android.support.annotation.NonNull;
     25 import android.support.annotation.WorkerThread;
     26 import android.util.Log;
     27 import com.android.car.radio.service.RadioStation;
     28 import com.android.car.radio.demo.DemoRadioStations;
     29 import com.android.car.radio.demo.RadioDemo;
     30 
     31 import java.util.ArrayList;
     32 import java.util.HashSet;
     33 import java.util.List;
     34 import java.util.Set;
     35 
     36 /**
     37  * Class that manages persistent storage of various radio options.
     38  */
     39 public class RadioStorage {
     40     private static final String TAG = "Em.RadioStorage";
     41     private static final String PREF_NAME = "com.android.car.radio.RadioStorage";
     42 
     43     // Keys used for storage in the SharedPreferences.
     44     private static final String PREF_KEY_RADIO_BAND = "radio_band";
     45     private static final String PREF_KEY_RADIO_CHANNEL_AM = "radio_channel_am";
     46     private static final String PREF_KEY_RADIO_CHANNEL_FM = "radio_channel_fm";
     47 
     48     public static final int INVALID_RADIO_CHANNEL = -1;
     49     public static final int INVALID_RADIO_BAND = -1;
     50 
     51     private static SharedPreferences sSharedPref;
     52     private static RadioStorage sInstance;
     53     private static RadioDatabase sRadioDatabase;
     54 
     55     /**
     56      * Listener that will be called when something in the radio storage changes.
     57      */
     58     public interface PresetsChangeListener {
     59         /**
     60          * Called when {@link #refreshPresets()} has completed.
     61          */
     62         void onPresetsRefreshed();
     63     }
     64 
     65     /**
     66      * Listener that will be called when something in the pre-scanned channels has changed.
     67      */
     68     public interface PreScannedChannelChangeListener {
     69         /**
     70          * Notifies that the pre-scanned channels for the given radio band has changed.
     71          *
     72          * @param radioBand One of the band values in {@link RadioManager}.
     73          */
     74         void onPreScannedChannelChange(int radioBand);
     75     }
     76 
     77     private Set<PresetsChangeListener> mPresetListeners = new HashSet<>();
     78 
     79     /**
     80      * Set of listeners that will be notified whenever pre-scanned channels have changed.
     81      *
     82      * <p>Note that this set is not initialized because pre-scanned channels are only needed if
     83      * dual-tuners exist in the current radio. Thus, this set is created conditionally.
     84      */
     85     private Set<PreScannedChannelChangeListener> mPreScannedListeners;
     86 
     87     private List<RadioStation> mPresets = new ArrayList<>();
     88 
     89     private RadioStorage(Context context) {
     90         if (sSharedPref == null) {
     91             sSharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
     92         }
     93 
     94         if (sRadioDatabase == null) {
     95             sRadioDatabase = new RadioDatabase(context);
     96         }
     97     }
     98 
     99     public static RadioStorage getInstance(Context context) {
    100         if (sInstance == null) {
    101             sInstance = new RadioStorage(context.getApplicationContext());
    102 
    103             // When the RadioStorage is first created, load the list of radio presets.
    104             sInstance.refreshPresets();
    105         }
    106 
    107         return sInstance;
    108     }
    109 
    110     /**
    111      * Registers the given {@link PresetsChangeListener} to be notified when any radio preset state
    112      * has changed.
    113      */
    114     public void addPresetsChangeListener(PresetsChangeListener listener) {
    115         mPresetListeners.add(listener);
    116     }
    117 
    118     /**
    119      * Unregisters the given {@link PresetsChangeListener}.
    120      */
    121     public void removePresetsChangeListener(PresetsChangeListener listener) {
    122         mPresetListeners.remove(listener);
    123     }
    124 
    125     /**
    126      * Registers the given {@link PreScannedChannelChangeListener} to be notified of changes to
    127      * pre-scanned channels.
    128      */
    129     public void addPreScannedChannelChangeListener(PreScannedChannelChangeListener listener) {
    130         if (mPreScannedListeners == null) {
    131             mPreScannedListeners = new HashSet<>();
    132         }
    133 
    134         mPreScannedListeners.add(listener);
    135     }
    136 
    137     /**
    138      * Unregisters the given {@link PreScannedChannelChangeListener}.
    139      */
    140     public void removePreScannedChannelChangeListener(PreScannedChannelChangeListener listener) {
    141         if (mPreScannedListeners == null) {
    142             return;
    143         }
    144 
    145         mPreScannedListeners.remove(listener);
    146     }
    147 
    148     /**
    149      * Requests a load of all currently stored presets. This operation runs asynchronously. When
    150      * the presets have been loaded, any registered {@link PresetsChangeListener}s are
    151      * notified via the {@link PresetsChangeListener#onPresetsRefreshed()} method.
    152      */
    153     private void refreshPresets() {
    154         new GetAllPresetsAsyncTask().execute();
    155     }
    156 
    157     /**
    158      * Returns all currently loaded presets. If there are no stored presets, this method will
    159      * return an empty {@link List}.
    160      *
    161      * <p>Register as a {@link PresetsChangeListener} to be notified of any changes in the
    162      * preset list.
    163      */
    164     public List<RadioStation> getPresets() {
    165         return mPresets;
    166     }
    167 
    168     /**
    169      * Convenience method for checking if a specific channel is a preset. This method will assume
    170      * the subchannel is 0.
    171      *
    172      * @see {@link #isPreset(RadioStation)}
    173      * @return {@code true} if the channel is a user saved preset.
    174      */
    175     public boolean isPreset(int channel, int radioBand) {
    176         return isPreset(new RadioStation(channel, 0 /* subchannel */, radioBand, null /* rds */));
    177     }
    178 
    179     /**
    180      * Returns {@code true} if the given {@link RadioStation} is a user saved preset.
    181      */
    182     public boolean isPreset(RadioStation station) {
    183         if (station == null) {
    184             return false;
    185         }
    186 
    187         // Just iterate through the list and match the station. If we anticipate this list growing
    188         // large, might have to change it to some sort of Set.
    189         for (RadioStation preset : mPresets) {
    190             if (preset.equals(station)) {
    191                 return true;
    192             }
    193         }
    194 
    195         return false;
    196     }
    197 
    198     /**
    199      * Stores that given {@link RadioStation} as a preset. This operation will override any
    200      * previously stored preset that matches the given preset.
    201      *
    202      * <p>Upon a successful store, the presets list will be refreshed via a call to
    203      * {@link #refreshPresets()}.
    204      *
    205      * @see {@link #refreshPresets()}
    206      */
    207     public void storePreset(RadioStation preset) {
    208         if (preset == null) {
    209             return;
    210         }
    211 
    212         new StorePresetAsyncTask().execute(preset);
    213     }
    214 
    215     /**
    216      * Removes the given {@link RadioStation} as a preset.
    217      *
    218      * <p>Upon a successful removal, the presets list will be refreshed via a call to
    219      * {@link #refreshPresets()}.
    220      *
    221      * @see {@link #refreshPresets()}
    222      */
    223     public void removePreset(RadioStation preset) {
    224         if (preset == null) {
    225             return;
    226         }
    227 
    228         new RemovePresetAsyncTask().execute(preset);
    229     }
    230 
    231     /**
    232      * Returns the stored radio band that was set in {@link #storeRadioBand(int)}. If a radio band
    233      * has not previously been stored, then {@link RadioManager#BAND_FM} is returned.
    234      *
    235      * @return One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
    236      * {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
    237      */
    238     public int getStoredRadioBand() {
    239         // No need to verify that the returned value is one of AM_BAND or FM_BAND because this is
    240         // done in storeRadioBand(int).
    241         return sSharedPref.getInt(PREF_KEY_RADIO_BAND, RadioManager.BAND_FM);
    242     }
    243 
    244     /**
    245      * Stores a radio band for later retrieval via {@link #getStoredRadioBand()}.
    246      */
    247     public void storeRadioBand(int radioBand) {
    248         // Ensure that an incorrect radio band is not stored. Currently only FM and AM supported.
    249         if (radioBand != RadioManager.BAND_FM && radioBand != RadioManager.BAND_AM) {
    250             return;
    251         }
    252 
    253         sSharedPref.edit().putInt(PREF_KEY_RADIO_BAND, radioBand).apply();
    254     }
    255 
    256     /**
    257      * Returns the stored radio channel that was set in {@link #storeRadioChannel(int, int)}. If a
    258      * radio channel for the given band has not been previously stored, then
    259      * {@link #INVALID_RADIO_CHANNEL} is returned.
    260      *
    261      * @param band One of the BAND_* values from {@link RadioManager}. For example,
    262      *             {@link RadioManager#BAND_AM}.
    263      */
    264     public int getStoredRadioChannel(int band) {
    265         switch (band) {
    266             case RadioManager.BAND_AM:
    267                 return sSharedPref.getInt(PREF_KEY_RADIO_CHANNEL_AM, INVALID_RADIO_CHANNEL);
    268 
    269             case RadioManager.BAND_FM:
    270                 return sSharedPref.getInt(PREF_KEY_RADIO_CHANNEL_FM, INVALID_RADIO_CHANNEL);
    271 
    272             default:
    273                 return INVALID_RADIO_CHANNEL;
    274         }
    275     }
    276 
    277     /**
    278      * Stores a radio channel (i.e. the radio frequency) for a particular band so it can be later
    279      * retrieved via {@link #getStoredRadioChannel(int band)}.
    280      */
    281     public void storeRadioChannel(int band, int channel) {
    282         if (Log.isLoggable(TAG, Log.DEBUG)) {
    283             Log.d(TAG, String.format("storeRadioChannel(); band: %s, channel %s", band, channel));
    284         }
    285 
    286         if (channel <= 0) {
    287             return;
    288         }
    289 
    290         switch (band) {
    291             case RadioManager.BAND_AM:
    292                 sSharedPref.edit().putInt(PREF_KEY_RADIO_CHANNEL_AM, channel).apply();
    293                 break;
    294 
    295             case RadioManager.BAND_FM:
    296                 sSharedPref.edit().putInt(PREF_KEY_RADIO_CHANNEL_FM, channel).apply();
    297                 break;
    298 
    299             default:
    300                 Log.w(TAG, "Attempting to store channel for invalid band: " + band);
    301         }
    302     }
    303 
    304     /**
    305      * Stores the list of {@link RadioStation}s as the pre-scanned stations for the given radio
    306      * band.
    307      *
    308      * @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
    309      * {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
    310      */
    311     public void storePreScannedStations(int radioBand, List<RadioStation> stations) {
    312         if (stations == null) {
    313             return;
    314         }
    315 
    316         new StorePreScannedAsyncTask(radioBand).execute(stations);
    317     }
    318 
    319     /**
    320      * Returns the list of pre-scanned radio channels for the given band.
    321      */
    322     @NonNull
    323     @WorkerThread
    324     public List<RadioStation> getPreScannedStationsForBand(int radioBand) {
    325         if (SystemProperties.getBoolean(RadioDemo.DEMO_MODE_PROPERTY, false)) {
    326             switch (radioBand) {
    327                 case RadioManager.BAND_AM:
    328                     return DemoRadioStations.getAmStations();
    329 
    330                 case RadioManager.BAND_FM:
    331                 default:
    332                     return DemoRadioStations.getFmStations();
    333 
    334             }
    335         }
    336 
    337         return sRadioDatabase.getAllPreScannedStationsForBand(radioBand);
    338     }
    339 
    340     /**
    341      * Calls {@link PresetsChangeListener#onPresetsRefreshed()} for all registered
    342      * {@link PresetsChangeListener}s.
    343      */
    344     private void notifyPresetsListeners() {
    345         for (PresetsChangeListener listener : mPresetListeners) {
    346             listener.onPresetsRefreshed();
    347         }
    348     }
    349 
    350     /**
    351      * Calls {@link PreScannedChannelChangeListener#onPreScannedChannelChange(int)} for all
    352      * registered {@link PreScannedChannelChangeListener}s.
    353      */
    354     private void notifyPreScannedListeners(int radioBand) {
    355         if (mPreScannedListeners == null) {
    356             return;
    357         }
    358 
    359         for (PreScannedChannelChangeListener listener : mPreScannedListeners) {
    360             listener.onPreScannedChannelChange(radioBand);
    361         }
    362     }
    363 
    364     /**
    365      * {@link AsyncTask} that will fetch all stored radio presets.
    366      */
    367     private class GetAllPresetsAsyncTask extends AsyncTask<Void, Void, Void> {
    368         private static final String TAG = "Em.GetAllPresetsAT";
    369 
    370         @Override
    371         protected Void doInBackground(Void... voids) {
    372             mPresets = sRadioDatabase.getAllPresets();
    373 
    374             if (Log.isLoggable(TAG, Log.DEBUG)) {
    375                 Log.d(TAG, "Loaded presets: " + mPresets);
    376             }
    377 
    378             return null;
    379         }
    380 
    381         @Override
    382         public void onPostExecute(Void result) {
    383             notifyPresetsListeners();
    384         }
    385     }
    386 
    387     /**
    388      * {@link AsyncTask} that will store a single {@link RadioStation} that is passed to its
    389      * {@link AsyncTask#execute(Object[])}.
    390      */
    391     private class StorePresetAsyncTask extends AsyncTask<RadioStation, Void, Boolean> {
    392         private static final String TAG = "Em.StorePresetAT";
    393 
    394         @Override
    395         protected Boolean doInBackground(RadioStation... radioStations) {
    396             RadioStation presetToStore = radioStations[0];
    397             boolean result = sRadioDatabase.insertPreset(presetToStore);
    398 
    399             if (Log.isLoggable(TAG, Log.DEBUG)) {
    400                 Log.d(TAG, "Store preset success: " + result);
    401             }
    402 
    403             if (result) {
    404                 // Refresh the presets list.
    405                 mPresets = sRadioDatabase.getAllPresets();
    406             }
    407 
    408             return result;
    409         }
    410 
    411         @Override
    412         public void onPostExecute(Boolean result) {
    413             if (result) {
    414                 notifyPresetsListeners();
    415             }
    416         }
    417     }
    418 
    419     /**
    420      * {@link AsyncTask} that will remove a single {@link RadioStation} that is passed to its
    421      * {@link AsyncTask#execute(Object[])}.
    422      */
    423     private class RemovePresetAsyncTask extends AsyncTask<RadioStation, Void, Boolean> {
    424         private static final String TAG = "Em.RemovePresetAT";
    425 
    426         @Override
    427         protected Boolean doInBackground(RadioStation... radioStations) {
    428             RadioStation presetToStore = radioStations[0];
    429             boolean result = sRadioDatabase.deletePreset(presetToStore);
    430 
    431             if (Log.isLoggable(TAG, Log.DEBUG)) {
    432                 Log.d(TAG, "Remove preset success: " + result);
    433             }
    434 
    435             if (result) {
    436                 // Refresh the presets list.
    437                 mPresets = sRadioDatabase.getAllPresets();
    438             }
    439 
    440             return result;
    441         }
    442 
    443         @Override
    444         public void onPostExecute(Boolean result) {
    445             if (result) {
    446                 notifyPresetsListeners();
    447             }
    448         }
    449     }
    450 
    451     /**
    452      * {@link AsyncTask} that will store a list of pre-scanned {@link RadioStation}s that is passed
    453      * to its {@link AsyncTask#execute(Object[])}.
    454      */
    455     private class StorePreScannedAsyncTask extends AsyncTask<List<RadioStation>, Void, Boolean> {
    456         private static final String TAG = "Em.StorePreScannedAT";
    457         private final int mRadioBand;
    458 
    459         public StorePreScannedAsyncTask(int radioBand) {
    460             mRadioBand = radioBand;
    461         }
    462 
    463         @Override
    464         protected Boolean doInBackground(List<RadioStation> ... stationsList) {
    465             List<RadioStation> stations = stationsList[0];
    466 
    467             boolean result = sRadioDatabase.insertPreScannedStations(mRadioBand, stations);
    468 
    469             if (Log.isLoggable(TAG, Log.DEBUG)) {
    470                 Log.d(TAG, "Store pre-scanned stations success: " + result);
    471             }
    472 
    473             return result;
    474         }
    475 
    476         @Override
    477         public void onPostExecute(Boolean result) {
    478             if (result) {
    479                 notifyPreScannedListeners(mRadioBand);
    480             }
    481         }
    482     }
    483 }
    484