diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bf1bf2571..5c7ca310f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,37 +16,30 @@
~
~ You should have received a copy of the GNU General Public License along
~ with this program. If not, see .
- -->
-
-
+-->
+
-
-
-
-
-
+
-
@@ -64,6 +57,7 @@
android:targetActivity=".activities.habits.list.ListHabitsActivity">
+
@@ -75,7 +69,6 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
-
@@ -83,12 +76,10 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity"/>
-
-
@@ -96,7 +87,6 @@
-
@@ -116,7 +106,6 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_checkmark_info"/>
-
@@ -128,7 +117,6 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_history_info"/>
-
@@ -140,7 +128,6 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_score_info"/>
-
@@ -152,7 +139,6 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_streak_info"/>
-
@@ -164,33 +150,35 @@
android:name="android.appwidget.provider"
android:resource="@xml/widget_frequency_info"/>
-
-
+
+
-
+
+
-
+
+
@@ -211,8 +199,7 @@
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
-
+
@@ -232,8 +219,14 @@
android:grantUriPermissions="true">
+ android:resource="@xml/file_paths"/>
+
+
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
index 6101b60e5..1c68b49ff 100644
--- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
+++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsActivity.java
@@ -19,6 +19,7 @@
package org.isoron.uhabits.activities.habits.list;
+import android.content.*;
import android.os.*;
import org.isoron.uhabits.*;
@@ -47,8 +48,6 @@ public class ListHabitsActivity extends BaseActivity
private MidnightTimer midnightTimer;
- private SyncManager syncManager;
-
public ListHabitsComponent getListHabitsComponent()
{
return component;
@@ -84,7 +83,9 @@ public class ListHabitsActivity extends BaseActivity
rootView.setController(controller, selectionMenu);
midnightTimer = component.getMidnightTimer();
- syncManager = app.getComponent().getSyncManager();
+
+ if(prefs.isSyncFeatureEnabled())
+ startService(new Intent(this, SyncService.class));
setScreen(screen);
controller.onStartup();
@@ -93,7 +94,6 @@ public class ListHabitsActivity extends BaseActivity
@Override
protected void onPause()
{
- syncManager.stopListening();
midnightTimer.onPause();
screen.onDettached();
adapter.cancelRefresh();
@@ -107,7 +107,6 @@ public class ListHabitsActivity extends BaseActivity
screen.onAttached();
rootView.postInvalidate();
midnightTimer.onResume();
- syncManager.startListening();
if (prefs.getTheme() == ThemeSwitcher.THEME_DARK &&
prefs.isPureBlackEnabled() != pureBlack)
diff --git a/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java b/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
index 560b7647a..878aaa681 100644
--- a/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
+++ b/app/src/main/java/org/isoron/uhabits/preferences/Preferences.java
@@ -242,9 +242,10 @@ public class Preferences
}
if (key.equals("pref_sticky_notifications"))
- {
for (Listener l : listeners) l.onNotificationsChanged();
- }
+
+ if (key.equals("pref_feature_sync"))
+ for (Listener l : listeners) l.onSyncFeatureChanged();
}
public void removeListener(Listener listener)
@@ -306,5 +307,7 @@ public class Preferences
default void onCheckmarkOrderChanged() {}
default void onNotificationsChanged() {}
+
+ default void onSyncFeatureChanged() {}
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java b/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java
index 55243f015..4d10d7ae5 100644
--- a/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java
+++ b/app/src/main/java/org/isoron/uhabits/receivers/PebbleReceiver.java
@@ -30,6 +30,8 @@ import com.getpebble.android.kit.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.models.*;
+import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.sync.*;
import org.isoron.uhabits.tasks.*;
import org.isoron.uhabits.utils.*;
@@ -51,6 +53,8 @@ public class PebbleReceiver extends PebbleDataReceiver
private HabitList filteredHabits;
+ private Preferences prefs;
+
public PebbleReceiver()
{
super(WATCHAPP_UUID);
@@ -69,9 +73,14 @@ public class PebbleReceiver extends PebbleDataReceiver
HabitsApplication app =
(HabitsApplication) context.getApplicationContext();
- commandRunner = app.getComponent().getCommandRunner();
- taskRunner = app.getComponent().getTaskRunner();
- allHabits = app.getComponent().getHabitList();
+ AppComponent component = app.getComponent();
+ commandRunner = component.getCommandRunner();
+ taskRunner = component.getTaskRunner();
+ allHabits = component.getHabitList();
+ prefs = component.getPreferences();
+
+ if(prefs.isSyncFeatureEnabled())
+ context.startService(new Intent(context, SyncService.class));
HabitMatcher build = new HabitMatcherBuilder()
.setArchivedAllowed(false)
diff --git a/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java b/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java
index 763c41871..e8dd70729 100644
--- a/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java
+++ b/app/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java
@@ -24,6 +24,8 @@ import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.intents.*;
+import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.sync.*;
import dagger.*;
@@ -59,6 +61,10 @@ public class WidgetReceiver extends BroadcastReceiver
IntentParser parser = app.getComponent().getIntentParser();
WidgetController controller = component.getWidgetController();
+ Preferences prefs = app.getComponent().getPreferences();
+
+ if(prefs.isSyncFeatureEnabled())
+ context.startService(new Intent(context, SyncService.class));
try
{
diff --git a/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java b/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
index f5320d430..124af8682 100644
--- a/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
+++ b/app/src/main/java/org/isoron/uhabits/sync/SyncManager.java
@@ -26,17 +26,13 @@ import android.util.*;
import org.isoron.uhabits.*;
import org.isoron.uhabits.commands.*;
import org.isoron.uhabits.preferences.*;
+import org.isoron.uhabits.utils.*;
import org.json.*;
-import java.io.*;
import java.net.*;
-import java.security.*;
-import java.security.cert.Certificate;
-import java.security.cert.*;
import java.util.*;
import javax.inject.*;
-import javax.net.ssl.*;
import io.socket.client.*;
import io.socket.client.Socket;
@@ -58,8 +54,10 @@ public class SyncManager implements CommandRunner.Listener
public static final String EVENT_POST_EVENT = "postEvent";
+ @NonNull
private String clientId;
+ @NonNull
private String groupKey;
@NonNull
@@ -69,16 +67,17 @@ public class SyncManager implements CommandRunner.Listener
private LinkedList pendingConfirmation;
@NonNull
- private List pendingEmit;
+ private LinkedList pendingEmit;
private boolean readyToEmit = false;
- private Context context;
-
+ @NonNull
private final Preferences prefs;
+ @NonNull
private CommandRunner commandRunner;
+ @NonNull
private CommandParser commandParser;
@Inject
@@ -87,54 +86,26 @@ public class SyncManager implements CommandRunner.Listener
@NonNull CommandRunner commandRunner,
@NonNull CommandParser commandParser)
{
- this.context = context;
this.prefs = prefs;
this.commandRunner = commandRunner;
this.commandParser = commandParser;
pendingConfirmation = new LinkedList<>();
- pendingEmit = Event.getAll();
+ pendingEmit = new LinkedList<>(Event.getAll());
groupKey = prefs.getSyncKey();
clientId = prefs.getSyncClientId();
String serverURL = prefs.getSyncAddress();
Log.d("SyncManager", clientId);
-
- try
- {
- IO.setDefaultSSLContext(getCACertSSLContext());
- socket = IO.socket(serverURL);
- logSocketEvent(socket, EVENT_CONNECT, "Connected");
- logSocketEvent(socket, EVENT_CONNECT_TIMEOUT, "Connect timeout");
- logSocketEvent(socket, EVENT_CONNECTING, "Connecting...");
- logSocketEvent(socket, EVENT_CONNECT_ERROR, "Connect error");
- logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
- logSocketEvent(socket, EVENT_RECONNECT, "Reconnected");
- logSocketEvent(socket, EVENT_RECONNECT_ATTEMPT, "Reconnecting...");
- logSocketEvent(socket, EVENT_RECONNECT_ERROR, "Reconnect error");
- logSocketEvent(socket, EVENT_RECONNECT_FAILED, "Reconnect failed");
- logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
- logSocketEvent(socket, EVENT_PING, "Ping");
- logSocketEvent(socket, EVENT_PONG, "Pong");
-
- socket.on(EVENT_CONNECT, new OnConnectListener());
- socket.on(EVENT_DISCONNECT, new OnDisconnectListener());
- socket.on(EVENT_EXECUTE_EVENT, new OnExecuteCommandListener());
- socket.on(EVENT_AUTH_OK, new OnAuthOKListener());
- socket.on(EVENT_FETCH_OK, new OnFetchOKListener());
- }
- catch (URISyntaxException e)
- {
- throw new RuntimeException(e);
- }
+ connect(context, serverURL);
}
@Override
public void onCommandExecuted(@NonNull Command command,
@Nullable Long refreshKey)
{
- if(command.isRemote()) return;
+ if (command.isRemote()) return;
JSONObject msg = command.toJson();
Long now = new Date().getTime();
@@ -149,8 +120,8 @@ public class SyncManager implements CommandRunner.Listener
public void startListening()
{
- if(!prefs.isSyncFeatureEnabled()) return;
- if(groupKey.isEmpty()) return;
+ if (!prefs.isSyncFeatureEnabled()) return;
+ if (groupKey.isEmpty()) return;
socket.connect();
commandRunner.addListener(this);
@@ -162,47 +133,52 @@ public class SyncManager implements CommandRunner.Listener
socket.close();
}
- private void emitPending()
+ private void connect(@AppContext @NonNull Context context, String serverURL)
{
try
{
- for (Event e : pendingEmit)
- {
- Log.i("SyncManager", "Emitting: " + e.message);
- socket.emit(EVENT_POST_EVENT, new JSONObject(e.message));
- pendingConfirmation.add(e);
- }
+ IO.setDefaultSSLContext(SSLUtils.getCACertSSLContext(context));
+ socket = IO.socket(serverURL);
- pendingEmit.clear();
+ logSocketEvent(socket, EVENT_CONNECT, "Connected");
+ logSocketEvent(socket, EVENT_CONNECT_TIMEOUT, "Connect timeout");
+ logSocketEvent(socket, EVENT_CONNECTING, "Connecting...");
+ logSocketEvent(socket, EVENT_CONNECT_ERROR, "Connect error");
+ logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
+ logSocketEvent(socket, EVENT_RECONNECT, "Reconnected");
+ logSocketEvent(socket, EVENT_RECONNECT_ATTEMPT, "Reconnecting...");
+ logSocketEvent(socket, EVENT_RECONNECT_ERROR, "Reconnect error");
+ logSocketEvent(socket, EVENT_RECONNECT_FAILED, "Reconnect failed");
+ logSocketEvent(socket, EVENT_DISCONNECT, "Disconnected");
+ logSocketEvent(socket, EVENT_PING, "Ping");
+ logSocketEvent(socket, EVENT_PONG, "Pong");
+
+ socket.on(EVENT_CONNECT, new OnConnectListener());
+ socket.on(EVENT_DISCONNECT, new OnDisconnectListener());
+ socket.on(EVENT_EXECUTE_EVENT, new OnExecuteCommandListener());
+ socket.on(EVENT_AUTH_OK, new OnAuthOKListener());
+ socket.on(EVENT_FETCH_OK, new OnFetchOKListener());
}
- catch (JSONException e)
+ catch (URISyntaxException e)
{
throw new RuntimeException(e);
}
}
- private SSLContext getCACertSSLContext()
+ private void emitPending()
{
try
{
- CertificateFactory cf = CertificateFactory.getInstance("X.509");
- InputStream caInput = context.getAssets().open("cacert.pem");
- Certificate ca = cf.generateCertificate(caInput);
-
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
- ks.load(null, null);
- ks.setCertificateEntry("ca", ca);
-
- TrustManagerFactory tmf = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- tmf.init(ks);
-
- SSLContext ctx = SSLContext.getInstance("TLS");
- ctx.init(null, tmf.getTrustManagers(), null);
+ for (Event e : pendingEmit)
+ {
+ Log.i("SyncManager", "Emitting: " + e.message);
+ socket.emit(EVENT_POST_EVENT, new JSONObject(e.message));
+ pendingConfirmation.add(e);
+ }
- return ctx;
+ pendingEmit.clear();
}
- catch (Exception e)
+ catch (JSONException e)
{
throw new RuntimeException(e);
}
@@ -283,6 +259,8 @@ public class SyncManager implements CommandRunner.Listener
public void call(Object... args)
{
readyToEmit = false;
+ for(Event e : pendingConfirmation) pendingEmit.add(e);
+ pendingConfirmation.clear();
}
}
diff --git a/app/src/main/java/org/isoron/uhabits/sync/SyncService.java b/app/src/main/java/org/isoron/uhabits/sync/SyncService.java
new file mode 100644
index 000000000..e65bcbc00
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/sync/SyncService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * This file is part of Loop Habit Tracker.
+ *
+ * Loop Habit Tracker is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Loop Habit Tracker is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+package org.isoron.uhabits.sync;
+
+import android.app.*;
+import android.content.*;
+import android.os.*;
+import android.support.v7.app.*;
+
+import org.isoron.uhabits.*;
+import org.isoron.uhabits.preferences.*;
+
+public class SyncService extends Service implements Preferences.Listener
+{
+ private SyncManager syncManager;
+
+ private Preferences prefs;
+
+ public SyncService()
+ {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent)
+ {
+ return null;
+ }
+
+ @Override
+ public void onCreate()
+ {
+ Intent notificationIntent = new Intent(this, SyncService.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
+
+ Notification notification = new NotificationCompat.Builder(this)
+ .setContentTitle("Loop Habit Tracker")
+ .setContentText("Sync service running")
+ .setSmallIcon(R.drawable.ic_notification)
+ .setPriority(NotificationCompat.PRIORITY_MIN)
+ .setContentIntent(pendingIntent)
+ .build();
+
+ startForeground(99999, notification);
+
+ HabitsApplication app = (HabitsApplication) getApplicationContext();
+ syncManager = app.getComponent().getSyncManager();
+ syncManager.startListening();
+
+ prefs = app.getComponent().getPreferences();
+ prefs.addListener(this);
+ }
+
+ @Override
+ public void onSyncFeatureChanged()
+ {
+ if(!prefs.isSyncFeatureEnabled()) stopSelf();
+ }
+
+ @Override
+ public void onDestroy()
+ {
+ syncManager.stopListening();
+ }
+}
diff --git a/app/src/main/java/org/isoron/uhabits/utils/SSLUtils.java b/app/src/main/java/org/isoron/uhabits/utils/SSLUtils.java
new file mode 100644
index 000000000..812e94dee
--- /dev/null
+++ b/app/src/main/java/org/isoron/uhabits/utils/SSLUtils.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 Álinson Santos Xavier
+ *
+ * This file is part of Loop Habit Tracker.
+ *
+ * Loop Habit Tracker is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * Loop Habit Tracker is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program. If not, see .
+ */
+
+package org.isoron.uhabits.utils;
+
+import android.content.*;
+import android.support.annotation.*;
+
+import java.io.*;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.*;
+
+import javax.net.ssl.*;
+
+public abstract class SSLUtils
+{
+ public static SSLContext getCACertSSLContext(@NonNull Context context)
+ {
+ try
+ {
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ InputStream caInput = context.getAssets().open("cacert.pem");
+ Certificate ca = cf.generateCertificate(caInput);
+
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(null, null);
+ ks.setCertificateEntry("ca", ca);
+
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+ TrustManagerFactory.getDefaultAlgorithm());
+ tmf.init(ks);
+
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, tmf.getTrustManagers(), null);
+
+ return ctx;
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+}