co.easimart.PushRouter Maven / Gradle / Ivy
package co.easimart;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
/**
* PushRouter handles distribution of push payloads through a broadcast intent with the
* "co.easimart.push.intent.RECEIVE" action. It also serializes a history of the last several pushes
* seen by this app. This history is necessary for two reasons:
*
* - For PPNS, we provide the last-seen timestamp to the server as part of the handshake. This is
* used as a cursor into the server-side inbox of recent pushes for this client.
* - For GCM, we use the history to deduplicate pushes when GCM decides to change the canonical
* registration id for a client (which can result in duplicate pushes while both the old and
* new registration id are still valid).
*/
/** package */ class PushRouter {
private static final String TAG = "co.easimart.EasimartPushRouter";
private static final String LEGACY_STATE_LOCATION = "pushState";
private static final String STATE_LOCATION = "push";
private static int MAX_HISTORY_LENGTH = 10;
private static PushRouter instance;
public static synchronized PushRouter getInstance() {
if (instance == null) {
File diskState = new File(EasimartPlugins.get().getFilesDir(), STATE_LOCATION);
File oldDiskState = new File(EasimartPlugins.get().getEasimartDir(), LEGACY_STATE_LOCATION);
instance = pushRouterFromState(diskState, oldDiskState, MAX_HISTORY_LENGTH);
}
return instance;
}
/* package for tests */ static synchronized void resetInstance() {
EasimartFileUtils.deleteQuietly(new File(EasimartPlugins.get().getFilesDir(), STATE_LOCATION));
instance = null;
}
/* package for tests */ static PushRouter pushRouterFromState(
File diskState, File oldDiskState, int maxHistoryLength) {
JSONObject state = readJSONFileQuietly(diskState);
JSONObject historyJSON = (state != null) ? state.optJSONObject("history") : null;
PushHistory history = new PushHistory(maxHistoryLength, historyJSON);
// If the deserialized push history object doesn't have a last timestamp, we might have to
// migrate the last timestamp from the legacy pushState file instead.
boolean didMigrate = false;
if (history.getLastReceivedTimestamp() == null) {
JSONObject oldState = readJSONFileQuietly(oldDiskState);
if (oldState != null) {
String lastTime = oldState.optString("lastTime", null);
if (lastTime != null) {
history.setLastReceivedTimestamp(lastTime);
}
didMigrate = true;
}
}
PushRouter router = new PushRouter(diskState, history);
if (didMigrate) {
router.saveStateToDisk();
EasimartFileUtils.deleteQuietly(oldDiskState);
}
return router;
}
private static JSONObject readJSONFileQuietly(File file) {
JSONObject json = null;
if (file != null) {
try {
json = EasimartFileUtils.readFileToJSONObject(file);
} catch (IOException | JSONException e) {
// do nothing
}
}
return json;
}
private final File diskState;
private final PushHistory history;
private PushRouter(File diskState, PushHistory history) {
this.diskState = diskState;
this.history = history;
}
/**
* Returns the state in this object as a persistable JSONObject. The persisted state looks like
* this:
*
* {
* "history": {
* "seen": {
* "": "",
* ...
* }
* "lastTime": ""
* }
* }
*/
/* package */ synchronized JSONObject toJSON() throws JSONException {
JSONObject json = new JSONObject();
json.put("history", history.toJSON());
return json;
}
private synchronized void saveStateToDisk() {
try {
EasimartFileUtils.writeJSONObjectToFile(diskState, toJSON());
} catch (IOException | JSONException e) {
EasimartLog.e(TAG, "Unexpected error when serializing push state to " + diskState, e);
}
}
public synchronized String getLastReceivedTimestamp() {
return history.getLastReceivedTimestamp();
}
public synchronized boolean handlePush(
String pushId, String timestamp, String channel, JSONObject data) {
if (EasimartTextUtils.isEmpty(pushId) || EasimartTextUtils.isEmpty(timestamp)) {
return false;
}
if (!history.tryInsertPush(pushId, timestamp)) {
return false;
}
// Persist the fact that we've seen this push.
saveStateToDisk();
Bundle extras = new Bundle();
extras.putString(EasimartPushBroadcastReceiver.KEY_PUSH_CHANNEL, channel);
if (data == null) {
extras.putString(EasimartPushBroadcastReceiver.KEY_PUSH_DATA, "{}");
} else {
extras.putString(EasimartPushBroadcastReceiver.KEY_PUSH_DATA, data.toString());
}
Intent intent = new Intent(EasimartPushBroadcastReceiver.ACTION_PUSH_RECEIVE);
intent.putExtras(extras);
// Set the package name to keep this intent within the given package.
Context context = Easimart.getApplicationContext();
intent.setPackage(context.getPackageName());
context.sendBroadcast(intent);
return true;
}
}