blob: 3ce829794b41376ea2b4ed4cb5ecc0a4eb879a59 [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.radio;
import android.content.Context;
import android.content.SharedPreferences;
import android.hardware.radio.RadioManager;
import android.os.AsyncTask;
import android.os.SystemProperties;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
import android.util.Log;
import com.android.car.radio.service.RadioStation;
import com.android.car.radio.demo.DemoRadioStations;
import com.android.car.radio.demo.RadioDemo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Class that manages persistent storage of various radio options.
*/
public class RadioStorage {
private static final String TAG = "Em.RadioStorage";
private static final String PREF_NAME = "com.android.car.radio.RadioStorage";
// Keys used for storage in the SharedPreferences.
private static final String PREF_KEY_RADIO_BAND = "radio_band";
private static final String PREF_KEY_RADIO_CHANNEL_AM = "radio_channel_am";
private static final String PREF_KEY_RADIO_CHANNEL_FM = "radio_channel_fm";
public static final int INVALID_RADIO_CHANNEL = -1;
public static final int INVALID_RADIO_BAND = -1;
private static SharedPreferences sSharedPref;
private static RadioStorage sInstance;
private static RadioDatabase sRadioDatabase;
/**
* Listener that will be called when something in the radio storage changes.
*/
public interface PresetsChangeListener {
/**
* Called when {@link #refreshPresets()} has completed.
*/
void onPresetsRefreshed();
}
/**
* Listener that will be called when something in the pre-scanned channels has changed.
*/
public interface PreScannedChannelChangeListener {
/**
* Notifies that the pre-scanned channels for the given radio band has changed.
*
* @param radioBand One of the band values in {@link RadioManager}.
*/
void onPreScannedChannelChange(int radioBand);
}
private Set<PresetsChangeListener> mPresetListeners = new HashSet<>();
/**
* Set of listeners that will be notified whenever pre-scanned channels have changed.
*
* <p>Note that this set is not initialized because pre-scanned channels are only needed if
* dual-tuners exist in the current radio. Thus, this set is created conditionally.
*/
private Set<PreScannedChannelChangeListener> mPreScannedListeners;
private List<RadioStation> mPresets = new ArrayList<>();
private RadioStorage(Context context) {
if (sSharedPref == null) {
sSharedPref = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
if (sRadioDatabase == null) {
sRadioDatabase = new RadioDatabase(context);
}
}
public static RadioStorage getInstance(Context context) {
if (sInstance == null) {
sInstance = new RadioStorage(context.getApplicationContext());
// When the RadioStorage is first created, load the list of radio presets.
sInstance.refreshPresets();
}
return sInstance;
}
/**
* Registers the given {@link PresetsChangeListener} to be notified when any radio preset state
* has changed.
*/
public void addPresetsChangeListener(PresetsChangeListener listener) {
mPresetListeners.add(listener);
}
/**
* Unregisters the given {@link PresetsChangeListener}.
*/
public void removePresetsChangeListener(PresetsChangeListener listener) {
mPresetListeners.remove(listener);
}
/**
* Registers the given {@link PreScannedChannelChangeListener} to be notified of changes to
* pre-scanned channels.
*/
public void addPreScannedChannelChangeListener(PreScannedChannelChangeListener listener) {
if (mPreScannedListeners == null) {
mPreScannedListeners = new HashSet<>();
}
mPreScannedListeners.add(listener);
}
/**
* Unregisters the given {@link PreScannedChannelChangeListener}.
*/
public void removePreScannedChannelChangeListener(PreScannedChannelChangeListener listener) {
if (mPreScannedListeners == null) {
return;
}
mPreScannedListeners.remove(listener);
}
/**
* Requests a load of all currently stored presets. This operation runs asynchronously. When
* the presets have been loaded, any registered {@link PresetsChangeListener}s are
* notified via the {@link PresetsChangeListener#onPresetsRefreshed()} method.
*/
private void refreshPresets() {
new GetAllPresetsAsyncTask().execute();
}
/**
* Returns all currently loaded presets. If there are no stored presets, this method will
* return an empty {@link List}.
*
* <p>Register as a {@link PresetsChangeListener} to be notified of any changes in the
* preset list.
*/
public List<RadioStation> getPresets() {
return mPresets;
}
/**
* Convenience method for checking if a specific channel is a preset. This method will assume
* the subchannel is 0.
*
* @see #isPreset(RadioStation)
* @return {@code true} if the channel is a user saved preset.
*/
public boolean isPreset(int channel, int radioBand) {
return isPreset(new RadioStation(channel, 0 /* subchannel */, radioBand, null /* rds */));
}
/**
* Returns {@code true} if the given {@link RadioStation} is a user saved preset.
*/
public boolean isPreset(RadioStation station) {
if (station == null) {
return false;
}
// Just iterate through the list and match the station. If we anticipate this list growing
// large, might have to change it to some sort of Set.
for (RadioStation preset : mPresets) {
if (preset.equals(station)) {
return true;
}
}
return false;
}
/**
* Stores that given {@link RadioStation} as a preset. This operation will override any
* previously stored preset that matches the given preset.
*
* <p>Upon a successful store, the presets list will be refreshed via a call to
* {@link #refreshPresets()}.
*
* @see #refreshPresets()
*/
public void storePreset(RadioStation preset) {
if (preset == null) {
return;
}
new StorePresetAsyncTask().execute(preset);
}
/**
* Removes the given {@link RadioStation} as a preset.
*
* <p>Upon a successful removal, the presets list will be refreshed via a call to
* {@link #refreshPresets()}.
*
* @see #refreshPresets()
*/
public void removePreset(RadioStation preset) {
if (preset == null) {
return;
}
new RemovePresetAsyncTask().execute(preset);
}
/**
* Returns the stored radio band that was set in {@link #storeRadioBand(int)}. If a radio band
* has not previously been stored, then {@link RadioManager#BAND_FM} is returned.
*
* @return One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
* {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
*/
public int getStoredRadioBand() {
// No need to verify that the returned value is one of AM_BAND or FM_BAND because this is
// done in storeRadioBand(int).
return sSharedPref.getInt(PREF_KEY_RADIO_BAND, RadioManager.BAND_FM);
}
/**
* Stores a radio band for later retrieval via {@link #getStoredRadioBand()}.
*/
public void storeRadioBand(int radioBand) {
// Ensure that an incorrect radio band is not stored. Currently only FM and AM supported.
if (radioBand != RadioManager.BAND_FM && radioBand != RadioManager.BAND_AM) {
return;
}
sSharedPref.edit().putInt(PREF_KEY_RADIO_BAND, radioBand).apply();
}
/**
* Returns the stored radio channel that was set in {@link #storeRadioChannel(int, int)}. If a
* radio channel for the given band has not been previously stored, then
* {@link #INVALID_RADIO_CHANNEL} is returned.
*
* @param band One of the BAND_* values from {@link RadioManager}. For example,
* {@link RadioManager#BAND_AM}.
*/
public int getStoredRadioChannel(int band) {
switch (band) {
case RadioManager.BAND_AM:
return sSharedPref.getInt(PREF_KEY_RADIO_CHANNEL_AM, INVALID_RADIO_CHANNEL);
case RadioManager.BAND_FM:
return sSharedPref.getInt(PREF_KEY_RADIO_CHANNEL_FM, INVALID_RADIO_CHANNEL);
default:
return INVALID_RADIO_CHANNEL;
}
}
/**
* Stores a radio channel (i.e. the radio frequency) for a particular band so it can be later
* retrieved via {@link #getStoredRadioChannel(int band)}.
*/
public void storeRadioChannel(int band, int channel) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, String.format("storeRadioChannel(); band: %s, channel %s", band, channel));
}
if (channel <= 0) {
return;
}
switch (band) {
case RadioManager.BAND_AM:
sSharedPref.edit().putInt(PREF_KEY_RADIO_CHANNEL_AM, channel).apply();
break;
case RadioManager.BAND_FM:
sSharedPref.edit().putInt(PREF_KEY_RADIO_CHANNEL_FM, channel).apply();
break;
default:
Log.w(TAG, "Attempting to store channel for invalid band: " + band);
}
}
/**
* Stores the list of {@link RadioStation}s as the pre-scanned stations for the given radio
* band.
*
* @param radioBand One of {@link RadioManager#BAND_FM}, {@link RadioManager#BAND_AM},
* {@link RadioManager#BAND_FM_HD} or {@link RadioManager#BAND_AM_HD}.
*/
public void storePreScannedStations(int radioBand, List<RadioStation> stations) {
if (stations == null) {
return;
}
// Converting to an array rather than passing a List to the execute to avoid any potential
// heap pollution via AsyncTask's varargs.
new StorePreScannedAsyncTask(radioBand).execute(
stations.toArray(new RadioStation[stations.size()]));
}
/**
* Returns the list of pre-scanned radio channels for the given band.
*/
@NonNull
@WorkerThread
public List<RadioStation> getPreScannedStationsForBand(int radioBand) {
if (SystemProperties.getBoolean(RadioDemo.DEMO_MODE_PROPERTY, false)) {
switch (radioBand) {
case RadioManager.BAND_AM:
return DemoRadioStations.getAmStations();
case RadioManager.BAND_FM:
default:
return DemoRadioStations.getFmStations();
}
}
return sRadioDatabase.getAllPreScannedStationsForBand(radioBand);
}
/**
* Calls {@link PresetsChangeListener#onPresetsRefreshed()} for all registered
* {@link PresetsChangeListener}s.
*/
private void notifyPresetsListeners() {
for (PresetsChangeListener listener : mPresetListeners) {
listener.onPresetsRefreshed();
}
}
/**
* Calls {@link PreScannedChannelChangeListener#onPreScannedChannelChange(int)} for all
* registered {@link PreScannedChannelChangeListener}s.
*/
private void notifyPreScannedListeners(int radioBand) {
if (mPreScannedListeners == null) {
return;
}
for (PreScannedChannelChangeListener listener : mPreScannedListeners) {
listener.onPreScannedChannelChange(radioBand);
}
}
/**
* {@link AsyncTask} that will fetch all stored radio presets.
*/
private class GetAllPresetsAsyncTask extends AsyncTask<Void, Void, Void> {
private static final String TAG = "Em.GetAllPresetsAT";
@Override
protected Void doInBackground(Void... voids) {
mPresets = sRadioDatabase.getAllPresets();
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Loaded presets: " + mPresets);
}
return null;
}
@Override
public void onPostExecute(Void result) {
notifyPresetsListeners();
}
}
/**
* {@link AsyncTask} that will store a single {@link RadioStation} that is passed to its
* {@link AsyncTask#execute(Object[])}.
*/
private class StorePresetAsyncTask extends AsyncTask<RadioStation, Void, Boolean> {
private static final String TAG = "Em.StorePresetAT";
@Override
protected Boolean doInBackground(RadioStation... radioStations) {
RadioStation presetToStore = radioStations[0];
boolean result = sRadioDatabase.insertPreset(presetToStore);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Store preset success: " + result);
}
if (result) {
// Refresh the presets list.
mPresets = sRadioDatabase.getAllPresets();
}
return result;
}
@Override
public void onPostExecute(Boolean result) {
if (result) {
notifyPresetsListeners();
}
}
}
/**
* {@link AsyncTask} that will remove a single {@link RadioStation} that is passed to its
* {@link AsyncTask#execute(Object[])}.
*/
private class RemovePresetAsyncTask extends AsyncTask<RadioStation, Void, Boolean> {
private static final String TAG = "Em.RemovePresetAT";
@Override
protected Boolean doInBackground(RadioStation... radioStations) {
RadioStation presetToStore = radioStations[0];
boolean result = sRadioDatabase.deletePreset(presetToStore);
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Remove preset success: " + result);
}
if (result) {
// Refresh the presets list.
mPresets = sRadioDatabase.getAllPresets();
}
return result;
}
@Override
public void onPostExecute(Boolean result) {
if (result) {
notifyPresetsListeners();
}
}
}
/**
* {@link AsyncTask} that will store a list of pre-scanned {@link RadioStation}s that is passed
* to its {@link AsyncTask#execute(Object[])}.
*/
private class StorePreScannedAsyncTask extends AsyncTask<RadioStation, Void, Boolean> {
private static final String TAG = "Em.StorePreScannedAT";
private final int mRadioBand;
public StorePreScannedAsyncTask(int radioBand) {
mRadioBand = radioBand;
}
@Override
protected Boolean doInBackground(RadioStation... radioStations) {
boolean result = sRadioDatabase.insertPreScannedStations(mRadioBand,
Arrays.asList(radioStations));
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "Store pre-scanned stations success: " + result);
}
return result;
}
@Override
public void onPostExecute(Boolean result) {
if (result) {
notifyPreScannedListeners(mRadioBand);
}
}
}
}