From 508fd65d1bc60cf1b51791be23c5d2ba3c188a19 Mon Sep 17 00:00:00 2001 From: Ryan Lepinski Date: Mon, 24 Aug 2020 13:58:45 -0700 Subject: [PATCH] [MOBILE-1853] Fix datastore crash (#825) * [MOBILE-1853] Fix datastore crash * Fixes * Release 13.3.3 * Feedback --- CHANGELOG.md | 3 + build.gradle | 2 +- .../com/urbanairship/PreferenceDataStore.java | 127 ++++++++++++++++-- .../urbanairship/channel/AirshipChannel.java | 1 + .../com/urbanairship/channel/NamedUser.java | 2 + .../channel/TagGroupRegistrar.java | 3 + 6 files changed, 125 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3f79a991..2142d0aa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ [Migration Guides](https://github.com/urbanairship/android-library/tree/main/documentation/migration) +## Version 13.3.3 - August 24, 2020 +Patch release to fix SQL exceptions during SDK init. Applications that are seeing any SQL crashes from Airship should update. + ## Version 13.3.2 - July 28, 2020 Patch release to fix In-App Automation version triggers to only fire on app updates instead of new installs. diff --git a/build.gradle b/build.gradle index c85a8f803..85f653911 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,7 @@ buildscript { ext { // Airship Version - major.minor.patch - airshipVersion = '13.3.2' + airshipVersion = '13.3.3' // Airship Version Qualifier beta, release, etc... // airshipVersionQualifier = "beta" diff --git a/urbanairship-core/src/main/java/com/urbanairship/PreferenceDataStore.java b/urbanairship-core/src/main/java/com/urbanairship/PreferenceDataStore.java index 9fd118e6d..089456ed2 100644 --- a/urbanairship-core/src/main/java/com/urbanairship/PreferenceDataStore.java +++ b/urbanairship-core/src/main/java/com/urbanairship/PreferenceDataStore.java @@ -15,6 +15,7 @@ import com.urbanairship.util.UAStringUtil; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -100,24 +101,122 @@ public void removeListener(@NonNull PreferenceChangeListener listener) { * Initializes the preference data store. */ protected void init() { - Cursor cursor = resolver.query(UrbanAirshipProvider.getPreferencesContentUri(context), null, null, null, null); - if (cursor == null) { + loadPreferences(); + } + + private void loadPreferences() { + Cursor cursor = null; + + try { + List fromStore = new ArrayList<>(); + + cursor = resolver.query(UrbanAirshipProvider.getPreferencesContentUri(context), null, null, null, null); + if (cursor == null) { + Logger.error("Failed to load preferences. Retrying with fallback loading."); + fallbackLoad(); + return; + } + + int keyIndex = cursor.getColumnIndex(PreferencesDataManager.COLUMN_NAME_KEY); + int valueIndex = cursor.getColumnIndex(PreferencesDataManager.COLUMN_NAME_VALUE); + + while (cursor.moveToNext()) { + String key = cursor.getString(keyIndex); + String value = cursor.getString(valueIndex); + fromStore.add(new Preference(key, value)); + } + + cursor.close(); + finishLoad(fromStore); + } catch (Exception e) { + if (cursor != null) { + cursor.close(); + } + + Logger.error(e, "Failed to load preferences. Retrying with fallback loading."); + fallbackLoad(); + } + } + + private void fallbackLoad() { + List keys = queryKeys(); + if (keys.isEmpty()) { + Logger.error("Unable to load keys, deleting preference store."); + resolver.delete(UrbanAirshipProvider.getPreferencesContentUri(context), null, null); return; } - int keyIndex = cursor.getColumnIndex(PreferencesDataManager.COLUMN_NAME_KEY); - int valueIndex = cursor.getColumnIndex(PreferencesDataManager.COLUMN_NAME_VALUE); + List fromStore = new ArrayList<>(); - while (cursor.moveToNext()) { - String key = cursor.getString(keyIndex); - String value = cursor.getString(valueIndex); - Preference preference = new Preference(key, value); - preference.registerObserver(); + for (String key : keys) { + String value = queryValue(key); + if (value == null) { + Logger.error("Unable to fetch preference value. Deleting: %s", key); + resolver.delete(UrbanAirshipProvider.getPreferencesContentUri(context), "_id == ?", new String[] { key }); + } else { + fromStore.add(new Preference(key, value)); + } + } - preferences.put(key, preference); + finishLoad(fromStore); + } + + private String queryValue(String key) { + String[] columns = new String[] { + PreferencesDataManager.COLUMN_NAME_VALUE + }; + + Cursor cursor = null; + String value = null; + + try { + cursor = resolver.query(UrbanAirshipProvider.getPreferencesContentUri(context), columns, "_id == ?", new String[] { key }, null); + if (cursor != null && cursor.moveToFirst()) { + value = cursor.getString(0); + } + } catch (Exception e) { + Logger.error(e, "Failed to query preference: %s", key); + } finally { + if (cursor != null) { + cursor.close(); + } + } + + return value; + } + + @NonNull + private List queryKeys() { + String[] columns = new String[] { + PreferencesDataManager.COLUMN_NAME_KEY + }; + + Cursor cursor = null; + try { + cursor = resolver.query(UrbanAirshipProvider.getPreferencesContentUri(context), columns, null, null, null); + if (cursor == null) { + resolver.delete(UrbanAirshipProvider.getPreferencesContentUri(context), null, null); + } + List keys = new ArrayList<>(); + while (cursor.moveToNext()) { + keys.add(cursor.getString(0)); + } + return keys; + } catch (Exception e) { + Logger.error(e, "Failed to query keys."); + } finally { + if (cursor != null) { + cursor.close(); + } } + return Collections.emptyList(); + } - cursor.close(); + private void finishLoad(@NonNull List preferences) { + for (Preference preference : preferences) { + this.preferences.put(preference.key, preference); + preference.registerObserver(); + } } /** @@ -505,7 +604,11 @@ void syncValue() { } if (cursor != null) { - setValue(cursor.moveToFirst() ? cursor.getString(0) : null); + try { + setValue(cursor.moveToFirst() ? cursor.getString(0) : null); + } catch (Exception e) { + Logger.error(e, "Unable to sync preference %s from database", key); + } } else { Logger.debug("PreferenceDataStore - Unable to get preference %s from database. Falling back to cached value.", key); } diff --git a/urbanairship-core/src/main/java/com/urbanairship/channel/AirshipChannel.java b/urbanairship-core/src/main/java/com/urbanairship/channel/AirshipChannel.java index 29d954cd1..45e28781d 100644 --- a/urbanairship-core/src/main/java/com/urbanairship/channel/AirshipChannel.java +++ b/urbanairship-core/src/main/java/com/urbanairship/channel/AirshipChannel.java @@ -172,6 +172,7 @@ protected void init() { } channelCreationDelayEnabled = getId() == null && runtimeConfig.getConfigOptions().channelCreationDelayEnabled; + attributeMutationStore.collapseAndSaveMutations(); } /** diff --git a/urbanairship-core/src/main/java/com/urbanairship/channel/NamedUser.java b/urbanairship-core/src/main/java/com/urbanairship/channel/NamedUser.java index 404a514e7..686bf86e6 100644 --- a/urbanairship-core/src/main/java/com/urbanairship/channel/NamedUser.java +++ b/urbanairship-core/src/main/java/com/urbanairship/channel/NamedUser.java @@ -137,6 +137,8 @@ public ChannelRegistrationPayload.Builder extend(@NonNull ChannelRegistrationPay if (airshipChannel.getId() != null && (!isIdUpToDate() || getId() != null)) { dispatchNamedUserUpdateJob(); } + + attributeMutationStore.collapseAndSaveMutations(); } /** diff --git a/urbanairship-core/src/main/java/com/urbanairship/channel/TagGroupRegistrar.java b/urbanairship-core/src/main/java/com/urbanairship/channel/TagGroupRegistrar.java index 4f83fabfc..377691345 100644 --- a/urbanairship-core/src/main/java/com/urbanairship/channel/TagGroupRegistrar.java +++ b/urbanairship-core/src/main/java/com/urbanairship/channel/TagGroupRegistrar.java @@ -95,6 +95,9 @@ public TagGroupRegistrar(@NonNull AirshipRuntimeConfig runtimeConfig, this.namedUserStore = namedUserStore; this.channelStore = channelStore; this.client = client; + + namedUserStore.collapseMutations(); + channelStore.collapseMutations(); } /**