Skip to content

Commit 76f1fc6

Browse files
committed
Adding 2 layers of SQL DB security and minor clean up
* Removed the commented `enableWriteAheadLogging` * No need for this as this allows concurrent read and writes to occur and we would rather pause a read while a write occurs * Pausing will ensure that we are querying the most updated data * 2 layers of security for SQL DB regarding `OSInAppMessageRepository.java` * First one will be transactions using `beginTransaction`, `setTransactionSuccessful`, and `endTransaction` methods wherever we `insert`, `delete`, or `update` * Second one is keeping TABLE manipulation or reading within the same file and setting these methods as synchronized so that any other TABLE touching that occurs won't happen till any current methods are complete * The combination of the two steps should help prevent any conflicts between SQL DB and solve the locking issues * WIP - Making sure any DB reading and writing is from the same file so `synchronized` along with transactions provide this 2 layer security around our single SQL DB instance
1 parent 2be8e02 commit 76f1fc6

File tree

4 files changed

+227
-181
lines changed

4 files changed

+227
-181
lines changed

OneSignalSDK/onesignal/src/main/java/com/onesignal/OSInAppMessageRepository.java

Lines changed: 166 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.content.ContentValues;
44
import android.database.Cursor;
5+
import android.database.SQLException;
56
import android.database.sqlite.SQLiteDatabase;
67
import android.support.annotation.WorkerThread;
78

@@ -23,18 +24,28 @@ class OSInAppMessageRepository {
2324
@WorkerThread
2425
synchronized void saveInAppMessage(OSInAppMessage inAppMessage) {
2526
SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries();
26-
27-
ContentValues values = new ContentValues();
28-
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, inAppMessage.messageId);
29-
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_DISPLAY_QUANTITY, inAppMessage.getRedisplayStats().getDisplayQuantity());
30-
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_LAST_DISPLAY, inAppMessage.getRedisplayStats().getLastDisplayTime());
31-
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS, inAppMessage.getClickedClickIds().toString());
32-
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_DISPLAYED_IN_SESSION, inAppMessage.isDisplayedInSession());
33-
34-
int rowsUpdated = writableDb.update(OneSignalDbContract.InAppMessageTable.TABLE_NAME, values,
35-
OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID + " = ?", new String[]{inAppMessage.messageId});
36-
if (rowsUpdated == 0)
37-
writableDb.insert(OneSignalDbContract.InAppMessageTable.TABLE_NAME, null, values);
27+
writableDb.beginTransaction();
28+
try {
29+
ContentValues values = new ContentValues();
30+
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID, inAppMessage.messageId);
31+
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_DISPLAY_QUANTITY, inAppMessage.getRedisplayStats().getDisplayQuantity());
32+
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_NAME_LAST_DISPLAY, inAppMessage.getRedisplayStats().getLastDisplayTime());
33+
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS, inAppMessage.getClickedClickIds().toString());
34+
values.put(OneSignalDbContract.InAppMessageTable.COLUMN_DISPLAYED_IN_SESSION, inAppMessage.isDisplayedInSession());
35+
36+
int rowsUpdated = writableDb.update(OneSignalDbContract.InAppMessageTable.TABLE_NAME, values,
37+
OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID + " = ?", new String[]{inAppMessage.messageId});
38+
if (rowsUpdated == 0)
39+
writableDb.insert(OneSignalDbContract.InAppMessageTable.TABLE_NAME, null, values);
40+
41+
writableDb.setTransactionSuccessful();
42+
} finally {
43+
try {
44+
writableDb.endTransaction(); // May throw if transaction was never opened or DB is full.
45+
} catch (Throwable t) {
46+
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", t);
47+
}
48+
}
3849
}
3950

4051
@WorkerThread
@@ -78,4 +89,147 @@ synchronized List<OSInAppMessage> getCachedInAppMessages() {
7889
return iams;
7990
}
8091

92+
@WorkerThread
93+
synchronized void cleanCachedInAppMessages() {
94+
SQLiteDatabase writableDb = dbHelper.getSQLiteDatabaseWithRetries();
95+
96+
// 1. Query for all old message ids and old clicked click ids
97+
String[] retColumns = new String[]{
98+
OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID,
99+
OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS
100+
};
101+
102+
String whereStr = OneSignalDbContract.InAppMessageTable.COLUMN_NAME_LAST_DISPLAY + " < ?";
103+
104+
String sixMonthsAgoInSeconds = String.valueOf((System.currentTimeMillis() / 1_000L) - OneSignalCacheCleaner.IAM_CACHE_DATA_LIFETIME);
105+
String[] whereArgs = new String[]{sixMonthsAgoInSeconds};
106+
107+
Set<String> oldMessageIds = OSUtils.newConcurrentSet();
108+
Set<String> oldClickedClickIds = OSUtils.newConcurrentSet();
109+
110+
Cursor cursor = null;
111+
try {
112+
cursor = writableDb.query(OneSignalDbContract.InAppMessageTable.TABLE_NAME,
113+
retColumns,
114+
whereStr,
115+
whereArgs,
116+
null,
117+
null,
118+
null);
119+
120+
if (cursor == null || cursor.getCount() == 0) {
121+
OneSignal.onesignalLog(OneSignal.LOG_LEVEL.DEBUG, "Attempted to clean 6 month old IAM data, but none exists!");
122+
return;
123+
}
124+
125+
// From cursor get all of the old message ids and old clicked click ids
126+
if (cursor.moveToFirst()) {
127+
do {
128+
String oldMessageId = cursor.getString(
129+
cursor.getColumnIndex(
130+
OneSignalDbContract.InAppMessageTable.COLUMN_NAME_MESSAGE_ID));
131+
String oldClickIds = cursor.getString(
132+
cursor.getColumnIndex(
133+
OneSignalDbContract.InAppMessageTable.COLUMN_CLICK_IDS));
134+
135+
oldMessageIds.add(oldMessageId);
136+
oldClickedClickIds.addAll(OSUtils.newStringSetFromJSONArray(new JSONArray(oldClickIds)));
137+
} while (cursor.moveToNext());
138+
}
139+
} catch (JSONException e) {
140+
e.printStackTrace();
141+
} finally {
142+
if (cursor != null && !cursor.isClosed())
143+
cursor.close();
144+
}
145+
146+
writableDb.beginTransaction();
147+
try {
148+
// 2. Delete old IAMs from SQL
149+
writableDb.delete(
150+
OneSignalDbContract.InAppMessageTable.TABLE_NAME,
151+
whereStr,
152+
whereArgs);
153+
writableDb.setTransactionSuccessful();
154+
} finally {
155+
try {
156+
writableDb.endTransaction(); // May throw if transaction was never opened or DB is full.
157+
} catch (SQLException e) {
158+
OneSignal.Log(OneSignal.LOG_LEVEL.ERROR, "Error closing transaction! ", e);
159+
}
160+
}
161+
162+
// 3. Use queried data to clean SharedPreferences
163+
cleanInAppMessageIds(oldMessageIds);
164+
cleanInAppMessageClickedClickIds(oldClickedClickIds);
165+
}
166+
167+
/**
168+
* Clean up 6 month old IAM ids in {@link android.content.SharedPreferences}:
169+
* 1. Dismissed message ids
170+
* 2. Impressioned message ids
171+
* <br/><br/>
172+
* Note: This should only ever be called by {@link OSInAppMessageRepository#cleanCachedInAppMessages()}
173+
* <br/><br/>
174+
* @see OneSignalCacheCleaner#cleanCachedInAppMessages(OneSignalDbHelper)
175+
* @see OSInAppMessageRepository#cleanCachedInAppMessages()
176+
*/
177+
private void cleanInAppMessageIds(Set<String> oldMessageIds) {
178+
if (oldMessageIds != null && oldMessageIds.size() > 0) {
179+
Set<String> dismissedMessages = OneSignalPrefs.getStringSet(
180+
OneSignalPrefs.PREFS_ONESIGNAL,
181+
OneSignalPrefs.PREFS_OS_DISMISSED_IAMS,
182+
OSUtils.<String>newConcurrentSet());
183+
184+
Set<String> impressionedMessages = OneSignalPrefs.getStringSet(
185+
OneSignalPrefs.PREFS_ONESIGNAL,
186+
OneSignalPrefs.PREFS_OS_IMPRESSIONED_IAMS,
187+
OSUtils.<String>newConcurrentSet());
188+
189+
if (dismissedMessages != null && dismissedMessages.size() > 0) {
190+
dismissedMessages.removeAll(oldMessageIds);
191+
OneSignalPrefs.saveStringSet(
192+
OneSignalPrefs.PREFS_ONESIGNAL,
193+
OneSignalPrefs.PREFS_OS_DISMISSED_IAMS,
194+
dismissedMessages);
195+
}
196+
197+
if (impressionedMessages != null && impressionedMessages.size() > 0) {
198+
impressionedMessages.removeAll(oldMessageIds);
199+
OneSignalPrefs.saveStringSet(
200+
OneSignalPrefs.PREFS_ONESIGNAL,
201+
OneSignalPrefs.PREFS_OS_IMPRESSIONED_IAMS,
202+
impressionedMessages);
203+
}
204+
}
205+
}
206+
207+
/**
208+
* Clean up 6 month old IAM clicked click ids in {@link android.content.SharedPreferences}:
209+
* 1. Clicked click ids from elements within IAM
210+
* <br/><br/>
211+
* Note: This should only ever be called by {@link OSInAppMessageRepository#cleanCachedInAppMessages()}
212+
* <br/><br/>
213+
* @see OneSignalCacheCleaner#cleanCachedInAppMessages(OneSignalDbHelper)
214+
* @see OSInAppMessageRepository#cleanCachedInAppMessages()
215+
*/
216+
private void cleanInAppMessageClickedClickIds(Set<String> oldClickedClickIds) {
217+
if (oldClickedClickIds != null && oldClickedClickIds.size() > 0) {
218+
Set<String> clickedClickIds = OneSignalPrefs.getStringSet(
219+
OneSignalPrefs.PREFS_ONESIGNAL,
220+
OneSignalPrefs.PREFS_OS_CLICKED_CLICK_IDS_IAMS,
221+
OSUtils.<String>newConcurrentSet());
222+
223+
if (clickedClickIds != null && clickedClickIds.size() > 0) {
224+
clickedClickIds.removeAll(oldClickedClickIds);
225+
OneSignalPrefs.saveStringSet(
226+
OneSignalPrefs.PREFS_ONESIGNAL,
227+
OneSignalPrefs.PREFS_OS_CLICKED_CLICK_IDS_IAMS,
228+
clickedClickIds);
229+
}
230+
}
231+
}
232+
233+
234+
81235
}

0 commit comments

Comments
 (0)