diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFileStorage.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFileStorage.java index 1df0bc420..49711a047 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFileStorage.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppFileStorage.java @@ -26,21 +26,27 @@ public class IterableInAppFileStorage implements IterableInAppStorage, IterableI private static final String FOLDER_PATH = "IterableInAppFileStorage"; private static final String INDEX_FILE = "index.html"; private static final int OPERATION_SAVE = 100; + private final Context context; + private Map messages = Collections.synchronizedMap(new LinkedHashMap()); private final HandlerThread fileOperationThread = new HandlerThread("FileOperationThread"); + @VisibleForTesting FileOperationHandler fileOperationHandler; IterableInAppFileStorage(Context context) { this.context = context; + fileOperationThread.start(); fileOperationHandler = new FileOperationHandler(fileOperationThread.getLooper()); + load(); } + //region IterableInAppStorage interface implementation @NonNull @Override public synchronized List getMessages() { @@ -68,6 +74,45 @@ public synchronized void removeMessage(@NonNull IterableInAppMessage message) { saveMessagesInBackground(); } + @Override + public void saveHTML(@NonNull String messageID, @NonNull String contentHTML) { + File folder = createFolderForMessage(messageID); + if (folder == null) { + IterableLogger.e(TAG, "Failed to create folder for HTML content"); + return; + } + + File file = new File(folder, INDEX_FILE); + boolean result = IterableUtil.writeFile(file, contentHTML); + if (!result) { + IterableLogger.e(TAG, "Failed to store HTML content"); + } + } + + @Nullable + @Override + public String getHTML(@NonNull String messageID) { + File file = getFileForContent(messageID); + return IterableUtil.readFile(file); + } + + @Override + public void removeHTML(@NonNull String messageID) { + File folder = getFolderForMessage(messageID); + + File[] files = folder.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + file.delete(); + } + folder.delete(); + } + //endregion + + //region In-App Lifecycle @Override public void onInAppMessageChanged(@NonNull IterableInAppMessage message) { saveMessagesInBackground(); @@ -80,7 +125,9 @@ private synchronized void clearMessages() { } messages.clear(); } + //endregion + //region JSON Parsing @NonNull private JSONObject serializeMessages() { JSONObject jsonData = new JSONObject(); @@ -116,15 +163,9 @@ private void loadMessagesFromJson(JSONObject jsonData) { } } } + //endregion - private File getInAppStorageFile() { - return new File(getInAppContentFolder(), "itbl_inapp.json"); - } - - private File getInAppCacheStorageFile() { - return new File(IterableUtil.getSdkCacheDir(context), "itbl_inapp.json"); - } - + //region File Saving/Loading private void load() { try { File inAppStorageFile = getInAppStorageFile(); @@ -169,20 +210,15 @@ private synchronized void saveMetadata() { IterableLogger.e(TAG, "Error while saving in-app messages to file", e); } } + //endregion - @Override - public void saveHTML(@NonNull String messageID, @NonNull String contentHTML) { - File folder = createFolderForMessage(messageID); - if (folder == null) { - IterableLogger.e(TAG, "Failed to create folder for HTML content"); - return; - } + //region File Management + private File getInAppStorageFile() { + return new File(getInAppContentFolder(), "itbl_inapp.json"); + } - File file = new File(folder, INDEX_FILE); - boolean result = IterableUtil.writeFile(file, contentHTML); - if (!result) { - IterableLogger.e(TAG, "Failed to store HTML content"); - } + private File getInAppCacheStorageFile() { + return new File(IterableUtil.getSdkCacheDir(context), "itbl_inapp.json"); } @Nullable @@ -217,28 +253,7 @@ private File getFileForContent(String messageID) { File folder = getFolderForMessage(messageID); return new File(folder, INDEX_FILE); } - - @Nullable - @Override - public String getHTML(@NonNull String messageID) { - File file = getFileForContent(messageID); - return IterableUtil.readFile(file); - } - - @Override - public void removeHTML(@NonNull String messageID) { - File folder = getFolderForMessage(messageID); - - File[] files = folder.listFiles(); - if (files == null) { - return; - } - - for (File file : files) { - file.delete(); - } - folder.delete(); - } + //endregion class FileOperationHandler extends Handler { FileOperationHandler(Looper threadLooper) { diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppMemoryStorage.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppMemoryStorage.java index 647b27f10..b8ca9708b 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppMemoryStorage.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppMemoryStorage.java @@ -13,6 +13,7 @@ class IterableInAppMemoryStorage implements IterableInAppStorage { } + //region IterableInAppStorage interface implementation @NonNull @Override public synchronized List getMessages() { @@ -54,4 +55,5 @@ public String getHTML(@NonNull String messageID) { public void removeHTML(@NonNull String messageID) { } + //endregion } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppStorage.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppStorage.java index fcf107872..ccf199f72 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppStorage.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableInAppStorage.java @@ -8,13 +8,18 @@ interface IterableInAppStorage { @NonNull List getMessages(); + @Nullable IterableInAppMessage getMessage(String messageId); + void addMessage(@NonNull IterableInAppMessage message); + void removeMessage(@NonNull IterableInAppMessage message); void saveHTML(@NonNull String messageID, @NonNull String contentHTML); + @Nullable String getHTML(@NonNull String messageID); + void removeHTML(@NonNull String messageID); } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtil.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtil.java index 5a6e06bb9..62538b803 100644 --- a/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtil.java +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtil.java @@ -2,28 +2,16 @@ import android.content.Context; import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.iterable.iterableapi.util.IOUtils; - -import org.json.JSONException; import org.json.JSONObject; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; class IterableUtil { - private static final String TAG = "IterableUtil"; - @VisibleForTesting static IterableUtilImpl instance = new IterableUtilImpl(); @@ -86,7 +74,6 @@ static File getDirectory(File folder, String subFolder) { return instance.getDirectory(folder, subFolder); } - @Nullable static String readFile(File file) { return instance.readFile(file); @@ -96,175 +83,7 @@ static boolean writeFile(File file, String content) { return instance.writeFile(file, content); } - static class IterableUtilImpl { - - public long currentTimeMillis() { - return System.currentTimeMillis(); - } - - String getAppVersion(Context context) { - try { - PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - return pInfo.versionName; - } catch (PackageManager.NameNotFoundException e) { - IterableLogger.e(TAG, "Error while retrieving app version", e); - } - return null; - } - - String getAppVersionCode(Context context) { - try { - PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - return Integer.toString(pInfo.versionCode); - } catch (PackageManager.NameNotFoundException e) { - IterableLogger.e(TAG, "Error while retrieving app version code", e); - } - return null; - } - - /** - * Maps a version string like "x.y.z" onto an integer like xxxyyyzzz - * Example: "1.2.3" -> 1002003 - * @param versionString Version string - * @return Integer representation of the version - */ - int convertVersionStringToInt(String versionString) { - int version = 0; - String[] versionNumbers = versionString.split("\\."); - for (int i = 0; i < versionNumbers.length; i++) { - version += ((int) Math.pow(10, 3 * (2 - i))) * Integer.parseInt(versionNumbers[i]); - } - return version; - } - - void saveExpirableJsonObject(SharedPreferences preferences, String key, JSONObject object, long expirationInterval) { - saveExpirableValue(preferences, key, object.toString(), expirationInterval); - } - - void saveExpirableValue(SharedPreferences preferences, String key, String value, long expirationInterval) { - SharedPreferences.Editor editor = preferences.edit(); - editor.putString(key + IterableConstants.SHARED_PREFS_OBJECT_SUFFIX, value); - editor.putLong(key + IterableConstants.SHARED_PREFS_EXPIRATION_SUFFIX, currentTimeMillis() + expirationInterval); - editor.apply(); - } - - String retrieveExpirableValue(SharedPreferences preferences, String key) { - String value = preferences.getString(key + IterableConstants.SHARED_PREFS_OBJECT_SUFFIX, null); - long expirationTime = preferences.getLong(key + IterableConstants.SHARED_PREFS_EXPIRATION_SUFFIX, 0); - - if (value == null || expirationTime < currentTimeMillis()) { - return null; - } else { - return value; - } - } - - JSONObject retrieveExpirableJsonObject(SharedPreferences preferences, String key) { - try { - String encodedObject = retrieveExpirableValue(preferences, key); - if (encodedObject != null) { - return new JSONObject(encodedObject); - } - } catch (Exception e) { - IterableLogger.e(TAG, "Error while parsing an expirable object for key: " + key, e); - } - return null; - } - - @Nullable - Long retrieveValidCampaignIdOrNull(final JSONObject json, final String key) { - try { - final long id = json.getLong(key); - if (isValidCampaignId(id)) { - return id; - } else { - return null; - } - } catch (final JSONException ex) { - return null; - } - } - - boolean isValidCampaignId(final long campaignId) { - return campaignId >= 0; - } - - File getSdkCacheDir(Context context) { - File sdkCacheDir = new File(context.getCacheDir(), "com.iterable.sdk"); - if (!sdkCacheDir.exists()) { - sdkCacheDir.mkdirs(); - } - return sdkCacheDir; - } - - File getSDKFilesDirectory(Context context) { - File iterableSDKRootDirectory = new File(context.getFilesDir(), "com.iterable.sdk"); - if (!iterableSDKRootDirectory.exists()) { - iterableSDKRootDirectory.mkdirs(); - } - return iterableSDKRootDirectory; - } - - File getDirectory(File folder, String subFolder) { - File applicationRootDirectory = new File(folder, subFolder); - if (!applicationRootDirectory.exists()) { - applicationRootDirectory.mkdirs(); - } - return applicationRootDirectory; - } - - @Nullable - String readFile(File file) { - FileInputStream inputStream = null; - InputStreamReader streamReader = null; - BufferedReader bufferedReader = null; - try { - inputStream = new FileInputStream(file); - streamReader = new InputStreamReader(inputStream); - bufferedReader = new BufferedReader(streamReader); - StringBuilder stringBuilder = new StringBuilder(); - String line; - while ((line = bufferedReader.readLine()) != null) { - stringBuilder.append(line); - } - return stringBuilder.toString(); - } catch (Exception e) { - IterableLogger.e(TAG, "Error while reading file: " + file.toString(), e); - } finally { - IOUtils.closeQuietly(inputStream); - IOUtils.closeQuietly(streamReader); - IOUtils.closeQuietly(bufferedReader); - } - return null; - } - - boolean writeFile(File file, String content) { - try { - FileOutputStream outputStream = new FileOutputStream(file); - OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); - outputStreamWriter.write(content); - outputStreamWriter.close(); - return true; - } catch (Exception e) { - IterableLogger.e(TAG, "Error while writing to file: " + file.toString(), e); - } - return false; - } - } - static boolean isUrlOpenAllowed(@NonNull String url) { - String urlProtocol = url.split("://")[0]; - if (urlProtocol.equals("https")) { - return true; - } - - for (String allowedProtocol : IterableApi.sharedInstance.config.allowedProtocols) { - if (urlProtocol.equals(allowedProtocol)) { - return true; - } - } - - IterableLogger.d(TAG, urlProtocol + " is not in the allowed protocols"); - return false; + return instance.isUrlOpenAllowed(url); } } diff --git a/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtilImpl.java b/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtilImpl.java new file mode 100644 index 000000000..de8fd116a --- /dev/null +++ b/iterableapi/src/main/java/com/iterable/iterableapi/IterableUtilImpl.java @@ -0,0 +1,196 @@ +package com.iterable.iterableapi; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.iterable.iterableapi.util.IOUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; + +class IterableUtilImpl { + private static final String TAG = "IterableUtilImpl"; + + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + String getAppVersion(Context context) { + try { + PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return pInfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + IterableLogger.e(TAG, "Error while retrieving app version", e); + } + return null; + } + + String getAppVersionCode(Context context) { + try { + PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return Integer.toString(pInfo.versionCode); + } catch (PackageManager.NameNotFoundException e) { + IterableLogger.e(TAG, "Error while retrieving app version code", e); + } + return null; + } + + /** + * Maps a version string like "x.y.z" onto an integer like xxxyyyzzz + * Example: "1.2.3" -> 1002003 + * @param versionString Version string + * @return Integer representation of the version + */ + int convertVersionStringToInt(String versionString) { + int version = 0; + String[] versionNumbers = versionString.split("\\."); + for (int i = 0; i < versionNumbers.length; i++) { + version += ((int) Math.pow(10, 3 * (2 - i))) * Integer.parseInt(versionNumbers[i]); + } + return version; + } + + void saveExpirableJsonObject(SharedPreferences preferences, String key, JSONObject object, long expirationInterval) { + saveExpirableValue(preferences, key, object.toString(), expirationInterval); + } + + void saveExpirableValue(SharedPreferences preferences, String key, String value, long expirationInterval) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(key + IterableConstants.SHARED_PREFS_OBJECT_SUFFIX, value); + editor.putLong(key + IterableConstants.SHARED_PREFS_EXPIRATION_SUFFIX, currentTimeMillis() + expirationInterval); + editor.apply(); + } + + String retrieveExpirableValue(SharedPreferences preferences, String key) { + String value = preferences.getString(key + IterableConstants.SHARED_PREFS_OBJECT_SUFFIX, null); + long expirationTime = preferences.getLong(key + IterableConstants.SHARED_PREFS_EXPIRATION_SUFFIX, 0); + + if (value == null || expirationTime < currentTimeMillis()) { + return null; + } else { + return value; + } + } + + JSONObject retrieveExpirableJsonObject(SharedPreferences preferences, String key) { + try { + String encodedObject = retrieveExpirableValue(preferences, key); + if (encodedObject != null) { + return new JSONObject(encodedObject); + } + } catch (Exception e) { + IterableLogger.e(TAG, "Error while parsing an expirable object for key: " + key, e); + } + return null; + } + + @Nullable + Long retrieveValidCampaignIdOrNull(final JSONObject json, final String key) { + try { + final long id = json.getLong(key); + if (isValidCampaignId(id)) { + return id; + } else { + return null; + } + } catch (final JSONException ex) { + return null; + } + } + + boolean isValidCampaignId(final long campaignId) { + return campaignId >= 0; + } + + File getSdkCacheDir(Context context) { + File sdkCacheDir = new File(context.getCacheDir(), "com.iterable.sdk"); + if (!sdkCacheDir.exists()) { + sdkCacheDir.mkdirs(); + } + return sdkCacheDir; + } + + File getSDKFilesDirectory(Context context) { + File iterableSDKRootDirectory = new File(context.getFilesDir(), "com.iterable.sdk"); + if (!iterableSDKRootDirectory.exists()) { + iterableSDKRootDirectory.mkdirs(); + } + return iterableSDKRootDirectory; + } + + File getDirectory(File folder, String subFolder) { + File applicationRootDirectory = new File(folder, subFolder); + if (!applicationRootDirectory.exists()) { + applicationRootDirectory.mkdirs(); + } + return applicationRootDirectory; + } + + @Nullable + String readFile(File file) { + FileInputStream inputStream = null; + InputStreamReader streamReader = null; + BufferedReader bufferedReader = null; + try { + inputStream = new FileInputStream(file); + streamReader = new InputStreamReader(inputStream); + bufferedReader = new BufferedReader(streamReader); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line); + } + return stringBuilder.toString(); + } catch (Exception e) { + IterableLogger.e(TAG, "Error while reading file: " + file.toString(), e); + } finally { + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(streamReader); + IOUtils.closeQuietly(bufferedReader); + } + return null; + } + + boolean writeFile(File file, String content) { + try { + FileOutputStream outputStream = new FileOutputStream(file); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); + outputStreamWriter.write(content); + outputStreamWriter.close(); + return true; + } catch (Exception e) { + IterableLogger.e(TAG, "Error while writing to file: " + file.toString(), e); + } + return false; + } + + static boolean isUrlOpenAllowed(@NonNull String url) { + String urlProtocol = url.split("://")[0]; + + if (urlProtocol.equals("https")) { + return true; + } + + for (String allowedProtocol : IterableApi.sharedInstance.config.allowedProtocols) { + if (urlProtocol.equals(allowedProtocol)) { + return true; + } + } + + IterableLogger.d(TAG, urlProtocol + " is not in the allowed protocols"); + + return false; + } +} \ No newline at end of file diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/BaseTest.java b/iterableapi/src/test/java/com/iterable/iterableapi/BaseTest.java index e790a449a..27f8f44ae 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/BaseTest.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/BaseTest.java @@ -22,7 +22,7 @@ public abstract class BaseTest { @Rule public AsyncTaskRule asyncTaskRule = new AsyncTaskRule(); - protected IterableUtil.IterableUtilImpl getIterableUtilSpy() { + protected IterableUtilImpl getIterableUtilSpy() { return utilsRule.iterableUtilSpy; } diff --git a/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java b/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java index 0df786ea6..5d17ff967 100644 --- a/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java +++ b/iterableapi/src/test/java/com/iterable/iterableapi/IterableUtilRule.java @@ -6,9 +6,8 @@ import static org.mockito.Mockito.spy; public class IterableUtilRule extends TestWatcher { - - private IterableUtil.IterableUtilImpl originalIterableUtil; - public IterableUtil.IterableUtilImpl iterableUtilSpy; + private IterableUtilImpl originalIterableUtil; + public IterableUtilImpl iterableUtilSpy; @Override protected void starting(Description description) {