summaryrefslogtreecommitdiffstats
path: root/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/base/java/org/mozilla/gecko/push/PushState.java')
-rw-r--r--mobile/android/base/java/org/mozilla/gecko/push/PushState.java137
1 files changed, 137 insertions, 0 deletions
diff --git a/mobile/android/base/java/org/mozilla/gecko/push/PushState.java b/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
new file mode 100644
index 000000000..686bf5a0d
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushState.java
@@ -0,0 +1,137 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.push;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.WorkerThread;
+import android.support.v4.util.AtomicFile;
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Firefox for Android maintains an App-wide mapping associating
+ * profile names to push registrations. Each push registration in turn associates channels to
+ * push subscriptions.
+ * <p/>
+ * We use a simple storage model of JSON backed by an atomic file. It is assumed that instances
+ * of this class will reference distinct files on disk; and that all accesses will be happen on a
+ * single (worker thread).
+ */
+public class PushState {
+ private static final String LOG_TAG = "GeckoPushState";
+
+ private static final long VERSION = 1L;
+
+ protected final @NonNull AtomicFile file;
+
+ protected final @NonNull Map<String, PushRegistration> registrations;
+
+ public PushState(Context context, @NonNull String fileName) {
+ this.registrations = new HashMap<>();
+
+ file = new AtomicFile(new File(context.getApplicationInfo().dataDir, fileName));
+ synchronized (file) {
+ try {
+ final String s = new String(file.readFully(), "UTF-8");
+ final JSONObject temp = new JSONObject(s);
+ if (temp.optLong("version", 0L) != VERSION) {
+ throw new JSONException("Unknown version!");
+ }
+
+ final JSONObject registrationsObject = temp.getJSONObject("registrations");
+ final Iterator<String> it = registrationsObject.keys();
+ while (it.hasNext()) {
+ final String profileName = it.next();
+ final PushRegistration registration = PushRegistration.fromJSONObject(registrationsObject.getJSONObject(profileName));
+ this.registrations.put(profileName, registration);
+ }
+ } catch (FileNotFoundException e) {
+ Log.i(LOG_TAG, "No storage found; starting fresh.");
+ this.registrations.clear();
+ } catch (IOException | JSONException e) {
+ Log.w(LOG_TAG, "Got exception reading storage; dropping storage and starting fresh.", e);
+ this.registrations.clear();
+ }
+ }
+ }
+
+ public JSONObject toJSONObject() throws JSONException {
+ final JSONObject registrations = new JSONObject();
+ for (Map.Entry<String, PushRegistration> entry : this.registrations.entrySet()) {
+ registrations.put(entry.getKey(), entry.getValue().toJSONObject());
+ }
+
+ final JSONObject jsonObject = new JSONObject();
+ jsonObject.put("version", 1L);
+ jsonObject.put("registrations", registrations);
+ return jsonObject;
+ }
+
+ /**
+ * Synchronously persist the cache to disk.
+ * @return whether the cache was persisted successfully.
+ */
+ @WorkerThread
+ public boolean checkpoint() {
+ synchronized (file) {
+ FileOutputStream fileOutputStream = null;
+ try {
+ fileOutputStream = file.startWrite();
+ fileOutputStream.write(toJSONObject().toString().getBytes("UTF-8"));
+ file.finishWrite(fileOutputStream);
+ return true;
+ } catch (JSONException | IOException e) {
+ Log.e(LOG_TAG, "Got exception writing JSON storage; ignoring.", e);
+ if (fileOutputStream != null) {
+ file.failWrite(fileOutputStream);
+ }
+ return false;
+ }
+ }
+ }
+
+ public PushRegistration putRegistration(@NonNull String profileName, @NonNull PushRegistration registration) {
+ return registrations.put(profileName, registration);
+ }
+
+ /**
+ * Return the existing push registration for the given profile name.
+ * @return the push registration, if one is registered; null otherwise.
+ */
+ public PushRegistration getRegistration(@NonNull String profileName) {
+ return registrations.get(profileName);
+ }
+
+ /**
+ * Return all push registrations, keyed by profile names.
+ * @return a map of all push registrations. <b>The map is intentionally mutable - be careful!</b>
+ */
+ public @NonNull Map<String, PushRegistration> getRegistrations() {
+ return registrations;
+ }
+
+ /**
+ * Remove any existing push registration for the given profile name.
+ * </p>
+ * Most registration removals are during iteration, which should use an iterator that is
+ * aware of removals.
+ * @return the removed push registration, if one was removed; null otherwise.
+ */
+ public PushRegistration removeRegistration(@NonNull String profileName) {
+ return registrations.remove(profileName);
+ }
+}