1 /* 2 * Copyright (C) 2018 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.content.Context; 20 import android.content.SharedPreferences; 21 import android.hardware.radio.ProgramSelector; 22 import android.net.Uri; 23 import android.os.AsyncTask; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 27 import androidx.lifecycle.LiveData; 28 29 import com.android.car.broadcastradio.support.Program; 30 import com.android.car.broadcastradio.support.platform.ProgramSelectorExt; 31 import com.android.car.radio.SkipMode; 32 import com.android.car.radio.bands.ProgramType; 33 import com.android.car.radio.util.Log; 34 35 import java.util.List; 36 import java.util.Objects; 37 38 /** 39 * Manages persistent storage for broadcast radio application. 40 */ 41 public class RadioStorage { 42 private static final String TAG = "BcRadioApp.storage"; 43 private static final String PREF_NAME = "RadioAppPrefs"; 44 45 private static final String PREF_KEY_RECENT_TYPE = "recentProgramType"; 46 private static final String PREF_KEY_RECENT_PROGRAM_PREFIX = "recentProgram-"; 47 48 private static final String PREF_KEY_SKIP_MODE = "smartSeekMode"; 49 50 private static RadioStorage sInstance; 51 52 private final SharedPreferences mPrefs; 53 private final RadioDatabase mDatabase; 54 private final LiveData<List<Program>> mFavorites; 55 RadioStorage(Context context)56 private RadioStorage(Context context) { 57 mPrefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); 58 mDatabase = RadioDatabase.buildInstance(context); 59 60 mFavorites = mDatabase.getAllFavorites(); 61 } 62 63 /** 64 * Returns singleton instance of {@link RadioStorage}. 65 */ getInstance(Context context)66 public static @NonNull RadioStorage getInstance(Context context) { 67 if (sInstance != null) return sInstance; 68 synchronized (RadioStorage.class) { 69 if (sInstance != null) return sInstance; 70 sInstance = new RadioStorage(context.getApplicationContext()); 71 return sInstance; 72 } 73 } 74 75 /** 76 * Returns a list of all favorites added previously by the user. 77 */ 78 @NonNull getFavorites()79 public LiveData<List<Program>> getFavorites() { 80 return mFavorites; 81 } 82 83 /** 84 * Checks, if a given program is favorite. 85 * 86 * @param favorites List of favorites. 87 * @param selector Program to check. 88 */ isFavorite(@onNull List<Program> favorites, @NonNull ProgramSelector selector)89 public static boolean isFavorite(@NonNull List<Program> favorites, 90 @NonNull ProgramSelector selector) { 91 return favorites.contains(new Program(selector, "")); 92 } 93 94 /** 95 * Checks, if a given program is favorite. 96 * 97 * @param selector Program to check. 98 */ isFavorite(@onNull ProgramSelector selector)99 public boolean isFavorite(@NonNull ProgramSelector selector) { 100 List<Program> favorites = mFavorites.getValue(); 101 if (favorites == null) { 102 Log.w(TAG, "Database is not ready yet"); 103 return false; 104 } 105 return isFavorite(favorites, selector); 106 } 107 108 private class AddFavoriteTask extends AsyncTask<Program, Void, Void> { 109 @Override doInBackground(Program... programs)110 protected Void doInBackground(Program... programs) { 111 mDatabase.insertFavorite(programs[0]); 112 return null; 113 } 114 } 115 116 private class RemoveFavoriteTask extends AsyncTask<ProgramSelector, Void, Void> { 117 @Override doInBackground(ProgramSelector... selectors)118 protected Void doInBackground(ProgramSelector... selectors) { 119 mDatabase.removeFavorite(selectors[0]); 120 return null; 121 } 122 } 123 124 /** 125 * Adds a new program to the favorites list. 126 * 127 * After the operation succeeds, the list is refreshed via live object returned 128 * from {@link #getFavorites}. 129 * 130 * @param favorite A program to add. 131 */ addFavorite(@onNull Program favorite)132 public void addFavorite(@NonNull Program favorite) { 133 new AddFavoriteTask().execute(Objects.requireNonNull(favorite)); 134 } 135 136 /** 137 * Removes a program from the favorites list. 138 * 139 * After the operation succeeds, the list is refreshed via live object returned 140 * from {@link #getFavorites}. 141 * 142 * @param favorite A program to remove. 143 */ removeFavorite(@onNull ProgramSelector favorite)144 public void removeFavorite(@NonNull ProgramSelector favorite) { 145 new RemoveFavoriteTask().execute(Objects.requireNonNull(favorite)); 146 } 147 148 /** 149 * Stores recently selected program so it can be recalled on next app launch. 150 * 151 * @param sel Program to store as recently selected. 152 */ setRecentlySelected(@onNull ProgramSelector sel)153 public void setRecentlySelected(@NonNull ProgramSelector sel) { 154 ProgramType pt = ProgramType.fromSelector(sel); 155 int ptid = pt == null ? 0 : pt.id; 156 157 SharedPreferences.Editor editor = mPrefs.edit(); 158 boolean hasChanges = false; 159 160 String prefName = PREF_KEY_RECENT_PROGRAM_PREFIX + ptid; 161 Uri selUri = ProgramSelectorExt.toUri(sel); 162 if (selUri == null) return; 163 String selUriStr = selUri.toString(); 164 if (!mPrefs.getString(prefName, "").equals(selUriStr)) { 165 editor.putString(prefName, selUriStr); 166 hasChanges = true; 167 } 168 169 if (mPrefs.getInt(PREF_KEY_RECENT_TYPE, -1) != ptid) { 170 editor.putInt(PREF_KEY_RECENT_TYPE, ptid); 171 hasChanges = true; 172 } 173 174 if (hasChanges) editor.apply(); 175 } 176 177 /** 178 * Retrieves recently selected program. 179 * 180 * This function can either retrieve the recently selected program for a specific 181 * {@link ProgramType} (band) or just the recently selected program in general. 182 * 183 * @param pt Program type to filter the result on, or {@code null} for general check 184 * @return Selector of the recent program or {@code null}, if there was none saved 185 */ getRecentlySelected(@ullable ProgramType pt)186 public @Nullable ProgramSelector getRecentlySelected(@Nullable ProgramType pt) { 187 int ptid = pt != null ? pt.id : mPrefs.getInt(PREF_KEY_RECENT_TYPE, -1); 188 if (ptid == -1) return null; 189 190 String selUriStr = mPrefs.getString(PREF_KEY_RECENT_PROGRAM_PREFIX + ptid, ""); 191 if (selUriStr.equals("")) return null; 192 193 return ProgramSelectorExt.fromUri(Uri.parse(selUriStr)); 194 } 195 196 /** 197 * Stores the last {@link SkipMode} set. 198 */ setSkipMode(@onNull SkipMode mode)199 public void setSkipMode(@NonNull SkipMode mode) { 200 int value = mode.ordinal(); 201 SharedPreferences.Editor editor = mPrefs.edit(); 202 203 if (mPrefs.getInt(PREF_KEY_SKIP_MODE, 204 SkipMode.DEFAULT_MODE.ordinal()) != value) { 205 editor.putInt(PREF_KEY_SKIP_MODE, value); 206 editor.apply(); 207 } 208 } 209 210 /** 211 * Gets the last {@link SkipMode} set. 212 */ 213 @NonNull getSkipMode()214 public SkipMode getSkipMode() { 215 int value = mPrefs.getInt(PREF_KEY_SKIP_MODE, SkipMode.DEFAULT_MODE.ordinal()); 216 SkipMode mode = SkipMode.valueOf(value); 217 if (mode == null) { 218 Log.e(TAG, "getSkipMode(): invalid pref value " + value + "; returning " 219 + SkipMode.DEFAULT_MODE + " instead"); 220 mode = SkipMode.DEFAULT_MODE; 221 } 222 return mode; 223 } 224 } 225