All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy