1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-11-22 20:31:16 +00:00

Move all notification handling into common file

This commit is contained in:
Felix Ableitner 2017-10-04 00:40:59 +09:00
parent e184c43c3a
commit 4c2b325676
10 changed files with 189 additions and 157 deletions

View file

@ -3,8 +3,11 @@ package com.nutomic.syncthingandroid;
import com.nutomic.syncthingandroid.activities.FirstStartActivity;
import com.nutomic.syncthingandroid.activities.FolderPickerActivity;
import com.nutomic.syncthingandroid.activities.MainActivity;
import com.nutomic.syncthingandroid.receiver.AppConfigReceiver;
import com.nutomic.syncthingandroid.service.DeviceStateHolder;
import com.nutomic.syncthingandroid.service.EventProcessor;
import com.nutomic.syncthingandroid.service.NotificationHandler;
import com.nutomic.syncthingandroid.service.RestApi;
import com.nutomic.syncthingandroid.service.SyncthingRunnable;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.util.Languages;
@ -26,4 +29,7 @@ public interface DaggerComponent {
void inject(DeviceStateHolder deviceStateHolder);
void inject(EventProcessor eventProcessor);
void inject(SyncthingRunnable syncthingRunnable);
void inject(NotificationHandler notificationHandler);
void inject(AppConfigReceiver appConfigReceiver);
void inject(RestApi restApi);
}

View file

@ -3,6 +3,8 @@ package com.nutomic.syncthingandroid;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.nutomic.syncthingandroid.service.NotificationHandler;
import javax.inject.Singleton;
import dagger.Module;
@ -22,4 +24,10 @@ public class SyncthingModule {
public SharedPreferences getPreferences() {
return PreferenceManager.getDefaultSharedPreferences(mApp);
}
@Provides
@Singleton
public NotificationHandler getNotificationHandler() {
return new NotificationHandler(mApp);
}
}

View file

@ -1,16 +1,10 @@
package com.nutomic.syncthingandroid.activities;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.service.SyncthingService;
import com.nutomic.syncthingandroid.service.NotificationHandler;
/**
* Shows restart dialog.
@ -20,8 +14,6 @@ import com.nutomic.syncthingandroid.service.SyncthingService;
*/
public class RestartActivity extends SyncthingActivity {
public static final int NOTIFICATION_RESTART = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -48,20 +40,7 @@ public class RestartActivity extends SyncthingActivity {
* Creates a notification prompting the user to restart the app.
*/
private void createRestartNotification() {
Intent intent = new Intent(this, SyncthingService.class)
.setAction(SyncthingService.ACTION_RESTART);
PendingIntent pi = PendingIntent.getService(this, 0, intent, 0);
Notification n = new NotificationCompat.Builder(this)
.setContentTitle(getString(R.string.restart_title))
.setContentText(getString(R.string.restart_notification_text))
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentIntent(pi)
.build();
n.flags |= Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
NotificationManager nm = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_RESTART, n);
new NotificationHandler(getService()).showRestartNotification();
getApi().setRestartPostponed();
}

View file

@ -1,25 +1,21 @@
package com.nutomic.syncthingandroid.receiver;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.activities.MainActivity;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.service.NotificationHandler;
import com.nutomic.syncthingandroid.service.SyncthingService;
import javax.inject.Inject;
/**
* Broadcast-receiver to control and configure SyncThing remotely
*
* Created by sqrt-1764 on 25.03.16.
*/
public class AppConfigReceiver extends BroadcastReceiver {
private static final int ID_NOTIFICATION_BACKGROUND_ACTIVE = 3;
/**
* Start the Syncthing-Service
@ -33,8 +29,11 @@ public class AppConfigReceiver extends BroadcastReceiver {
*/
public static final String ACTION_STOP = "com.nutomic.syncthingandroid.action.STOP";
@Inject NotificationHandler mNotificationHandler;
@Override
public void onReceive(Context context, Intent intent) {
((SyncthingApp) context.getApplicationContext()).component().inject(this);
switch (intent.getAction()) {
case ACTION_START:
context.startService(new Intent(context, SyncthingService.class));
@ -42,31 +41,7 @@ public class AppConfigReceiver extends BroadcastReceiver {
case ACTION_STOP:
if (SyncthingService.alwaysRunInBackground(context)) {
final String msg = context.getString(R.string.appconfig_receiver_background_enabled);
Context appContext = context.getApplicationContext();
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
.setContentText(msg)
.setTicker(msg)
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentTitle(context.getText(context.getApplicationInfo().labelRes))
.setSmallIcon(R.drawable.ic_stat_notify)
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(appContext,
0,
new Intent(appContext, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
nb.setCategory(Notification.CATEGORY_ERROR); // Only supported in API 21 or better
}
NotificationManager nm =
(NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(ID_NOTIFICATION_BACKGROUND_ACTIVE, nb.build());
mNotificationHandler.showStopSyncthingWarningNotification();
} else {
context.stopService(new Intent(context, SyncthingService.class));
}

View file

@ -1,7 +1,5 @@
package com.nutomic.syncthingandroid.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@ -9,8 +7,6 @@ import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import com.annimon.stream.Stream;
@ -56,6 +52,7 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
private final Context mContext;
private final RestApi mApi;
@Inject SharedPreferences mPreferences;
@Inject NotificationHandler mNotificationHandler;
public EventProcessor(Context context, RestApi api) {
((SyncthingApp) context.getApplicationContext()).component().inject(this);
@ -200,20 +197,9 @@ public class EventProcessor implements SyncthingService.OnWebGuiAvailableListene
}
private void notify(String text, PendingIntent pi) {
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
Notification n = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(text)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text))
.setContentIntent(pi)
.setSmallIcon(R.drawable.ic_stat_notify)
.setAutoCancel(true)
.build();
// HACK: Use a random, deterministic ID between 1000 and 2000 to avoid duplicate
// notifications.
int notificationId = 1000 + text.hashCode() % 1000;
nm.notify(notificationId, n);
mNotificationHandler.showEventNotification(text, pi, notificationId);
}
}

View file

@ -0,0 +1,145 @@
package com.nutomic.syncthingandroid.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.activities.FirstStartActivity;
import com.nutomic.syncthingandroid.activities.MainActivity;
import javax.inject.Inject;
public class NotificationHandler {
private static final int ID_PERSISTENT = 1;
private static final int ID_RESTART = 2;
private static final int ID_STOP_BACKGROUND_WARNING = 3;
private static final int ID_CRASH = 9;
private final Context mContext;
@Inject SharedPreferences mPreferences;
private final NotificationManager mNotificationManager;
public NotificationHandler(Context context) {
((SyncthingApp) context.getApplicationContext()).component().inject(this);
mContext = context;
mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
}
/**
* Shows or hides the persistent notification based on running state and
* {@link SyncthingService#PREF_NOTIFICATION_TYPE}.
*/
public void updatePersistentNotification(SyncthingService service, SyncthingService.State currentState) {
String type = mPreferences.getString(SyncthingService.PREF_NOTIFICATION_TYPE, "low_priority");
boolean foreground = mPreferences.getBoolean(SyncthingService.PREF_FOREGROUND_SERVICE, false);
if ("none".equals(type) && foreground) {
// foreground priority requires any notification
// so this ensures that we either have a "default" or "low_priority" notification,
// but not "none".
type = "low_priority";
}
if ((currentState == SyncthingService.State.ACTIVE || currentState == SyncthingService.State.STARTING) &&
!type.equals("none")) {
// Launch FirstStartActivity instead of MainActivity so we can request permission if
// necessary.
PendingIntent pi = PendingIntent.getActivity(mContext, 0,
new Intent(mContext, FirstStartActivity.class), 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.syncthing_active))
.setSmallIcon(R.drawable.ic_stat_notify)
.setOngoing(true)
.setContentIntent(pi);
if (type.equals("low_priority"))
builder.setPriority(NotificationCompat.PRIORITY_MIN);
if (foreground) {
builder.setContentText(mContext.getString(R.string.syncthing_active_foreground));
service.startForeground(ID_PERSISTENT, builder.build());
} else {
service.stopForeground(false); // ensure no longer running with foreground priority
mNotificationManager.notify(ID_PERSISTENT, builder.build());
}
} else {
// ensure no longer running with foreground priority
cancelPersistentNotification(service);
}
}
public void cancelPersistentNotification(SyncthingService service) {
service.stopForeground(false);
mNotificationManager.cancel(ID_PERSISTENT);
}
public void showCrashedNotification(Intent intent) {
if (mPreferences.getBoolean("notify_crashes", false)) {
Notification n = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.notification_crash_title))
.setContentText(mContext.getString(R.string.notification_crash_text))
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0))
.setAutoCancel(true)
.build();
mNotificationManager.notify(ID_CRASH, n);
}
}
public void showEventNotification(String text, PendingIntent pi, int id) {
Notification n = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(text)
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(text))
.setContentIntent(pi)
.setSmallIcon(R.drawable.ic_stat_notify)
.setAutoCancel(true)
.build();
mNotificationManager.notify(id, n);
}
public void showRestartNotification() {
Intent intent = new Intent(mContext, SyncthingService.class)
.setAction(SyncthingService.ACTION_RESTART);
PendingIntent pi = PendingIntent.getService(mContext, 0, intent, 0);
Notification n = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.restart_title))
.setContentText(mContext.getString(R.string.restart_notification_text))
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentIntent(pi)
.build();
n.flags |= Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(ID_RESTART, n);
}
public void cancelRestartNotification() {
mNotificationManager.cancel(ID_RESTART);
}
public void showStopSyncthingWarningNotification() {
final String msg = mContext.getString(R.string.appconfig_receiver_background_enabled);
NotificationCompat.Builder nb = new NotificationCompat.Builder(mContext)
.setContentText(msg)
.setTicker(msg)
.setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
.setContentTitle(mContext.getText(mContext.getApplicationInfo().labelRes))
.setSmallIcon(R.drawable.ic_stat_notify)
.setAutoCancel(true)
.setContentIntent(PendingIntent.getActivity(mContext, 0,
new Intent(mContext, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
nb.setCategory(Notification.CATEGORY_ERROR);
}
mNotificationManager.notify(ID_STOP_BACKGROUND_WARNING, nb.build());
}
}

View file

@ -1,7 +1,6 @@
package com.nutomic.syncthingandroid.service;
import android.app.Activity;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
@ -17,6 +16,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nutomic.syncthingandroid.BuildConfig;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.activities.RestartActivity;
import com.nutomic.syncthingandroid.http.GetRequest;
import com.nutomic.syncthingandroid.http.PostConfigRequest;
@ -43,6 +43,8 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import javax.inject.Inject;
/**
* Provides functions to interact with the syncthing REST API.
*/
@ -100,8 +102,11 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
*/
private final HashMap<String, Model> mCachedModelInfo = new HashMap<>();
@Inject NotificationHandler mNotificationHandler;
public RestApi(Context context, URL url, String apiKey, OnApiAvailableListener apiListener,
OnConfigChangedListener configListener) {
((SyncthingApp) context.getApplicationContext()).component().inject(this);
mContext = context;
mUrl = url;
mApiKey = apiKey;
@ -195,13 +200,8 @@ public class RestApi implements SyncthingService.OnWebGuiAvailableListener,
});
}
/**
* Stops syncthing and cancels notification.
*/
public void shutdown() {
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(RestartActivity.NOTIFICATION_RESTART);
mNotificationHandler.cancelRestartNotification();
mRestartPostponed = false;
}

View file

@ -1,21 +1,16 @@
package com.nutomic.syncthingandroid.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Environment;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
import android.util.Log;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import java.io.BufferedReader;
@ -50,7 +45,6 @@ public class SyncthingRunnable implements Runnable {
private static final String TAG_KILL = "SyncthingRunnableKill";
private static final String BINARY_NAME = "libsyncthing.so";
private static final int LOG_FILE_MAX_LINES = 10;
private static final int NOTIFICATION_ID_CRASH = 9;
private static final AtomicReference<Process> mSyncthing = new AtomicReference<>();
private final Context mContext;
@ -59,6 +53,7 @@ public class SyncthingRunnable implements Runnable {
private final File mLogFile;
@Inject SharedPreferences mPreferences;
private final boolean mUseRoot;
@Inject NotificationHandler mNotificationHandler;
public enum Command {
generate, // Generate keys, a config file and immediately exit.
@ -146,22 +141,10 @@ public class SyncthingRunnable implements Runnable {
break;
default:
Log.w(TAG, "Syncthing has crashed (exit code " + ret + ")");
if (mPreferences.getBoolean("notify_crashes", false)) {
// Show notification to inform user about crash.
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(mLogFile), "text/plain");
Notification n = new NotificationCompat.Builder(mContext)
.setContentTitle(mContext.getString(R.string.notification_crash_title))
.setContentText(mContext.getString(R.string.notification_crash_text))
.setSmallIcon(R.drawable.ic_stat_notify)
.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0))
.setAutoCancel(true)
.build();
NotificationManager nm = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(NOTIFICATION_ID_CRASH, n);
}
// Show notification to inform user about crash.
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(mLogFile), "text/plain");
mNotificationHandler.showCrashedNotification(intent);
}
} catch (IOException | InterruptedException e) {
Log.e(TAG, "Failed to execute syncthing binary or read output", e);

View file

@ -1,7 +1,5 @@
package com.nutomic.syncthingandroid.service;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -16,7 +14,6 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
@ -25,7 +22,6 @@ import com.annimon.stream.Stream;
import com.google.common.io.Files;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.SyncthingApp;
import com.nutomic.syncthingandroid.activities.FirstStartActivity;
import com.nutomic.syncthingandroid.http.PollWebGuiAvailableTask;
import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.receiver.NetworkReceiver;
@ -95,12 +91,10 @@ public class SyncthingService extends Service implements
public static final String PREF_SYNC_ONLY_CHARGING = "sync_only_charging";
public static final String PREF_RESPECT_BATTERY_SAVING = "respect_battery_saving";
public static final String PREF_USE_ROOT = "use_root";
private static final String PREF_NOTIFICATION_TYPE = "notification_type";
public static final String PREF_NOTIFICATION_TYPE = "notification_type";
public static final String PREF_USE_WAKE_LOCK = "wakelock_while_binary_running";
public static final String PREF_FOREGROUND_SERVICE = "run_as_foreground_service";
private static final int NOTIFICATION_ACTIVE = 1;
/**
* Callback for when the Syncthing web interface becomes first available after service start.
*/
@ -149,6 +143,8 @@ public class SyncthingService extends Service implements
private final NetworkReceiver mNetworkReceiver = new NetworkReceiver();
private final BroadcastReceiver mPowerSaveModeChangedReceiver = new PowerSaveModeChangedReceiver();
@Inject NotificationHandler mNotificationHandler;
/**
* Object that can be locked upon when accessing mCurrentState
* Currently used to male onDestroy() and PollWebGuiAvailableTaskImpl.onPostExcecute() tread-safe
@ -220,53 +216,10 @@ public class SyncthingService extends Service implements
}
}
/**
* Shows or hides the persistent notification based on running state and
* {@link #PREF_NOTIFICATION_TYPE}.
*/
private void updateNotification() {
String type = mPreferences.getString(PREF_NOTIFICATION_TYPE, "low_priority");
boolean foreground = mPreferences.getBoolean(PREF_FOREGROUND_SERVICE, false);
if ("none".equals(type) && foreground) {
// foreground priority requires any notification
// so this ensures that we either have a "default" or "low_priority" notification,
// but not "none".
type = "low_priority";
}
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if ((mCurrentState == State.ACTIVE || mCurrentState == State.STARTING) &&
!type.equals("none")) {
Context appContext = getApplicationContext();
// Launch FirstStartActivity instead of MainActivity so we can request permission if
// necessary.
PendingIntent pi = PendingIntent.getActivity(appContext, 0,
new Intent(appContext, FirstStartActivity.class), 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(appContext)
.setContentTitle(getString(R.string.syncthing_active))
.setSmallIcon(R.drawable.ic_stat_notify)
.setOngoing(true)
.setContentIntent(pi);
if (type.equals("low_priority"))
builder.setPriority(NotificationCompat.PRIORITY_MIN);
if (foreground) {
builder.setContentText(getString(R.string.syncthing_active_foreground));
startForeground(NOTIFICATION_ACTIVE, builder.build());
} else {
stopForeground(false); // ensure no longer running with foreground priority
nm.notify(NOTIFICATION_ACTIVE, builder.build());
}
} else {
// ensure no longer running with foreground priority
stopForeground(false);
nm.cancel(NOTIFICATION_ACTIVE);
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(PREF_NOTIFICATION_TYPE) || key.equals(PREF_FOREGROUND_SERVICE))
updateNotification();
mNotificationHandler.updatePersistentNotification(this, mCurrentState);
else if (key.equals(PREF_SYNC_ONLY_CHARGING) || key.equals(PREF_SYNC_ONLY_WIFI)
|| key.equals(PREF_SYNC_ONLY_WIFI_SSIDS) || key.equals(PREF_RESPECT_BATTERY_SAVING)) {
updateState();
@ -338,7 +291,6 @@ public class SyncthingService extends Service implements
pollWebGui();
mSyncthingRunnable = new SyncthingRunnable(SyncthingService.this, SyncthingRunnable.Command.main);
new Thread(mSyncthingRunnable).start();
updateNotification();
}
}
@ -409,9 +361,7 @@ public class SyncthingService extends Service implements
if (mApi != null)
mApi.shutdown();
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
stopForeground(false);
nm.cancel(NOTIFICATION_ACTIVE);
mNotificationHandler.cancelPersistentNotification(this);
Stream.of(mObservers).forEach(FolderObserver::stopWatching);
mObservers.clear();
@ -498,6 +448,7 @@ public class SyncthingService extends Service implements
*/
private void onApiChange(State newState) {
mCurrentState = newState;
mNotificationHandler.updatePersistentNotification(this, mCurrentState);
for (Iterator<OnApiChangeListener> i = mOnApiChangeListeners.iterator();
i.hasNext(); ) {
OnApiChangeListener listener = i.next();

View file

@ -8,7 +8,6 @@ import android.util.Log;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.service.SyncthingRunnable;
import com.nutomic.syncthingandroid.util.Util;
import org.mindrot.jbcrypt.BCrypt;
import org.w3c.dom.Document;