diff --git a/app/build.gradle b/app/build.gradle index 7624ea62c..347ec31cf 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,6 +12,14 @@ android { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary true } + productFlavors { + google { + buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "true" + } + fdroid { + buildConfigField "boolean", "USES_PUSH_NOTIFICATIONS", "false" + } + } buildTypes { release { minifyEnabled true @@ -51,8 +59,8 @@ dependencies { compile 'com.github.arimorty:floatingsearchview:2.0.3' compile 'com.theartofdev.edmodo:android-image-cropper:2.4.0' compile 'com.jakewharton:butterknife:8.4.0' - compile 'com.google.firebase:firebase-messaging:10.0.1' - compile 'com.google.firebase:firebase-crash:10.0.1' + googleCompile 'com.google.firebase:firebase-messaging:10.0.1' + googleCompile 'com.google.firebase:firebase-crash:10.0.1' testCompile 'junit:junit:4.12' annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' } diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml new file mode 100644 index 000000000..b7060e6e2 --- /dev/null +++ b/app/src/fdroid/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java new file mode 100644 index 000000000..f7c24c425 --- /dev/null +++ b/app/src/fdroid/java/com/keylesspalace/tusky/MessagingService.java @@ -0,0 +1,114 @@ +/* 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; + +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.entity.Notification; +import java.util.List; + +import java.io.IOException; + +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 MessagingService extends IntentService { + public static final int NOTIFY_ID = 6; // This is an arbitrary number. + + private MastodonAPI mastodonAPI; + + public MessagingService() { + 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()) { + List notificationList = response.body(); + for (Notification notification : notificationList) { + NotificationMaker.make(MessagingService.this, NOTIFY_ID, notification); + } + } + } + + @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); + } +} diff --git a/app/src/google/AndroidManifest.xml b/app/src/google/AndroidManifest.xml new file mode 100644 index 000000000..20ecbe94c --- /dev/null +++ b/app/src/google/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/google/java/com/keylesspalace/tusky/MessagingService.java b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java new file mode 100644 index 000000000..cf4e52733 --- /dev/null +++ b/app/src/google/java/com/keylesspalace/tusky/MessagingService.java @@ -0,0 +1,121 @@ +/* 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 . + * + * If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud + * Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing + * parts covered by the Google APIs Terms of Service, the licensors of this Program grant you + * additional permission to convey the resulting work. */ + +package com.keylesspalace.tusky; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.text.Spanned; + +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import com.keylesspalace.tusky.entity.Notification; + +import java.io.IOException; + +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 MessagingService extends FirebaseMessagingService { + private MastodonAPI mastodonAPI; + private static final String TAG = "MessagingService"; + public static final int NOTIFY_ID = 666; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + Log.d(TAG, remoteMessage.getFrom()); + Log.d(TAG, remoteMessage.toString()); + + String notificationId = remoteMessage.getData().get("notification_id"); + + if (notificationId == null) { + Log.e(TAG, "No notification ID in payload!!"); + return; + } + + Log.d(TAG, notificationId); + + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( + getApplicationContext()); + boolean enabled = preferences.getBoolean("notificationsEnabled", true); + if (!enabled) { + return; + } + + createMastodonAPI(); + + mastodonAPI.notification(notificationId).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + NotificationMaker.make(MessagingService.this, NOTIFY_ID, 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); + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java similarity index 97% rename from app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java rename to app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java index 9e139187f..adb478795 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java +++ b/app/src/google/java/com/keylesspalace/tusky/MyFirebaseInstanceIdService.java @@ -33,7 +33,7 @@ import retrofit2.Response; import retrofit2.Retrofit; public class MyFirebaseInstanceIdService extends FirebaseInstanceIdService { - private static final String TAG = "MyFirebaseInstanceIdService"; + private static final String TAG = "com.keylesspalace.tusky.MyFirebaseInstanceIdService"; private TuskyAPI tuskyAPI; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ee77b8949..fcf90d73a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme" android:name=".TuskyApplication"> - + @@ -72,18 +72,6 @@ android:name="com.theartofdev.edmodo.cropper.CropImageActivity" android:theme="@style/Base.Theme.AppCompat" /> - - - - - - - - - - - - () { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Enable push notifications response: " + response.message()); - } + if (BuildConfig.USES_PUSH_NOTIFICATIONS) { + tuskyAPI.register(getBaseUrl(), getAccessToken(), FirebaseInstanceId.getInstance().getToken()).enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + Log.d(TAG, "Enable push notifications response: " + response.message()); + } - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Enable push notifications failed: " + t.getMessage()); - } - }); + @Override + public void onFailure(Call call, Throwable t) { + Log.d(TAG, "Enable push notifications failed: " + t.getMessage()); + } + }); + } else { + // Start up the MessagingService on a repeating interval for "pull" notifications. + long checkInterval = 60 * 1000; // * 10; + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(this, MessagingService.class); + final int SERVICE_REQUEST_CODE = 8574603; // This number is arbitrary. + 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() { - tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback() { - @Override - public void onResponse(Call call, retrofit2.Response response) { - Log.d(TAG, "Disable push notifications response: " + response.message()); - } + if (BuildConfig.USES_PUSH_NOTIFICATIONS) { + tuskyAPI.unregister(getBaseUrl(), getAccessToken()).enqueue(new Callback() { + @Override + public void onResponse(Call call, retrofit2.Response response) { + Log.d(TAG, "Disable push notifications response: " + response.message()); + } - @Override - public void onFailure(Call call, Throwable t) { - Log.d(TAG, "Disable push notifications failed: " + t.getMessage()); - } - }); + @Override + public void onFailure(Call call, Throwable t) { + Log.d(TAG, "Disable push notifications failed: " + t.getMessage()); + } + }); + } else if (serviceAlarmIntent != null) { + // Cancel the repeating call for "pull" notifications. + AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); + alarmManager.cancel(serviceAlarmIntent); + } } } diff --git a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java b/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java index 8b00c9397..d052db34b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/BlocksAdapter.java @@ -15,7 +15,6 @@ package com.keylesspalace.tusky; -import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; diff --git a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java index 84f06334a..f0313f946 100644 --- a/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/EditProfileActivity.java @@ -1,3 +1,18 @@ +/* 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; import android.Manifest; diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java b/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java index e2b2fedcf..ba9a591fe 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java +++ b/app/src/main/java/com/keylesspalace/tusky/LinkHelper.java @@ -1,3 +1,18 @@ +/* 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; import android.preference.PreferenceManager; diff --git a/app/src/main/java/com/keylesspalace/tusky/LinkListener.java b/app/src/main/java/com/keylesspalace/tusky/LinkListener.java index de0242bc1..9e188ae42 100644 --- a/app/src/main/java/com/keylesspalace/tusky/LinkListener.java +++ b/app/src/main/java/com/keylesspalace/tusky/LinkListener.java @@ -1,3 +1,18 @@ +/* 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; interface LinkListener { diff --git a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java index f42dd72ca..d25729a29 100644 --- a/app/src/main/java/com/keylesspalace/tusky/MainActivity.java +++ b/app/src/main/java/com/keylesspalace/tusky/MainActivity.java @@ -187,7 +187,11 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove } // Setup push notifications - if (arePushNotificationsEnabled()) enablePushNotifications(); + if (arePushNotificationsEnabled()) { + enablePushNotifications(); + } else { + disablePushNotifications(); + } composeButton = floatingBtn; } @@ -203,7 +207,7 @@ public class MainActivity extends BaseActivity implements SFragment.OnUserRemove .apply(); ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))) - .cancel(MyFirebaseMessagingService.NOTIFY_ID); + .cancel(MessagingService.NOTIFY_ID); /* After editing a profile, the profile header in the navigation drawer needs to be * refreshed */ diff --git a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java b/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java deleted file mode 100644 index 6ff4d212b..000000000 --- a/app/src/main/java/com/keylesspalace/tusky/MyFirebaseMessagingService.java +++ /dev/null @@ -1,319 +0,0 @@ -/* 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 . - * - * If you modify this Program, or any covered work, by linking or combining it with Firebase Cloud - * Messaging and Firebase Crash Reporting (or a modified version of those libraries), containing - * parts covered by the Google APIs Terms of Service, the licensors of this Program grant you - * additional permission to convey the resulting work. */ - -package com.keylesspalace.tusky; - -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.preference.PreferenceManager; -import android.provider.Settings; -import android.support.v4.app.NotificationCompat; -import android.support.v4.app.TaskStackBuilder; -import android.text.Spanned; - -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.keylesspalace.tusky.entity.Notification; -import com.squareup.picasso.Picasso; -import com.squareup.picasso.Target; - -import org.json.JSONArray; -import org.json.JSONException; - -import java.io.IOException; - -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 MyFirebaseMessagingService extends FirebaseMessagingService { - private MastodonAPI mastodonAPI; - private static final String TAG = "MyFirebaseMessagingService"; - public static final int NOTIFY_ID = 666; - - @Override - public void onMessageReceived(RemoteMessage remoteMessage) { - Log.d(TAG, remoteMessage.getFrom()); - Log.d(TAG, remoteMessage.toString()); - - String notificationId = remoteMessage.getData().get("notification_id"); - - if (notificationId == null) { - Log.e(TAG, "No notification ID in payload!!"); - return; - } - - Log.d(TAG, notificationId); - - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences( - getApplicationContext()); - boolean enabled = preferences.getBoolean("notificationsEnabled", true); - if (!enabled) { - return; - } - - createMastodonAPI(); - - mastodonAPI.notification(notificationId).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - buildNotification(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 String truncateWithEllipses(String string, int limit) { - if (string.length() < limit) { - return string; - } else { - return string.substring(0, limit - 3) + "..."; - } - } - - private static boolean filterNotification(SharedPreferences preferences, - Notification notification) { - switch (notification.type) { - default: - case MENTION: { - return preferences.getBoolean("notificationFilterMentions", true); - } - case FOLLOW: { - return preferences.getBoolean("notificationFilterFollows", true); - } - case REBLOG: { - return preferences.getBoolean("notificationFilterReblogs", true); - } - case FAVOURITE: { - return preferences.getBoolean("notificationFilterFavourites", true); - } - } - } - - private void buildNotification(Notification body) { - final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); - final SharedPreferences notificationPreferences = getApplicationContext().getSharedPreferences("Notifications", MODE_PRIVATE); - - if (!filterNotification(preferences, body)) { - return; - } - - String rawCurrentNotifications = notificationPreferences.getString("current", "[]"); - JSONArray currentNotifications; - - try { - currentNotifications = new JSONArray(rawCurrentNotifications); - } catch (JSONException e) { - currentNotifications = new JSONArray(); - } - - boolean alreadyContains = false; - - for(int i = 0; i < currentNotifications.length(); i++) { - try { - if (currentNotifications.getString(i).equals(body.account.displayName)) { - alreadyContains = true; - } - } catch (JSONException e) { - e.printStackTrace(); - } - } - - if (!alreadyContains) { - currentNotifications.put(body.account.displayName); - } - - SharedPreferences.Editor editor = notificationPreferences.edit(); - editor.putString("current", currentNotifications.toString()); - editor.commit(); - - Intent resultIntent = new Intent(this, MainActivity.class); - resultIntent.putExtra("tab_position", 1); - TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); - stackBuilder.addParentStack(MainActivity.class); - stackBuilder.addNextIntent(resultIntent); - PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); - - Intent deleteIntent = new Intent(this, NotificationClearBroadcastReceiver.class); - PendingIntent deletePendingIntent = PendingIntent.getBroadcast(this, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT); - - final NotificationCompat.Builder builder = new NotificationCompat.Builder(this) - .setSmallIcon(R.drawable.ic_notify) - .setContentIntent(resultPendingIntent) - .setDeleteIntent(deletePendingIntent) - .setDefaults(0); // So it doesn't ring twice, notify only in Target callback - - if (currentNotifications.length() == 1) { - builder.setContentTitle(titleForType(body)) - .setContentText(truncateWithEllipses(bodyForType(body), 40)); - - Target mTarget = new Target() { - @Override - public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { - builder.setLargeIcon(bitmap); - - setupPreferences(preferences, builder); - - ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build()); - } - - @Override - public void onBitmapFailed(Drawable errorDrawable) { - - } - - @Override - public void onPrepareLoad(Drawable placeHolderDrawable) { - - } - }; - - Picasso.with(this) - .load(body.account.avatar) - .placeholder(R.drawable.avatar_default) - .transform(new RoundedTransformation(7, 0)) - .into(mTarget); - } else { - setupPreferences(preferences, builder); - - try { - builder.setContentTitle(String.format(getString(R.string.notification_title_summary), currentNotifications.length())) - .setContentText(truncateWithEllipses(joinNames(currentNotifications), 40)); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); - builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); - } - - ((NotificationManager) (getSystemService(NOTIFICATION_SERVICE))).notify(NOTIFY_ID, builder.build()); - } - - private void setupPreferences(SharedPreferences preferences, NotificationCompat.Builder builder) { - if (preferences.getBoolean("notificationAlertSound", true)) { - builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); - } - - if (preferences.getBoolean("notificationAlertVibrate", false)) { - builder.setVibrate(new long[] { 500, 500 }); - } - - if (preferences.getBoolean("notificationAlertLight", false)) { - builder.setLights(0xFF00FF8F, 300, 1000); - } - } - - private String joinNames(JSONArray array) throws JSONException { - if (array.length() > 3) { - return String.format(getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3); - } else if (array.length() == 3) { - return String.format(getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2)); - } else if (array.length() == 2) { - return String.format(getString(R.string.notification_summary_small), array.get(0), array.get(1)); - } - - return null; - } - - private String titleForType(Notification notification) { - switch (notification.type) { - case MENTION: - return String.format(getString(R.string.notification_mention_format), notification.account.getDisplayName()); - case FOLLOW: - return String.format(getString(R.string.notification_follow_format), notification.account.getDisplayName()); - case FAVOURITE: - return String.format(getString(R.string.notification_favourite_format), notification.account.getDisplayName()); - case REBLOG: - return String.format(getString(R.string.notification_reblog_format), notification.account.getDisplayName()); - } - - return null; - } - - private String bodyForType(Notification notification) { - switch (notification.type) { - case FOLLOW: - return notification.account.username; - case MENTION: - case FAVOURITE: - case REBLOG: - return notification.status.content.toString(); - } - - return null; - } -} diff --git a/app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java b/app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java new file mode 100644 index 000000000..8c7bfb85d --- /dev/null +++ b/app/src/main/java/com/keylesspalace/tusky/NotificationMaker.java @@ -0,0 +1,224 @@ +/* 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; + +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v4.app.TaskStackBuilder; + +import com.keylesspalace.tusky.entity.Notification; +import com.squareup.picasso.Picasso; +import com.squareup.picasso.Target; + +import org.json.JSONArray; +import org.json.JSONException; + +class NotificationMaker { + static void make(final Context context, final int notifyId, Notification body) { + final SharedPreferences preferences = + PreferenceManager.getDefaultSharedPreferences(context); + final SharedPreferences notificationPreferences = context.getSharedPreferences( + "Notifications", Context.MODE_PRIVATE); + + if (!filterNotification(preferences, body)) { + return; + } + + String rawCurrentNotifications = notificationPreferences.getString("current", "[]"); + JSONArray currentNotifications; + + try { + currentNotifications = new JSONArray(rawCurrentNotifications); + } catch (JSONException e) { + currentNotifications = new JSONArray(); + } + + boolean alreadyContains = false; + + for(int i = 0; i < currentNotifications.length(); i++) { + try { + if (currentNotifications.getString(i).equals(body.account.getDisplayName())) { + alreadyContains = true; + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (!alreadyContains) { + currentNotifications.put(body.account.getDisplayName()); + } + + notificationPreferences.edit() + .putString("current", currentNotifications.toString()) + .commit(); + + Intent resultIntent = new Intent(context, MainActivity.class); + resultIntent.putExtra("tab_position", 1); + TaskStackBuilder stackBuilder = TaskStackBuilder.create(context); + stackBuilder.addParentStack(MainActivity.class); + stackBuilder.addNextIntent(resultIntent); + PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + + Intent deleteIntent = new Intent(context, NotificationClearBroadcastReceiver.class); + PendingIntent deletePendingIntent = PendingIntent.getBroadcast(context, 0, deleteIntent, PendingIntent.FLAG_CANCEL_CURRENT); + + final NotificationCompat.Builder builder = new NotificationCompat.Builder(context) + .setSmallIcon(R.drawable.ic_notify) + .setContentIntent(resultPendingIntent) + .setDeleteIntent(deletePendingIntent) + .setDefaults(0); // So it doesn't ring twice, notify only in Target callback + + if (currentNotifications.length() == 1) { + builder.setContentTitle(titleForType(context, body)) + .setContentText(truncateWithEllipses(bodyForType(body), 40)); + + Target mTarget = new Target() { + @Override + public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { + builder.setLargeIcon(bitmap); + + setupPreferences(preferences, builder); + + ((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE))) + .notify(notifyId, builder.build()); + } + + @Override + public void onBitmapFailed(Drawable errorDrawable) {} + + @Override + public void onPrepareLoad(Drawable placeHolderDrawable) {} + }; + + Picasso.with(context) + .load(body.account.avatar) + .placeholder(R.drawable.avatar_default) + .transform(new RoundedTransformation(7, 0)) + .into(mTarget); + } else { + setupPreferences(preferences, builder); + try { + builder.setContentTitle(String.format(context.getString(R.string.notification_title_summary), currentNotifications.length())) + .setContentText(truncateWithEllipses(joinNames(context, currentNotifications), 40)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + builder.setVisibility(android.app.Notification.VISIBILITY_PRIVATE); + builder.setCategory(android.app.Notification.CATEGORY_SOCIAL); + } + + ((NotificationManager) (context.getSystemService(Context.NOTIFICATION_SERVICE))) + .notify(notifyId, builder.build()); + } + + private static boolean filterNotification(SharedPreferences preferences, + Notification notification) { + switch (notification.type) { + default: + case MENTION: { + return preferences.getBoolean("notificationFilterMentions", true); + } + case FOLLOW: { + return preferences.getBoolean("notificationFilterFollows", true); + } + case REBLOG: { + return preferences.getBoolean("notificationFilterReblogs", true); + } + case FAVOURITE: { + return preferences.getBoolean("notificationFilterFavourites", true); + } + } + } + + private static String truncateWithEllipses(String string, int limit) { + if (string.length() < limit) { + return string; + } else { + return string.substring(0, limit - 3) + "..."; + } + } + + private static void setupPreferences(SharedPreferences preferences, + NotificationCompat.Builder builder) { + if (preferences.getBoolean("notificationAlertSound", true)) { + builder.setSound(Settings.System.DEFAULT_NOTIFICATION_URI); + } + + if (preferences.getBoolean("notificationAlertVibrate", false)) { + builder.setVibrate(new long[] { 500, 500 }); + } + + if (preferences.getBoolean("notificationAlertLight", false)) { + builder.setLights(0xFF00FF8F, 300, 1000); + } + } + + @Nullable + private static String joinNames(Context context, JSONArray array) throws JSONException { + if (array.length() > 3) { + return String.format(context.getString(R.string.notification_summary_large), array.get(0), array.get(1), array.get(2), array.length() - 3); + } else if (array.length() == 3) { + return String.format(context.getString(R.string.notification_summary_medium), array.get(0), array.get(1), array.get(2)); + } else if (array.length() == 2) { + return String.format(context.getString(R.string.notification_summary_small), array.get(0), array.get(1)); + } + + return null; + } + + @Nullable + private static String titleForType(Context context, Notification notification) { + switch (notification.type) { + case MENTION: + return String.format(context.getString(R.string.notification_mention_format), notification.account.getDisplayName()); + case FOLLOW: + return String.format(context.getString(R.string.notification_follow_format), notification.account.getDisplayName()); + case FAVOURITE: + return String.format(context.getString(R.string.notification_favourite_format), notification.account.getDisplayName()); + case REBLOG: + return String.format(context.getString(R.string.notification_reblog_format), notification.account.getDisplayName()); + } + return null; + } + + @Nullable + private static String bodyForType(Notification notification) { + switch (notification.type) { + case FOLLOW: + return notification.account.username; + case MENTION: + case FAVOURITE: + case REBLOG: + return notification.status.content.toString(); + } + + return null; + } +} diff --git a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java b/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java index 09b086816..a821df4d8 100644 --- a/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java +++ b/app/src/main/java/com/keylesspalace/tusky/OkHttpUtils.java @@ -42,7 +42,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -class OkHttpUtils { +public class OkHttpUtils { static final String TAG = "OkHttpUtils"; // logging tag /** @@ -58,8 +58,7 @@ class OkHttpUtils { * TLS 1.1 and 1.2 have to be manually enabled on API levels 16-20. */ @NonNull - static OkHttpClient.Builder getCompatibleClientBuilder() { - + public static OkHttpClient.Builder getCompatibleClientBuilder() { ConnectionSpec fallback = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .allEnabledCipherSuites() .supportsTlsExtensions(true) @@ -79,7 +78,7 @@ class OkHttpUtils { } @NonNull - static OkHttpClient getCompatibleClient() { + public static OkHttpClient getCompatibleClient() { return getCompatibleClientBuilder().build(); } diff --git a/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java b/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java index 817d6d3f3..3b47acc3b 100644 --- a/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/SpannedTypeAdapter.java @@ -25,7 +25,7 @@ import com.google.gson.JsonParseException; import java.lang.reflect.Type; -class SpannedTypeAdapter implements JsonDeserializer { +public class SpannedTypeAdapter implements JsonDeserializer { @Override public Spanned deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { return HtmlUtils.fromHtml(Emojione.shortnameToUnicode(json.getAsString(), false)); diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java b/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java index b662f237d..23111c6ab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java +++ b/app/src/main/java/com/keylesspalace/tusky/StatusRemoveListener.java @@ -1,3 +1,18 @@ +/* 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; interface StatusRemoveListener { diff --git a/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java b/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java index 76d07e9c9..cad8e8c08 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java +++ b/app/src/main/java/com/keylesspalace/tusky/StringWithEmoji.java @@ -1,3 +1,18 @@ +/* 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; /** diff --git a/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java b/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java index e4923921a..ecf18bcd9 100644 --- a/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java +++ b/app/src/main/java/com/keylesspalace/tusky/StringWithEmojiTypeAdapter.java @@ -1,3 +1,18 @@ +/* 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; import com.emojione.Emojione;