• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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          */
onPresetsRefreshed()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 
RadioStorage(Context context)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      */
getInstance(Context context)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      */
addPresetsChangeListener(PresetsChangeListener listener)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      */
removePresetsChangeListener(PresetsChangeListener listener)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      */
getPresets()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      */
isPreset(@onNull ProgramSelector selector)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      */
storePreset(@onNull Program preset)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      */
removePreset(@onNull ProgramSelector preset)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      */
getStoredRadioBand()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      */
getStoredRadioChannel(int band)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      */
storeRadioChannel(@onNull ProgramSelector sel)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
doInBackground(Program... programs)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
doInBackground(ProgramSelector... selectors)248         protected Boolean doInBackground(ProgramSelector... selectors) {
249             sRadioDatabase.removeFavorite(selectors[0]);
250             return true;
251         }
252     }
253 }
254