1 /*
2  * Copyright 2019 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.media.testmediaapp.prefs;
18 
19 import android.content.Context;
20 import android.content.SharedPreferences;
21 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
22 
23 import androidx.preference.PreferenceManager;
24 
25 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaAccountType;
26 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaBrowseNodeType;
27 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaLoginEventOrder;
28 import com.android.car.media.testmediaapp.prefs.TmaEnumPrefs.TmaReplyDelay;
29 
30 import java.util.HashMap;
31 import java.util.Map;
32 import java.util.Objects;
33 
34 
35 /** Singleton class to access the application's preferences. */
36 public class TmaPrefs {
37 
38     private static TmaPrefs sPrefs;
39 
40     public final PrefEntry<TmaAccountType> mAccountType;
41     public final PrefEntry<TmaBrowseNodeType> mRootNodeType;
42 
43     /** Wait time before sending a node reply, unless overridden in json (when supported). */
44     public final PrefEntry<TmaReplyDelay> mRootReplyDelay;
45 
46     /** Wait time for openAssetFile. */
47     public final PrefEntry<TmaReplyDelay> mAssetReplyDelay;
48 
49     /** Media apps event (update playback state, load browse tree) order after login. */
50     public final PrefEntry<TmaLoginEventOrder> mLoginEventOrder;
51 
52 
getInstance(Context context)53     public synchronized static TmaPrefs getInstance(Context context) {
54         if (sPrefs == null) {
55             sPrefs = new TmaPrefs(context);
56         }
57         return sPrefs;
58     }
59 
60     public interface PrefValueChangedListener<T> {
onValueChanged(T oldValue, T newValue)61         void onValueChanged(T oldValue, T newValue);
62     }
63 
64     /** The set of keys used to store the preferences. */
65     private enum TmaPrefKey {
66         ACCOUNT_TYPE_KEY,
67         ROOT_NODE_TYPE_KEY,
68         ROOT_REPLY_DELAY_KEY,
69         ASSET_REPLY_DELAY_KEY,
70         LOGIN_EVENT_ORDER_KEY
71     }
72 
73     /**
74      *   Represents a entry in the prefs
75      */
76     public abstract class PrefEntry<T> {
77 
78         protected final String mKey;
79 
PrefEntry(TmaPrefKey prefKey)80         PrefEntry(TmaPrefKey prefKey) {
81             mKey = prefKey.name();
82         }
83 
getValue()84         public abstract T getValue();
setValue(T value)85         public abstract void setValue(T value);
86 
registerChangeListener(PrefValueChangedListener<T> listener)87         public void registerChangeListener(PrefValueChangedListener<T> listener) {
88             if (mListeners.get(listener) != null) return;
89 
90             T currentValue = getValue();
91             listener.onValueChanged(currentValue, currentValue);
92 
93             OnSharedPreferenceChangeListener listenerWrapper =
94                     new OnSharedPreferenceChangeListener() {
95                         private T mOldValue = currentValue;
96 
97                         @Override
98                         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
99                                 String key) {
100                             if (mKey.equals(key)) {
101                                 T newValue = getValue();
102                                 if (!Objects.equals(mOldValue, newValue)) {
103                                     listener.onValueChanged(mOldValue, newValue);
104                                     mOldValue = newValue;
105                                 }
106                             }
107                         }
108                     };
109 
110             mSharedPrefs.registerOnSharedPreferenceChangeListener(listenerWrapper);
111             mListeners.put(listener, listenerWrapper);
112         }
113 
unregisterChangeListener(PrefValueChangedListener<T> listener)114         public void unregisterChangeListener(PrefValueChangedListener<T> listener) {
115             OnSharedPreferenceChangeListener listenerWrapper = mListeners.get(listener);
116             if (listenerWrapper != null) {
117                 mSharedPrefs.unregisterOnSharedPreferenceChangeListener(listenerWrapper);
118                 mListeners.remove(listener);
119             }
120         }
121     }
122 
123 
124     private final Map<PrefValueChangedListener, OnSharedPreferenceChangeListener> mListeners
125             = new HashMap<>(5);
126 
127     private final SharedPreferences mSharedPrefs;
128 
129 
TmaPrefs(Context context)130     private TmaPrefs(Context context) {
131         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
132 
133         // Note: Android Auto emulator tests depend on a default account type which permits access
134         // to the browse tree (e.g. FREE), and the default root node type NODE_CHILDREN.
135         // Also, they assert on the titles of each root child of NODE_CHILDREN as well as the first
136         // track under the first root child ("Basic Songs"), i.e. "A normal 1H song".
137 
138         mAccountType = new EnumPrefEntry<>(TmaPrefKey.ACCOUNT_TYPE_KEY,
139                 TmaAccountType.values(), TmaAccountType.FREE);
140 
141         mRootNodeType = new EnumPrefEntry<>(TmaPrefKey.ROOT_NODE_TYPE_KEY,
142                 TmaBrowseNodeType.values(), TmaBrowseNodeType.NODE_CHILDREN);
143 
144         mRootReplyDelay = new EnumPrefEntry<>(TmaPrefKey.ROOT_REPLY_DELAY_KEY,
145                 TmaReplyDelay.values(), TmaReplyDelay.NONE);
146 
147         mAssetReplyDelay = new EnumPrefEntry<>(TmaPrefKey.ASSET_REPLY_DELAY_KEY,
148                 TmaReplyDelay.values(), TmaReplyDelay.NONE);
149 
150         mLoginEventOrder = new EnumPrefEntry<>(TmaPrefKey.LOGIN_EVENT_ORDER_KEY,
151                 TmaLoginEventOrder.values(), TmaLoginEventOrder.PLAYBACK_STATE_UPDATE_FIRST);
152     }
153 
154 
155     /** Handles the conversion between the enum values and the shared preferences. */
156     private class EnumPrefEntry<T extends Enum & TmaEnumPrefs.EnumPrefValue>
157             extends PrefEntry<T> {
158 
159         private final T[] mEnumValues;
160         private final T mDefaultValue;
161 
EnumPrefEntry(TmaPrefKey prefKey, T[] enumValues, T defaultValue)162         EnumPrefEntry(TmaPrefKey prefKey, T[] enumValues, T defaultValue) {
163             super(prefKey);
164             mEnumValues = enumValues;
165             mDefaultValue = defaultValue;
166         }
167 
168         @Override
getValue()169         public T getValue() {
170             String id = mSharedPrefs.getString(mKey, null);
171             if (id != null) {
172                 for (T value : mEnumValues) {
173                     if (value.getId().equals(id)) {
174                         return value;
175                     }
176                 }
177             }
178             return mDefaultValue;
179         }
180 
181         @Override
setValue(T value)182         public void setValue(T value) {
183             mSharedPrefs.edit().putString(mKey, value.getId()).commit();
184         }
185     }
186 
187 }
188