Home | History | Annotate | Download | only in storage
      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.storage;
     18 
     19 import android.annotation.NonNull;
     20 import android.arch.lifecycle.LiveData;
     21 import android.arch.lifecycle.Observer;
     22 import android.content.Context;
     23 import android.content.SharedPreferences;
     24 import android.hardware.radio.ProgramSelector;
     25 import android.hardware.radio.RadioManager;
     26 import android.os.AsyncTask;
     27 import android.util.Log;
     28 
     29 import com.android.car.broadcastradio.support.Program;
     30 import com.android.car.radio.utils.ProgramSelectorUtils;
     31 
     32 import java.util.ArrayList;
     33 import java.util.HashMap;
     34 import java.util.List;
     35 import java.util.Map;
     36 import java.util.Objects;
     37 
     38 /**
     39  * Class that manages persistent storage of various radio options.
     40  */
     41 public class RadioStorage {
     42     private static final String TAG = "Em.RadioStorage";
     43     private static final String PREF_NAME = "com.android.car.radio.RadioStorage";
     44 
     45     // Keys used for storage in the SharedPreferences.
     46     private static final String PREF_KEY_RADIO_BAND = "radio_band";
     47     private static final String PREF_KEY_RADIO_CHANNEL_AM = "radio_channel_am";
     48     private static final String PREF_KEY_RADIO_CHANNEL_FM = "radio_channel_fm";
     49 
     50     public static final int INVALID_RADIO_CHANNEL = -1;
     51     public static final int INVALID_RADIO_BAND = -1;
     52 
     53     private static SharedPreferences sSharedPref;
     54     private static RadioStorage sInstance;
     55     private static RadioDatabase sRadioDatabase;
     56 
     57     /**
     58      * Listener that will be called when something in the radio storage changes.
     59      */
     60     public interface PresetsChangeListener {
     61         /**
     62          * Called when favorite list has changed.
     63          */
     64         void onPresetsRefreshed();
     65     }
     66 
     67     private final LiveData<List<Program>> mFavorites;
     68 
     69     // TODO(b/73950974): use Observer<> directly
     70     private final Map<PresetsChangeListener, Observer<List<Program>>> mPresetListeners =
     71             new HashMap<>();
     72 
     73     private RadioStorage(Context context) {
     74         if (sSharedPref == null) {
     75             sSharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
     76         }
     77 
     78         if (sRadioDatabase == null) {
     79             sRadioDatabase = RadioDatabase.buildInstance(context);
     80         }
     81 
     82         mFavorites = sRadioDatabase.getAllFavorites();
     83     }
     84 
     85     /**
     86      * Returns singleton instance of {@link RadioStorage}.
     87      */
     88     public static RadioStorage getInstance(Context context) {
     89         if (sInstance == null) {
     90             sInstance = new RadioStorage(context.getApplicationContext());
     91         }
     92 
     93         return sInstance;
     94     }
     95 
     96     /**
     97      * Registers the given {@link PresetsChangeListener} to be notified when any radio preset state
     98      * has changed.
     99      */
    100     public void addPresetsChangeListener(PresetsChangeListener listener) {
    101         Observer<List<Program>> observer = list -> listener.onPresetsRefreshed();
    102         synchronized (mPresetListeners) {
    103             mFavorites.observeForever(observer);
    104             mPresetListeners.put(listener, observer);
    105         }
    106     }
    107 
    108     /**
    109      * Unregisters the given {@link PresetsChangeListener}.
    110      */
    111     public void removePresetsChangeListener(PresetsChangeListener listener) {
    112         Observer<List<Program>> observer;
    113         synchronized (mPresetListeners) {
    114             observer = mPresetListeners.remove(listener);
    115             mFavorites.removeObserver(observer);
    116         }
    117     }
    118 
    119     /**
    120      * Returns all currently loaded presets. If there are no stored presets, this method will
    121      * return an empty {@link List}.
    122      *
    123      * <p>Register as a {@link PresetsChangeListener} to be notified of any changes in the
    124      * preset list.
    125      */
    126     public @NonNull List<Program> getPresets() {
    127         List<Program> favorites = mFavorites.getValue();
    128         if (favorites != null) return favorites;
    129 
    130         // It won't be a problem when we use Observer<> directly.
    131         Log.w(TAG, "Database is not ready yet");
    132         return new ArrayList<>();
    133     }
    134 
    135     /**
    136      * Returns {@code true} if the given {@link ProgramSelector} is a user saved favorite.
    137      */
    138     public boolean isPreset(@NonNull ProgramSelector selector) {
    139         return mFavorites.getValue().contains(new Program(selector, ""));
    140     }
    141 
    142     /**
    143      * Stores that given {@link Program} as a preset. This operation will override any
    144      * previously stored preset that matches the given preset.
    145      *
    146      * <p>Upon a successful store, the presets list will be refreshed via a call to
    147      * {@link #refreshPresets()}.
    148      *
    149      * @see #refreshPresets()
    150      */
    151     public void storePreset(@NonNull Program preset) {
    152         new StorePresetAsyncTask().execute(Objects.requireNonNull(preset));
    153     }
    154 
    155     /**
    156      * Removes the given {@link Program} as a preset.
    157      *
    158      * <p>Upon a successful removal, the presets list will be refreshed via a call to
    159      * {@link #refreshPresets()}.
    160      *
    161      * @see #refreshPresets()
    162      */
    163     public void removePreset(@NonNull ProgramSelector preset) {
    164         new RemovePresetAsyncTask().execute(Objects.requireNonNull(preset));
    165     }
    166 
    167     /**
    168      * Returns the stored radio band that was set in {@link #storeRadioChannel}. If a radio band
    169      * has not previously been stored, then {@link RadioManager#BAND_FM} is returned.
    170      *
    171      * @return One of {@link RadioManager#BAND_FM} or {@link RadioManager#BAND_AM}.
    172      */
    173     public int getStoredRadioBand() {
    174         return sSharedPref.getInt(PREF_KEY_RADIO_BAND, RadioManager.BAND_FM);
    175     }
    176 
    177     /**
    178      * Returns the stored radio channel that was set in {@link #storeRadioChannel(int, int)}. If a
    179      * radio channel for the given band has not been previously stored, then
    180      * {@link #INVALID_RADIO_CHANNEL} is returned.
    181      *
    182      * @param band One of the BAND_* values from {@link RadioManager}. For example,
    183      *             {@link RadioManager#BAND_AM}.
    184      */
    185     public long getStoredRadioChannel(int band) {
    186         switch (band) {
    187             case RadioManager.BAND_AM:
    188                 return sSharedPref.getLong(PREF_KEY_RADIO_CHANNEL_AM, INVALID_RADIO_CHANNEL);
    189 
    190             case RadioManager.BAND_FM:
    191                 return sSharedPref.getLong(PREF_KEY_RADIO_CHANNEL_FM, INVALID_RADIO_CHANNEL);
    192 
    193             default:
    194                 return INVALID_RADIO_CHANNEL;
    195         }
    196     }
    197 
    198     /**
    199      * Stores a radio channel (i.e. the radio frequency) for a particular band so it can be later
    200      * retrieved via {@link #getStoredRadioChannel(int band)}.
    201      */
    202     public void storeRadioChannel(@NonNull ProgramSelector sel) {
    203         if (Log.isLoggable(TAG, Log.DEBUG)) {
    204             Log.d(TAG, "storeRadioChannel(" + sel + ")");
    205         }
    206 
    207         // TODO(b/73950974): don't store if it's already the same
    208 
    209         int band = ProgramSelectorUtils.getRadioBand(sel);
    210         if (band != RadioManager.BAND_AM && band != RadioManager.BAND_FM) return;
    211 
    212         SharedPreferences.Editor editor = sSharedPref.edit();
    213         editor.putInt(PREF_KEY_RADIO_BAND, band);
    214 
    215         long freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
    216         if (band == RadioManager.BAND_AM) {
    217             editor.putLong(PREF_KEY_RADIO_CHANNEL_AM, freq);
    218         }
    219         if (band == RadioManager.BAND_FM) {
    220             editor.putLong(PREF_KEY_RADIO_CHANNEL_FM, freq);
    221         }
    222 
    223         editor.apply();
    224     }
    225 
    226     /**
    227      * {@link AsyncTask} that will store a single {@link Program} that is passed to its
    228      * {@link AsyncTask#execute(Object[])}.
    229      */
    230     private class StorePresetAsyncTask extends AsyncTask<Program, Void, Boolean> {
    231         private static final String TAG = "Em.StorePresetAT";
    232 
    233         @Override
    234         protected Boolean doInBackground(Program... programs) {
    235             sRadioDatabase.insertFavorite(programs[0]);
    236             return true;
    237         }
    238     }
    239 
    240     /**
    241      * {@link AsyncTask} that will remove a single {@link Program} that is passed to its
    242      * {@link AsyncTask#execute(Object[])}.
    243      */
    244     private class RemovePresetAsyncTask extends AsyncTask<ProgramSelector, Void, Boolean> {
    245         private static final String TAG = "Em.RemovePresetAT";
    246 
    247         @Override
    248         protected Boolean doInBackground(ProgramSelector... selectors) {
    249             sRadioDatabase.removeFavorite(selectors[0]);
    250             return true;
    251         }
    252     }
    253 }
    254