1+ /**
2+ * Modified MIT License
3+ *
4+ * Copyright 2018 OneSignal
5+ *
6+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7+ * of this software and associated documentation files (the "Software"), to deal
8+ * in the Software without restriction, including without limitation the rights
9+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+ * copies of the Software, and to permit persons to whom the Software is
11+ * furnished to do so, subject to the following conditions:
12+ *
13+ * 1. The above copyright notice and this permission notice shall be included in
14+ * all copies or substantial portions of the Software.
15+ *
16+ * 2. All copies of substantial portions of the Software may only be used in connection
17+ * with services provided by OneSignal.
18+ *
19+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+ * THE SOFTWARE.
26+ */
27+
28+
129package com .onesignal ;
230
331import android .content .Context ;
432import android .content .SharedPreferences ;
5- import android .support .annotation .NonNull ;
6-
7- import java .util .HashSet ;
8- import java .util .Set ;
9- import java .util .concurrent .ConcurrentHashMap ;
10- import java .util .concurrent .ScheduledThreadPoolExecutor ;
11- import java .util .concurrent .ThreadFactory ;
33+ import android .os .Handler ;
34+ import android .os .HandlerThread ;
1235
13- /**
14- * Copyright 2017 OneSignal
15- * Created by alamgir on 9/20/17.
16- */
36+ import java .util .HashMap ;
1737
1838class OneSignalPrefs {
1939
40+ // SharedPreferences Instances
2041 static final String PREFS_ONESIGNAL = OneSignal .class .getSimpleName ();
2142 static final String PREFS_PLAYER_PURCHASES = "GTPlayerPurchases" ;
2243
23- // PREFERENCES KEYS
44+ // PREFERENCES KEYS
2445 static final String PREFS_OS_LAST_LOCATION_TIME = "OS_LAST_LOCATION_TIME" ;
2546 static final String PREFS_GT_SOUND_ENABLED = "GT_SOUND_ENABLED" ;
2647 static final String PREFS_OS_LAST_SESSION_TIME = "OS_LAST_SESSION_TIME" ;
@@ -45,154 +66,178 @@ class OneSignalPrefs {
4566 static final String PREFS_ONESIGNAL_SYNCED_SUBSCRIPTION = "ONESIGNAL_SYNCED_SUBSCRIPTION" ;
4667 static final String PREFS_GT_REGISTRATION_ID = "GT_REGISTRATION_ID" ;
4768
48- // PLAYER PURCHASE KEYS
69+ // PLAYER PURCHASE KEYS
4970 static final String PREFS_PURCHASE_TOKENS = "purchaseTokens" ;
5071 static final String PREFS_EXISTING_PURCHASES = "ExistingPurchases" ;
5172
52- static ConcurrentHashMap <String ,SharedPreferences > preferencesMap = new ConcurrentHashMap <>();
53- //use a thread pool executor to execute disk writes
54- private static final ScheduledThreadPoolExecutor prefsExecutor = new ScheduledThreadPoolExecutor (1 );
73+ // Buffered writes to apply on WritePrefHandlerThread with a short delay
74+ static HashMap <String , HashMap <String , Object >> prefsToApply ;
75+ public static WritePrefHandlerThread prefsHandler ;
76+
5577 static {
56- prefsExecutor .setThreadFactory (new ThreadFactory () {
57- @ Override
58- public Thread newThread (@ NonNull Runnable runnable ) {
59- Thread newThread = new Thread (runnable );
60- newThread .setName ("ONESIGNAL_EXECUTOR_" + newThread .getId ());
61- return newThread ;
62- }
63- });
78+ initializePool ();
6479 }
6580
66- private static class PrefsWriteRunnable implements Runnable {
81+ public static class WritePrefHandlerThread extends HandlerThread {
82+ public Handler mHandler ;
6783
68- private String prefsName ;
69- private String prefKeyToWrite ;
70- private Object prefValueToWrite ;
84+ private static final int WRITE_CALL_DELAY_TO_BUFFER_MS = 200 ;
85+ private long lastSyncTime = 0L ;
7186
72- PrefsWriteRunnable (String prefsName ,
73- String prefKeyToWrite ,
74- Object prefValueToWrite ) {
75- this .prefsName = prefsName ;
76- this .prefKeyToWrite = prefKeyToWrite ;
77- this .prefValueToWrite = prefValueToWrite ;
87+ WritePrefHandlerThread () {
88+ super ("OSH_WritePrefs" );
89+ start ();
90+ mHandler = new Handler (getLooper ());
7891 }
7992
80- @ Override
81- public void run () {
82- SharedPreferences prefsToWrite = getSharedPrefsByName (prefsName );
93+ void startDelayedWrite () {
94+ synchronized (mHandler ) {
95+ mHandler .removeCallbacksAndMessages (null );
96+ if (lastSyncTime == 0 )
97+ lastSyncTime = System .currentTimeMillis ();
8398
84- if (prefsToWrite != null ) {
85- SharedPreferences .Editor editor = prefsToWrite .edit ();
99+ long delay = lastSyncTime - System .currentTimeMillis () + WRITE_CALL_DELAY_TO_BUFFER_MS ;
100+
101+ mHandler .postDelayed (getNewRunnable (), delay );
102+ }
103+ }
86104
87- if (prefValueToWrite instanceof String ) {
88- editor .putString (prefKeyToWrite ,(String )prefValueToWrite );
89- } else if (prefValueToWrite instanceof Boolean ) {
90- editor .putBoolean (prefKeyToWrite , (Boolean )prefValueToWrite );
91- } else if (prefValueToWrite instanceof Integer ) {
92- editor .putInt (prefKeyToWrite , (Integer )prefValueToWrite );
93- } else if (prefValueToWrite instanceof Long ) {
94- editor .putLong (prefKeyToWrite , (Long )prefValueToWrite );
105+ private Runnable getNewRunnable () {
106+ return new Runnable () {
107+ @ Override
108+ public void run () {
109+ flushBufferToDisk ();
95110 }
96- OneSignal . Log ( OneSignal . LOG_LEVEL . INFO ,
97- "updating prefs: " + prefsName + ", " + prefKeyToWrite );
111+ };
112+ }
98113
114+ private void flushBufferToDisk () {
115+ for (String pref : prefsToApply .keySet ()) {
116+ SharedPreferences prefsToWrite = getSharedPrefsByName (pref );
117+ SharedPreferences .Editor editor = prefsToWrite .edit ();
118+ HashMap <String , Object > prefHash = prefsToApply .get (pref );
119+ synchronized (prefHash ) {
120+ for (String key : prefHash .keySet ()) {
121+ Object value = prefHash .get (key );
122+ if (value instanceof String )
123+ editor .putString (key , (String )value );
124+ else if (value instanceof Boolean )
125+ editor .putBoolean (key , (Boolean )value );
126+ else if (value instanceof Integer )
127+ editor .putInt (key , (Integer )value );
128+ else if (value instanceof Long )
129+ editor .putLong (key , (Long )value );
130+ }
131+ prefHash .clear ();
132+ }
99133 editor .apply ();
100134 }
101135
136+ lastSyncTime = System .currentTimeMillis ();
102137 }
103138 }
104139
140+ public static void initializePool () {
141+ prefsToApply = new HashMap <>();
142+ prefsToApply .put (PREFS_ONESIGNAL , new HashMap <String , Object >());
143+ prefsToApply .put (PREFS_PLAYER_PURCHASES , new HashMap <String , Object >());
144+
145+ prefsHandler = new WritePrefHandlerThread ();
146+ }
105147
106- static void saveString (final String prefsName ,final String key ,final String value ) {
107- PrefsWriteRunnable saveStringRunnable = new PrefsWriteRunnable (prefsName , key , value );
108- prefsExecutor .execute (saveStringRunnable );
148+ static void saveString (final String prefsName , final String key , final String value ) {
149+ save (prefsName , key , value );
109150 }
110151
111152 static void saveBool (String prefsName , String key , boolean value ) {
112- PrefsWriteRunnable saveBoolRunnable = new PrefsWriteRunnable (prefsName , key , value );
113- prefsExecutor .execute (saveBoolRunnable );
153+ save (prefsName , key , value );
114154 }
115155
116156 static void saveInt (String prefsName , String key , int value ) {
117- PrefsWriteRunnable saveBoolRunnable = new PrefsWriteRunnable (prefsName , key , value );
118- prefsExecutor .execute (saveBoolRunnable );
157+ save (prefsName , key , value );
119158 }
120159
121160 static void saveLong (String prefsName , String key , long value ) {
122- PrefsWriteRunnable saveBoolRunnable = new PrefsWriteRunnable (prefsName ,key ,value );
123- prefsExecutor .execute (saveBoolRunnable );
161+ save (prefsName , key , value );
124162 }
125163
126- static boolean hasBool (String prefsName , String key ) {
127- SharedPreferences prefs = getSharedPrefsByName (prefsName );
128- if (prefs != null )
129- return getSharedPrefsByName (prefsName ).contains (key );
130-
131- return false ;
132- }
133-
134- static boolean has (String prefsName , String key ) {
135- SharedPreferences prefs = getSharedPrefsByName (prefsName );
136- if (prefs != null )
137- return getSharedPrefsByName (prefsName ).contains (key );
138-
139- return false ;
164+ static private void save (String prefsName , String key , Object value ) {
165+ HashMap <String , Object > pref = prefsToApply .get (prefsName );
166+ synchronized (pref ) {
167+ pref .put (key , value );
168+ }
169+ prefsHandler .startDelayedWrite ();
140170 }
141171
142172 static String getString (String prefsName , String key , String defValue ) {
143- SharedPreferences prefs = getSharedPrefsByName (prefsName );
144- if (prefs != null )
145- return prefs .getString (key , defValue );
146-
147- return defValue ;
173+ return (String )get (prefsName , key , String .class , defValue );
148174 }
149175
150176 static boolean getBool (String prefsName , String key , boolean defValue ) {
151- SharedPreferences prefs = getSharedPrefsByName (prefsName );
152- if (prefs != null )
153- return prefs .getBoolean (key , defValue );
154-
155- return defValue ;
177+ return (Boolean )get (prefsName , key , Boolean .class , defValue );
156178 }
157179
158180 static int getInt (String prefsName , String key , int defValue ) {
159- SharedPreferences prefs = getSharedPrefsByName (prefsName );
160- if (prefs != null )
161- return prefs .getInt (key , defValue );
162-
163- return defValue ;
181+ return (Integer )get (prefsName , key , Integer .class , defValue );
164182 }
165183
166184 static long getLong (String prefsName , String key , long defValue ) {
185+ return (Long )get (prefsName , key , Long .class , defValue );
186+ }
187+
188+ // If type == Object then this is a contains check
189+ private static Object get (String prefsName , String key , Class type , Object defValue ) {
190+ HashMap <String , Object > pref = prefsToApply .get (prefsName );
191+
192+ synchronized (pref ) {
193+ if (type .equals (Object .class ) && pref .containsKey (key ))
194+ return true ;
195+
196+ Object cachedValue = pref .get (key );
197+ if (cachedValue != null )
198+ return cachedValue ;
199+ }
200+
167201 SharedPreferences prefs = getSharedPrefsByName (prefsName );
168- if (prefs != null )
169- return prefs .getLong (key , defValue );
202+ if (prefs != null ) {
203+ if (type .equals (String .class ))
204+ return prefs .getString (key , (String )defValue );
205+ else if (type .equals (Boolean .class ))
206+ return prefs .getBoolean (key , (Boolean )defValue );
207+ else if (type .equals (Integer .class ))
208+ return prefs .getInt (key , (Integer )defValue );
209+ else if (type .equals (Long .class ))
210+ return prefs .getLong (key , (Long )defValue );
211+ else if (type .equals (Object .class ))
212+ return prefs .contains (key );
213+
214+ return null ;
215+ }
170216
171217 return defValue ;
172218 }
173219
220+ // TODO: Removes could be optimized as well.
221+ // running applying here for safely for now.
174222 static void remove (String prefsName , String key ) {
223+ HashMap <String , Object > pref = prefsToApply .get (prefsName );
224+ synchronized (pref ) {
225+ pref .remove (key );
226+ }
227+
175228 SharedPreferences prefs = getSharedPrefsByName (prefsName );
176- if (prefs != null ) {
229+ if (prefs != null ) {
177230 SharedPreferences .Editor editor = prefs .edit ();
178231 editor .remove (key );
179232 editor .apply ();
180233 }
181234 }
182235
183236 private static synchronized SharedPreferences getSharedPrefsByName (String prefsName ) {
184- SharedPreferences prefs ;
185- if (OneSignal .appContext == null )
237+ if (OneSignal .appContext == null )
186238 return null ;
187239
188- if (!preferencesMap .contains (prefsName )) {
189- prefs = OneSignal .appContext .getSharedPreferences (prefsName , Context .MODE_PRIVATE );
190- preferencesMap .put (prefsName , prefs );
191- }
192- else
193- prefs = preferencesMap .get (prefsName );
194-
195- return prefs ;
240+ return OneSignal .appContext .getSharedPreferences (prefsName , Context .MODE_PRIVATE );
196241 }
197242
198243}
0 commit comments