From 293d838d807b3c1581d03c07a0d76ce608d7af2e Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 15 Apr 2017 16:34:15 -0400 Subject: [PATCH] Move Sync to foreground service --- app/src/main/AndroidManifest.xml | 53 ++++----- .../habits/list/ListHabitsActivity.java | 9 +- .../uhabits/preferences/Preferences.java | 7 +- .../uhabits/receivers/PebbleReceiver.java | 15 ++- .../uhabits/receivers/WidgetReceiver.java | 6 + .../org/isoron/uhabits/sync/SyncManager.java | 112 +++++++----------- .../org/isoron/uhabits/sync/SyncService.java | 81 +++++++++++++ .../org/isoron/uhabits/utils/SSLUtils.java | 61 ++++++++++ 8 files changed, 237 insertions(+), 107 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/sync/SyncService.java create mode 100644 app/src/main/java/org/isoron/uhabits/utils/SSLUtils.java 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); + } + } + +}