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