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 */ onPresetsRefreshed()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 */ onPreScannedChannelChange(int radioBand)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 RadioStorage(Context context)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 getInstance(Context context)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 */ addPresetsChangeListener(PresetsChangeListener listener)114 public void addPresetsChangeListener(PresetsChangeListener listener) { 115 mPresetListeners.add(listener); 116 } 117 118 /** 119 * Unregisters the given {@link PresetsChangeListener}. 120 */ removePresetsChangeListener(PresetsChangeListener listener)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 */ addPreScannedChannelChangeListener(PreScannedChannelChangeListener listener)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 */ removePreScannedChannelChangeListener(PreScannedChannelChangeListener listener)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 */ refreshPresets()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 */ getPresets()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 */ isPreset(int channel, int radioBand)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 */ isPreset(RadioStation station)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 */ storePreset(RadioStation preset)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 */ removePreset(RadioStation preset)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 */ getStoredRadioBand()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 */ storeRadioBand(int radioBand)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 */ getStoredRadioChannel(int band)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 */ storeRadioChannel(int band, int channel)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 */ storePreScannedStations(int radioBand, List<RadioStation> stations)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 getPreScannedStationsForBand(int radioBand)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 */ notifyPresetsListeners()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 */ notifyPreScannedListeners(int radioBand)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 doInBackground(Void... voids)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 onPostExecute(Void result)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 doInBackground(RadioStation... radioStations)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 onPostExecute(Boolean result)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 doInBackground(RadioStation... radioStations)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 onPostExecute(Boolean result)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 StorePreScannedAsyncTask(int radioBand)459 public StorePreScannedAsyncTask(int radioBand) { 460 mRadioBand = radioBand; 461 } 462 463 @Override doInBackground(List<RadioStation> .... stationsList)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 onPostExecute(Boolean result)477 public void onPostExecute(Boolean result) { 478 if (result) { 479 notifyPreScannedListeners(mRadioBand); 480 } 481 } 482 } 483 } 484