diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 90d2ef9b..222613d0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -98,7 +98,7 @@
-
+
callback = new Callback() {
- @Override
- public void onResponse(Call call,
- retrofit2.Response response) {
- if (response.isSuccessful()) {
- pushNotificationClient.subscribeToTopic(getPushNotificationTopic());
- pushNotificationClient.connect(BaseActivity.this);
- } else {
- onEnablePushNotificationsFailure(response.message());
- }
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- onEnablePushNotificationsFailure(t.getMessage());
- }
- };
- String deviceToken = pushNotificationClient.getDeviceToken();
- Session session = new Session(getDomain(), getAccessToken(), deviceToken);
- tuskyApi.register(session)
- .enqueue(callback);
- }
-
- private void onEnablePushNotificationsFailure(String message) {
- Log.e(TAG, "Enabling push notifications failed. " + message);
+ // Start up the PullNotificationService on a repeating interval.
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ String minutesString = preferences.getString("pullNotificationCheckInterval", "15");
+ long minutes = Long.valueOf(minutesString);
+ long checkInterval = 1000 * 60 * minutes;
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(this, PullNotificationService.class);
+ PendingIntent serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime(), checkInterval, serviceAlarmIntent);
}
protected void disablePushNotifications() {
- Callback callback = new Callback() {
- @Override
- public void onResponse(Call call,
- retrofit2.Response response) {
- if (response.isSuccessful()) {
- pushNotificationClient.unsubscribeToTopic(getPushNotificationTopic());
- } else {
- onDisablePushNotificationsFailure();
- }
- }
-
- @Override
- public void onFailure(Call call, Throwable t) {
- onDisablePushNotificationsFailure();
- }
- };
- String deviceToken = pushNotificationClient.getDeviceToken();
- Session session = new Session(getDomain(), getAccessToken(), deviceToken);
- tuskyApi.unregister(session)
- .enqueue(callback);
- }
-
- private void onDisablePushNotificationsFailure() {
- Log.e(TAG, "Disabling push notifications failed.");
- }
-
- private String getPushNotificationTopic() {
- return String.format("%s/%s/#", getDomain(), getAccessToken());
- }
-
- private String getDomain() {
- return getPrivatePreferences()
- .getString("domain", null);
+ // Cancel the repeating call for "pull" notifications.
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(this, PullNotificationService.class);
+ PendingIntent serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.cancel(serviceAlarmIntent);
+ }
+
+ protected void clearNotifications() {
+ SharedPreferences notificationPreferences = getApplicationContext()
+ .getSharedPreferences("Notifications", MODE_PRIVATE);
+ notificationPreferences.edit()
+ .putString("current", "[]")
+ .apply();
+
+ NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ manager.cancel(PullNotificationService.NOTIFY_ID);
+ }
+
+ protected void setPullNotificationCheckInterval(long minutes) {
+ long checkInterval = 1000 * 60 * minutes;
+ AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
+ Intent intent = new Intent(this, PullNotificationService.class);
+ PendingIntent serviceAlarmIntent = PendingIntent.getService(this, SERVICE_REQUEST_CODE,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ alarmManager.cancel(serviceAlarmIntent);
+ alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+ SystemClock.elapsedRealtime(), checkInterval, serviceAlarmIntent);
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
index 7423d0f4..a4d77f02 100644
--- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java
@@ -210,17 +210,19 @@ public class MainActivity extends BaseActivity {
composeButton = floatingBtn;
}
+ @Override
+ public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
+ ArrayList pageHistoryList = new ArrayList<>();
+ pageHistoryList.addAll(pageHistory);
+ outState.putIntegerArrayList("pageHistory", pageHistoryList);
+ super.onSaveInstanceState(outState, outPersistentState);
+ }
+
@Override
protected void onResume() {
super.onResume();
- SharedPreferences notificationPreferences = getApplicationContext()
- .getSharedPreferences("Notifications", MODE_PRIVATE);
- notificationPreferences.edit()
- .putString("current", "[]")
- .apply();
-
- pushNotificationClient.clearNotifications(this);
+ clearNotifications();
/* After editing a profile, the profile header in the navigation drawer needs to be
* refreshed */
@@ -234,11 +236,50 @@ public class MainActivity extends BaseActivity {
}
@Override
- public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
- ArrayList pageHistoryList = new ArrayList<>();
- pageHistoryList.addAll(pageHistory);
- outState.putIntegerArrayList("pageHistory", pageHistoryList);
- super.onSaveInstanceState(outState, outPersistentState);
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
+ Intent intent = new Intent(TimelineReceiver.Types.STATUS_COMPOSED);
+ LocalBroadcastManager.getInstance(getApplicationContext())
+ .sendBroadcast(intent);
+ }
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (drawer != null && drawer.isDrawerOpen()) {
+ drawer.closeDrawer();
+ } else if (pageHistory.size() < 2) {
+ super.onBackPressed();
+ } else {
+ pageHistory.pop();
+ viewPager.setCurrentItem(pageHistory.peek());
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_MENU: {
+ if (drawer.isDrawerOpen()) {
+ drawer.closeDrawer();
+ } else {
+ drawer.openDrawer();
+ }
+ return true;
+ }
+ case KeyEvent.KEYCODE_SEARCH: {
+ startActivity(new Intent(this, SearchActivity.class));
+ return true;
+ }
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ // Fix for GitHub issues #190, #259 (MainActivity won't restart on screen rotation.)
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
}
private void tintTab(TabLayout.Tab tab, boolean tinted) {
@@ -465,51 +506,4 @@ public class MainActivity extends BaseActivity {
private void onFetchUserInfoFailure(Exception exception) {
Log.e(TAG, "Failed to fetch user info. " + exception.getMessage());
}
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- if (requestCode == COMPOSE_RESULT && resultCode == ComposeActivity.RESULT_OK) {
- Intent intent = new Intent(TimelineReceiver.Types.STATUS_COMPOSED);
- LocalBroadcastManager.getInstance(getApplicationContext())
- .sendBroadcast(intent);
- }
- super.onActivityResult(requestCode, resultCode, data);
- }
-
- @Override
- public void onBackPressed() {
- if (drawer != null && drawer.isDrawerOpen()) {
- drawer.closeDrawer();
- } else if (pageHistory.size() < 2) {
- super.onBackPressed();
- } else {
- pageHistory.pop();
- viewPager.setCurrentItem(pageHistory.peek());
- }
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- switch (keyCode) {
- case KeyEvent.KEYCODE_MENU: {
- if (drawer.isDrawerOpen()) {
- drawer.closeDrawer();
- } else {
- drawer.openDrawer();
- }
- return true;
- }
- case KeyEvent.KEYCODE_SEARCH: {
- startActivity(new Intent(this, SearchActivity.class));
- return true;
- }
- }
- return super.onKeyDown(keyCode, event);
- }
-
- // Fix for GitHub issues #190, #259 (MainActivity won't restart on screen rotation.)
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
index 58c18588..716747ee 100644
--- a/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
+++ b/app/src/main/java/com/keylesspalace/tusky/PreferencesActivity.java
@@ -59,24 +59,34 @@ public class PreferencesActivity extends BaseActivity
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals("lightTheme")) {
- themeSwitched = true;
- // recreate() could be used instead, but it doesn't have an animation B).
- Intent intent = getIntent();
- intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
- Bundle savedInstanceState = new Bundle();
- saveInstanceState(savedInstanceState);
- intent.putExtras(savedInstanceState);
- startActivity(intent);
- finish();
- overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
- } else if (key.equals("notificationsEnabled")) {
- boolean notificationsEnabled = sharedPreferences.getBoolean("notificationsEnabled", true);
-
- if (notificationsEnabled) {
- enablePushNotifications();
- } else {
- disablePushNotifications();
+ switch (key) {
+ case "lightTheme": {
+ themeSwitched = true;
+ // recreate() could be used instead, but it doesn't have an animation B).
+ Intent intent = getIntent();
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ Bundle savedInstanceState = new Bundle();
+ saveInstanceState(savedInstanceState);
+ intent.putExtras(savedInstanceState);
+ startActivity(intent);
+ finish();
+ overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
+ break;
+ }
+ case "notificationsEnabled": {
+ boolean enabled = sharedPreferences.getBoolean("notificationsEnabled", true);
+ if (enabled) {
+ enablePushNotifications();
+ } else {
+ disablePushNotifications();
+ }
+ break;
+ }
+ case "pullNotificationCheckInterval": {
+ String s = sharedPreferences.getString("pullNotificationCheckInterval", "15");
+ long minutes = Long.valueOf(s);
+ setPullNotificationCheckInterval(minutes);
+ break;
}
}
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/service/PullNotificationService.java b/app/src/main/java/com/keylesspalace/tusky/service/PullNotificationService.java
new file mode 100644
index 00000000..fa4a18c0
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/service/PullNotificationService.java
@@ -0,0 +1,136 @@
+/* Copyright 2017 Andrew Dawson
+ *
+ * This file is a part of Tusky.
+ *
+ * This program 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.
+ *
+ * Tusky 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 Tusky; if not,
+ * see . */
+
+package com.keylesspalace.tusky.service;
+
+import android.app.IntentService;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.text.Spanned;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.keylesspalace.tusky.R;
+import com.keylesspalace.tusky.entity.Notification;
+import com.keylesspalace.tusky.json.SpannedTypeAdapter;
+import com.keylesspalace.tusky.json.StringWithEmoji;
+import com.keylesspalace.tusky.json.StringWithEmojiTypeAdapter;
+import com.keylesspalace.tusky.network.MastodonApi;
+import com.keylesspalace.tusky.util.OkHttpUtils;
+import com.keylesspalace.tusky.util.NotificationMaker;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import okhttp3.Interceptor;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+import retrofit2.converter.gson.GsonConverterFactory;
+
+public class PullNotificationService extends IntentService {
+ public static final int NOTIFY_ID = 6; // This is an arbitrary number.
+
+ private MastodonApi mastodonApi;
+
+ public PullNotificationService() {
+ super("Tusky Pull Notification Service");
+ }
+
+ @Override
+ protected void onHandleIntent(Intent intent) {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext());
+ boolean enabled = preferences.getBoolean("notificationsEnabled", true);
+ if (!enabled) {
+ return;
+ }
+
+ createMastodonApi();
+
+ mastodonApi.notifications(null, null, null).enqueue(new Callback>() {
+ @Override
+ public void onResponse(Call> call,
+ Response> response) {
+ if (response.isSuccessful()) {
+ onNotificationsReceived(response.body());
+ }
+ }
+
+ @Override
+ public void onFailure(Call> call, Throwable t) {}
+ });
+ }
+
+ private void createMastodonApi() {
+ SharedPreferences preferences = getSharedPreferences(
+ getString(R.string.preferences_file_key), Context.MODE_PRIVATE);
+ final String domain = preferences.getString("domain", null);
+ final String accessToken = preferences.getString("accessToken", null);
+
+ OkHttpClient okHttpClient = OkHttpUtils.getCompatibleClientBuilder()
+ .addInterceptor(new Interceptor() {
+ @Override
+ public okhttp3.Response intercept(Chain chain) throws IOException {
+ Request originalRequest = chain.request();
+
+ Request.Builder builder = originalRequest.newBuilder()
+ .header("Authorization", String.format("Bearer %s", accessToken));
+
+ Request newRequest = builder.build();
+
+ return chain.proceed(newRequest);
+ }
+ })
+ .build();
+
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
+ .registerTypeAdapter(StringWithEmoji.class, new StringWithEmojiTypeAdapter())
+ .create();
+
+ Retrofit retrofit = new Retrofit.Builder()
+ .baseUrl("https://" + domain)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create(gson))
+ .build();
+
+ mastodonApi = retrofit.create(MastodonApi.class);
+ }
+
+ private void onNotificationsReceived(List notificationList) {
+ SharedPreferences notificationsPreferences = getSharedPreferences(
+ "Notifications", Context.MODE_PRIVATE);
+ Set currentIds = notificationsPreferences.getStringSet(
+ "current_ids", new HashSet());
+ for (Notification notification : notificationList) {
+ String id = notification.id;
+ if (!currentIds.contains(id)) {
+ currentIds.add(id);
+ NotificationMaker.make(this, NOTIFY_ID, notification);
+ }
+ }
+ notificationsPreferences.edit()
+ .putStringSet("current_ids", currentIds)
+ .apply();
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
index e347a5ea..5232cd6f 100644
--- a/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
+++ b/app/src/main/java/com/keylesspalace/tusky/util/NotificationMaker.java
@@ -41,7 +41,7 @@ import com.squareup.picasso.Target;
import org.json.JSONArray;
import org.json.JSONException;
-class NotificationMaker {
+public class NotificationMaker {
public static final String TAG = "NotificationMaker";
diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml
new file mode 100644
index 00000000..8552a0df
--- /dev/null
+++ b/app/src/main/res/values/array.xml
@@ -0,0 +1,27 @@
+
+
+
+ - 5 minutes
+ - 10 minutes
+ - 15 minutes
+ - 20 minutes
+ - 25 minutes
+ - 30 minutes
+ - 45 minutes
+ - 1 hour
+ - 2 hours
+
+
+
+ - 5
+ - 10
+ - 15
+ - 20
+ - 25
+ - 30
+ - 45
+ - 60
+ - 120
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 518ce9f7..a25916c3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -146,7 +146,8 @@
Notifications
Edit Notifications
- Push notifications
+ Notifications
+ Check Interval
Alerts
Notify with a sound
Notify with vibration
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 6b2d0747..214ad9bf 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -54,6 +54,12 @@
android:title="@string/pref_title_notifications_enabled"
android:defaultValue="true" />
+
+