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