diff --git a/app/build.gradle b/app/build.gradle index b4f3360cf..bd04cd927 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,8 @@ -apply plugin: 'com.android.application' +import java.util.regex.Matcher +import java.util.regex.Pattern +apply plugin: 'com.android.application' +def flavor android { compileSdkVersion 29 buildToolsVersion "29.0.3" @@ -42,6 +45,7 @@ android { buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" + flavor = "fdroid" } lite { minSdkVersion 21 @@ -49,12 +53,14 @@ android { buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "true" resValue "string", "app_name", "Fedilab Lite" + flavor = "lite" } playstore { applicationId "app.fedilab.android" buildConfigField "boolean", "DONATIONS", "false" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" + flavor = "playstore" } } buildFeatures { @@ -62,7 +68,7 @@ android { } sourceSets { playstore { - manifest.srcFile "src/common/AndroidManifest.xml" + manifest.srcFile "src/playstore/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets', 'src/common/assets'] java.srcDirs = ['src/main/java', 'src/playstore/java','src/common/java'] res.srcDirs = ['src/main/res', 'src/playstore/res','src/common/res'] @@ -71,13 +77,13 @@ android { fdroid { manifest.srcFile "src/common/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets', 'src/common/assets'] - java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java'] + java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java', 'src/fdroidcommon/java'] res.srcDirs = ['src/main/res', 'src/fdroid/res','src/common/res'] } lite { manifest.srcFile "src/lite/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets'] - java.srcDirs = ['src/main/java', 'src/lite/java'] + java.srcDirs = ['src/main/java', 'src/lite/java', 'src/fdroidcommon/java'] res.srcDirs = ['src/main/res', 'src/lite/res'] } } @@ -173,6 +179,12 @@ dependencies { //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2' implementation 'com.huangyz0918:androidwm-light:0.1.2' + + implementation 'com.github.UnifiedPush:android-connector:1.1.0' + implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" + + + implementation 'commons-net:commons-net:3.6' //Flavors //Playstore @@ -182,7 +194,7 @@ dependencies { playstoreImplementation 'org.framagit.tom79:country-picker-android:1.2.0' playstoreImplementation 'com.vanniktech:emoji-one:0.6.0' playstoreImplementation 'ja.burhanrashid52:photoeditor:0.4.0' - + playstoreImplementation 'com.github.UnifiedPush:android-connector_fcm_added:1.0.0' //Fdroid fdroidApi 'com.theartofdev.edmodo:android-image-cropper:2.8.+' @@ -191,4 +203,29 @@ dependencies { fdroidImplementation 'com.vanniktech:emoji-one:0.6.0' fdroidImplementation 'ja.burhanrashid52:photoeditor:0.4.0' + + +} +def getCurrentFlavor() { + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() + + Pattern pattern + + if( tskReqStr.contains( "assemble" ) ) + pattern = Pattern.compile("assemble(\\w+)(Release|Debug)") + else + pattern = Pattern.compile("generate(\\w+)(Release|Debug)") + Matcher matcher = pattern.matcher( tskReqStr ) + + if( matcher.find() ) { + return matcher.group(1).toLowerCase() + }else + { + println "NO MATCH FOUND" + return "" + } +} +if( getCurrentFlavor() == "playstore") { + apply plugin: 'com.google.gms.google-services' } diff --git a/app/src/common/AndroidManifest.xml b/app/src/common/AndroidManifest.xml index d08f233c2..397a7c259 100644 --- a/app/src/common/AndroidManifest.xml +++ b/app/src/common/AndroidManifest.xml @@ -84,5 +84,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java b/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java deleted file mode 100644 index 1e4bf19c3..000000000 --- a/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java +++ /dev/null @@ -1,23 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2020 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import app.fedilab.android.helper.Helper; - -public class LiveNotificationService extends BaseLiveNotificationService { - static { - Helper.installProvider(); - } -} diff --git a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java new file mode 100644 index 000000000..24aa114a3 --- /dev/null +++ b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java @@ -0,0 +1,103 @@ +package app.fedilab.android.helper; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import org.unifiedpush.android.connector.Registration; + +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static android.content.Context.MODE_PRIVATE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + +public class PushHelper { + + public static void startStreaming(Context context) { + int liveNotifications = liveNotifType(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + NotificationsSyncJob.schedule(false); + switch (liveNotifications) { + case NOTIF_PUSH: + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + ((Activity) context).runOnUiThread(() -> { + for (Account account : accounts) { + registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + } + }); + }).start(); + break; + case NOTIF_NONE: + new Registration().unregisterApp(context); + break; + } + } + + + private static void registerAppWithDialog(Context context, String slug) { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + int style; + if (theme == Helper.THEME_DARK) { + style = R.style.DialogDark; + } else if (theme == Helper.THEME_BLACK) { + style = R.style.DialogBlack; + } else { + style = R.style.Dialog; + } + + Registration registration = new Registration(); + List distributors = registration.getDistributors(context); + if (distributors.size() == 1 || !registration.getDistributor(context).isEmpty()) { + if (distributors.size() == 1) { + registration.saveDistributor(context, distributors.get(0)); + } else { + registration.saveDistributor(context, registration.getDistributor(context)); + } + registration.registerApp(context, slug); + return; + } + AlertDialog.Builder alert = new AlertDialog.Builder(context, style); + if (distributors.size() == 0) { + alert.setTitle(R.string.no_distributors_found); + final TextView message = new TextView(context); + String link = "https://fedilab.app/wiki/features/push-notifications#fdroid"; + final SpannableString s = + new SpannableString(context.getString(R.string.no_distributors_explanation, link)); + Linkify.addLinks(s, Linkify.WEB_URLS); + message.setText(s); + message.setPadding(30, 20, 30, 10); + message.setMovementMethod(LinkMovementMethod.getInstance()); + alert.setView(message); + alert.setPositiveButton(R.string.close, (dialog, whichButton) -> dialog.dismiss()); + } else { + alert.setTitle(R.string.select_distributors); + String[] distributorsStr = distributors.toArray(new String[0]); + alert.setSingleChoiceItems(distributorsStr, -1, (dialog, item) -> { + String distributor = distributorsStr[item]; + registration.saveDistributor(context, distributor); + registration.registerApp(context, slug); + dialog.dismiss(); + }); + } + alert.show(); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 140b7d0cb..91e1fe031 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,18 +69,7 @@ android:name="app.fedilab.android.services.BackupNotificationInDataBaseService" android:exported="false" /> - - - - - - - - - - + @@ -88,20 +77,7 @@ - - - - - - - - - - + @@ -461,5 +437,19 @@ android:resource="@xml/file_paths" /> + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 274a159a8..692df044c 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -133,9 +133,11 @@ import app.fedilab.android.fragments.TabLayoutNotificationsFragment; import app.fedilab.android.fragments.TabLayoutScheduleFragment; import app.fedilab.android.fragments.WhoToFollowFragment; import app.fedilab.android.helper.CrossActions; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.ExpandableHeightListView; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MenuFloating; +import app.fedilab.android.helper.PushHelper; import app.fedilab.android.interfaces.OnFilterActionInterface; import app.fedilab.android.interfaces.OnRetrieveEmojiAccountInterface; import app.fedilab.android.interfaces.OnRetrieveFeedsInterface; @@ -156,7 +158,8 @@ import es.dmoral.toasty.Toasty; import static app.fedilab.android.activities.WebviewActivity.trackingDomains; import static app.fedilab.android.asynctasks.ManageFiltersAsyncTask.action.GET_ALL_FILTER; -import static app.fedilab.android.helper.BaseHelper.startStreaming; +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; import static app.fedilab.android.helper.Helper.changeDrawableColor; @@ -215,6 +218,7 @@ public abstract class BaseMainActivity extends BaseActivity Account account = new AccountDAO(BaseMainActivity.this, db).getUniqAccount(userId, instance); Intent intent = getIntent(); PackageManager pm = getPackageManager(); + try { if (intent != null && intent.getComponent() != null) { ActivityInfo ai = pm.getActivityInfo(intent.getComponent(), PackageManager.GET_META_DATA); @@ -249,6 +253,7 @@ public abstract class BaseMainActivity extends BaseActivity e.printStackTrace(); } + if (account == null) { Helper.logoutCurrentUser(BaseMainActivity.this); Intent myIntent = new Intent(BaseMainActivity.this, LoginActivity.class); @@ -306,6 +311,7 @@ public abstract class BaseMainActivity extends BaseActivity activity = this; rateThisApp(); + //Intialize Peertube information //This task will allow to instance a static PeertubeInformation class if (social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) { @@ -418,6 +424,9 @@ public abstract class BaseMainActivity extends BaseActivity add_new = findViewById(R.id.add_new); main_app_container = findViewById(R.id.main_app_container); + + + if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); @@ -662,7 +671,7 @@ public abstract class BaseMainActivity extends BaseActivity } boolean popupShown = sharedpreferences.getBoolean(Helper.SET_POPUP_PUSH, false); if (popupShown) { - Helper.startStreaming(BaseMainActivity.this); + PushHelper.startStreaming(BaseMainActivity.this); } if (hidde_menu != null) @@ -1194,55 +1203,39 @@ public abstract class BaseMainActivity extends BaseActivity //Live notification mode final Spinner set_live_type = dialogView.findViewById(R.id.set_live_type); - String[] labels = {getString(R.string.live_notif), getString(R.string.live_delayed), getString(R.string.no_live_notif)}; + String[] labels = {getString(R.string.push_notif), getString(R.string.no_live_notif)}; ArrayAdapter adapterLive = new ArrayAdapter<>(BaseMainActivity.this, android.R.layout.simple_spinner_dropdown_item, labels); set_live_type.setAdapter(adapterLive); TextView set_live_type_indication = dialogView.findViewById(R.id.set_live_type_indication); switch (Helper.liveNotifType(BaseMainActivity.this)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); + case Helper.NOTIF_PUSH: + set_live_type_indication.setText(R.string.set_push_notifications); break; case Helper.NOTIF_NONE: set_live_type_indication.setText(R.string.no_live_indication); break; } - set_live_type.setSelection(Helper.liveNotifType(BaseMainActivity.this), false); + int livenotif = Helper.liveNotifType(BaseMainActivity.this); + int selection = 0; + if (livenotif == NOTIF_NONE) + selection = 1; + set_live_type.setSelection(selection, false); set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { SharedPreferences.Editor editor = sharedpreferences.edit(); switch (position) { - case Helper.NOTIF_LIVE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); + case 0: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true); editor.apply(); - startStreaming(BaseMainActivity.this); + PushHelper.startStreaming(BaseMainActivity.this); + set_live_type_indication.setText(R.string.set_push_notifications); break; - case Helper.NOTIF_DELAYED: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true); - editor.apply(); - startStreaming(BaseMainActivity.this); - break; - case Helper.NOTIF_NONE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); - editor.apply(); - break; - } - switch (Helper.liveNotifType(BaseMainActivity.this)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); - break; - case Helper.NOTIF_NONE: + case 1: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false); set_live_type_indication.setText(R.string.no_live_indication); + editor.apply(); break; } } @@ -1386,6 +1379,7 @@ public abstract class BaseMainActivity extends BaseActivity protected abstract void rateThisApp(); + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); @@ -2426,6 +2420,7 @@ public abstract class BaseMainActivity extends BaseActivity protected abstract void launchOwnerNotificationsActivity(); + public enum iconLauncher { BUBBLES, FEDIVERSE, diff --git a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java index 3bdd57be6..43a90cb08 100644 --- a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java @@ -15,6 +15,7 @@ package app.fedilab.android.activities; import android.Manifest; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -121,6 +122,7 @@ public class LoginActivity extends BaseActivity { return Helper.instanceWithProtocol(context, instance) + Helper.EP_AUTHORIZE + "?" + queryString; } + @SuppressLint("ApplySharedPref") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -325,18 +327,23 @@ public class LoginActivity extends BaseActivity { thread.interrupt(); thread = null; } - if (oldSearch == null || !oldSearch.equals(s.toString().trim())) { thread = new Thread(() -> { try { - final String response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); + String response = null; + try { + response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); + } catch (Exception ignored) { + } + if (response == null) { return; } + String finalResponse = response; runOnUiThread(() -> { String[] instances; try { - JSONObject jsonObject = new JSONObject(response); + JSONObject jsonObject = new JSONObject(finalResponse); JSONArray jsonArray = jsonObject.getJSONArray("instances"); int length = 0; for (int i = 0; i < jsonArray.length(); i++) { diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java index b4ba9117d..0ef2e3922 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java @@ -30,6 +30,7 @@ import app.fedilab.android.client.Entities.Account; import app.fedilab.android.client.Entities.Error; import app.fedilab.android.client.GNUAPI; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.PushHelper; import app.fedilab.android.interfaces.OnPostStatusActionInterface; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TagsCacheDAO; @@ -125,11 +126,6 @@ public class PostStatusAsyncTask { } }; thread.start(); - if (account != null) { - String key = account.getUsername() + "@" + account.getInstance(); - Helper.sleeps.put(key, 30000); - Helper.startStreaming(contextReference.get()); - } }; mainHandler.post(myRunnable); }).start(); diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java new file mode 100644 index 000000000..511cc32cf --- /dev/null +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -0,0 +1,47 @@ +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +package app.fedilab.android.asynctasks; + +import android.content.Context; + +import java.lang.ref.WeakReference; + +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; + + +public class PostSubscriptionAsyncTask { + + private final WeakReference contextReference; + private APIResponse apiResponse; + private final String endpoint; + private final Account account; + + public PostSubscriptionAsyncTask(Context context, Account account, String endpoint) { + this.contextReference = new WeakReference<>(context); + this.endpoint = endpoint; + this.account = account; + doInBackground(); + } + + + protected void doInBackground() { + new Thread(() -> { + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); + }).start(); + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index a357cb19f..eab813fd2 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -23,7 +23,10 @@ import android.os.Bundle; import android.text.Html; import android.text.SpannableString; + import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.preference.PreferenceManager; + import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -58,6 +61,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -88,6 +92,7 @@ import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.Entities.Peertube; import app.fedilab.android.client.Entities.Poll; import app.fedilab.android.client.Entities.PollOptions; +import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Reaction; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Report; @@ -99,11 +104,15 @@ import app.fedilab.android.client.Entities.Tag; import app.fedilab.android.client.Entities.Trends; import app.fedilab.android.client.Entities.TrendsHistory; import app.fedilab.android.fragments.DisplayNotificationsFragment; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.Helper; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TimelineCacheDAO; +import static app.fedilab.android.helper.ECDH.kp_private; +import static app.fedilab.android.helper.ECDH.kp_public; + /** * Created by Thomas on 23/04/2017. @@ -555,6 +564,34 @@ public class API { return reactions; } + + /** + * Parse a push notification + * + * @param resobj JSONObject + * @return PushSubscription + */ + private static PushSubscription parsePushNotifications(JSONObject resobj) { + PushSubscription pushSubscription = new PushSubscription(); + try { + pushSubscription.setId(resobj.getString("id")); + pushSubscription.setEndpoint(resobj.getString("endpoint")); + pushSubscription.setServer_key(resobj.getString("server_key")); + JSONObject alertsObject = resobj.getJSONObject("alerts"); + Iterator iter = alertsObject.keys(); + HashMap alertsList = new HashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + boolean value = (boolean) alertsObject.get(key); + alertsList.put(key, value); + } + pushSubscription.setAlertsList(alertsList); + } catch (JSONException e) { + e.printStackTrace(); + } + return pushSubscription; + } + /** * Parse a reaction * @@ -5101,7 +5138,6 @@ public class API { private APIResponse getNotifications(DisplayNotificationsFragment.Type type, String max_id, String since_id, int limit, boolean display) { HashMap params = new HashMap<>(); - if (MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED) { params.put("pg", "true"); params.put("page", max_id); @@ -5767,6 +5803,133 @@ public class API { return apiResponse; } + /** + * Get subscribed push notifications + * + * @return APIResponse + */ + public APIResponse getPushSubscription() { + PushSubscription pushSubscription = null; + try { + String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/push/subscription"), 10, null, prefKeyOauthTokenT); + pushSubscription = parsePushNotifications(new JSONObject(response)); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + + + /** + * Update subscribed push notifications + * + * @return APIResponse + */ + public APIResponse updatePushSubscription(String endpoint) { + PushSubscription pushSubscription = new PushSubscription(); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + + + HashMap params = new HashMap<>(); + try { + endpoint = URLEncoder.encode(endpoint, "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } + params.put("subscription[endpoint]", endpoint); + params.put("data[alerts][follow]", String.valueOf(notif_follow)); + params.put("data[alerts][mention]", String.valueOf(notif_mention)); + params.put("data[alerts][favourite]", String.valueOf(notif_add)); + params.put("data[alerts][reblog]", String.valueOf(notif_share)); + params.put("data[alerts][poll]", String.valueOf(notif_poll)); + + try { + + String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); + pushSubscription = parsePushNotifications(new JSONObject(response)); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + + /** + * Subscribe to push notifications + * + * @return APIResponse + */ + public APIResponse pushSubscription(String endpoint, Account account) { + PushSubscription pushSubscription = new PushSubscription(); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String strPub = prefs.getString(kp_public, ""); + String strPriv = prefs.getString(kp_private, ""); + ECDH ecdh = ECDH.getInstance(); + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { + ecdh.newPair(context); + } + String pubKey = ecdh.getPublicKey(context); + byte[] randBytes = new byte[16]; + new Random().nextBytes(randBytes); + String auth = ECDH.base64Encode(randBytes); + JSONObject jsonObject = new JSONObject(); + + try { + JSONObject jsonObjectSub = new JSONObject(); + jsonObjectSub.put("endpoint", endpoint); + JSONObject jsonObjectkey = new JSONObject(); + jsonObjectkey.put("p256dh", pubKey); + jsonObjectkey.put("auth", auth); + jsonObjectSub.put("keys", jsonObjectkey); + jsonObject.put("subscription", jsonObjectSub); + + JSONObject jsonObjectdata = new JSONObject(); + JSONObject jsonObjectarlerts = new JSONObject(); + jsonObjectarlerts.put("follow", notif_follow); + jsonObjectarlerts.put("mention", notif_mention); + jsonObjectarlerts.put("favourite", notif_add); + jsonObjectarlerts.put("reblog", notif_share); + jsonObjectarlerts.put("poll", notif_poll); + jsonObjectdata.put("alerts", jsonObjectarlerts); + jsonObject.put("data", jsonObjectdata); + } catch (JSONException e) { + e.printStackTrace(); + } + try { + String response = new HttpsConnection(context, this.instance).postJson(getAbsoluteUrl("/push/subscription"), 10, jsonObject, prefKeyOauthTokenT); + pushSubscription = parsePushNotifications(new JSONObject(response)); + ecdh.saveServerKey(context, account, pushSubscription.getServer_key()); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + /** * Update a list by its id * diff --git a/app/src/main/java/app/fedilab/android/client/APIResponse.java b/app/src/main/java/app/fedilab/android/client/APIResponse.java index 635eaeed2..62327c10e 100644 --- a/app/src/main/java/app/fedilab/android/client/APIResponse.java +++ b/app/src/main/java/app/fedilab/android/client/APIResponse.java @@ -35,6 +35,7 @@ import app.fedilab.android.client.Entities.PeertubeNotification; import app.fedilab.android.client.Entities.PixelFedStory; import app.fedilab.android.client.Entities.PixelFedStoryItem; import app.fedilab.android.client.Entities.Playlist; +import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Report; import app.fedilab.android.client.Entities.Results; @@ -56,6 +57,7 @@ public class APIResponse { private List notifications = null; private List relationships = null; private List announcements = null; + private PushSubscription pushSubscription; private String targetedId = null; private Results results = null; private List howToVideos = null; @@ -85,6 +87,14 @@ public class APIResponse { return accounts; } + public PushSubscription getPushSubscription() { + return pushSubscription; + } + + public void setPushSubscription(PushSubscription pushSubscription) { + this.pushSubscription = pushSubscription; + } + public void setAccounts(List accounts) { this.accounts = accounts; } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Account.java b/app/src/main/java/app/fedilab/android/client/Entities/Account.java index ee0eb60ef..a57dd3c33 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Account.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Account.java @@ -828,6 +828,37 @@ public class Account implements Parcelable { } }, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } + matcher = Pattern.compile("gemini://([\\d\\w.-]*)(:\\d+)?(/\\S*)?").matcher(fieldSpan); + while (matcher.find()) { + URLSpan[] urls = fieldSpan.getSpans(0, fieldSpan.length(), URLSpan.class); + for (URLSpan span : urls) + fieldSpan.removeSpan(span); + int matchStart = matcher.start(0); + int matchEnd = matcher.end(); + final String url = fieldSpan.toString().substring(matchStart, matchEnd); + if (matchStart >= 0 && matchEnd <= fieldSpan.toString().length() && matchEnd >= matchStart) { + fieldSpan.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View textView) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(intent); + } catch (Exception e) { + Toasty.error(context, context.getString(R.string.toast_no_apps), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.setColor(link_color); + } + }, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + fieldsSpan.put(keySpan, fieldSpan); + } + + } if (accountsMentionUnknown.size() > 0) { for (Account accountMention : accountsMentionUnknown) { String targetedAccount = "@" + accountMention.getAcct(); diff --git a/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java new file mode 100644 index 000000000..d5f572fb1 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java @@ -0,0 +1,56 @@ +package app.fedilab.android.client.Entities; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +import java.util.HashMap; + +public class PushSubscription { + private String id; + private String endpoint; + private HashMap alerts; + private String server_key; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public HashMap getAlertsList() { + return alerts; + } + + public void setAlertsList(HashMap alertsList) { + this.alerts = alertsList; + } + + public String getServer_key() { + return server_key; + } + + public void setServer_key(String server_key) { + this.server_key = server_key; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 55a546dd2..e00399538 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -665,9 +665,7 @@ public class Status implements Parcelable { intent.putExtras(b); context.startActivity(intent); } else { - if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) - finalUrl = "http://" + url; - Helper.openBrowser(context, finalUrl); + Helper.openBrowser(context, url); } } @@ -891,10 +889,7 @@ public class Status implements Parcelable { contentSpanTranslated.setSpan(new ClickableSpan() { @Override public void onClick(@NonNull View textView) { - String finalUrl = url; - if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) - finalUrl = "http://" + url; - Helper.openBrowser(context, finalUrl); + Helper.openBrowser(context, url); } @Override diff --git a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java index 6327b8335..0b71fc085 100644 --- a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java +++ b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java @@ -474,6 +474,63 @@ public class HttpsConnection { } + + String postJson(String urlConnection, int timeout, JSONObject jsonObject, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { + + URL url = new URL(urlConnection); + byte[] postDataBytes; + postDataBytes = jsonObject.toString().getBytes(StandardCharsets.UTF_8); + if (proxy != null) + httpURLConnection = (HttpURLConnection) url.openConnection(proxy); + else + httpURLConnection = (HttpURLConnection) url.openConnection(); + httpURLConnection.setRequestProperty("User-Agent", USER_AGENT); + httpURLConnection.setConnectTimeout(timeout * 1000); + httpURLConnection.setDoOutput(true); + if (httpURLConnection instanceof HttpsURLConnection) { + ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(new TLSSocketFactory(this.instance)); + } + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Accept", "application/json"); + httpURLConnection.setRequestMethod("POST"); + setToken(token); + httpURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + + + httpURLConnection.getOutputStream().write(postDataBytes); + String response; + if (httpURLConnection.getResponseCode() >= 200 && httpURLConnection.getResponseCode() < 400) { + getSinceMaxId(); + response = converToString(httpURLConnection.getInputStream()); + } else { + String error = null; + if (httpURLConnection.getErrorStream() != null) { + InputStream stream = httpURLConnection.getErrorStream(); + if (stream == null) { + stream = httpURLConnection.getInputStream(); + } + try (Scanner scanner = new Scanner(stream)) { + scanner.useDelimiter("\\Z"); + if (scanner.hasNext()) { + error = scanner.next(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + int responseCode = httpURLConnection.getResponseCode(); + try { + httpURLConnection.getInputStream().close(); + } catch (Exception ignored) { + } + throw new HttpsConnectionException(responseCode, error); + } + getSinceMaxId(); + httpURLConnection.getInputStream().close(); + return response; + + } + @SuppressWarnings("SameParameterValue") String postMisskey(String urlConnection, int timeout, JSONObject paramaters, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { URL url = new URL(urlConnection); diff --git a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java index e9d39ea6c..fad56584b 100644 --- a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java +++ b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java @@ -1660,8 +1660,8 @@ public class PeertubeAPI { String errorM = jsonObject.get("error").toString(); message = "Error " + statusCode + " : " + errorM; } catch (JSONException e) { - if (error.getMessage().split(".").length > 0) { - String errorM = error.getMessage().split(".")[0]; + if (error.getMessage().split("\\.").length > 0) { + String errorM = error.getMessage().split("\\.")[0]; message = "Error " + statusCode + " : " + errorM; } } diff --git a/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java b/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java index fcee4070d..831ddf755 100644 --- a/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java @@ -31,7 +31,6 @@ import java.util.List; import app.fedilab.android.R; import app.fedilab.android.client.Entities.Account; import app.fedilab.android.helper.Helper; -import app.fedilab.android.services.LiveNotificationDelayedService; import static android.content.Context.MODE_PRIVATE; @@ -72,12 +71,6 @@ public class AccountLiveAdapter extends RecyclerView.Adapter= Build.VERSION_CODES.O) { set_notif_sound.setVisibility(View.GONE); channels_container.setVisibility(View.VISIBLE); - set_hide_status_bar_container.setVisibility(View.VISIBLE); - set_hide_status_bar.setOnClickListener(v -> { - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, LiveNotificationDelayedService.CHANNEL_ID); - startActivity(intent); - }); - sound_boost.setOnClickListener(v -> { Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); @@ -923,36 +906,6 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot } } - boolean allow_live_notifications = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + userId + instance, true); - TextView set_allow_live_notifications_title = rootView.findViewById(R.id.set_allow_live_notifications_title); - if (account != null) { - set_allow_live_notifications_title.setText(context.getString(R.string.set_allow_live_notifications, account.getAcct() + "@" + account.getInstance())); - } - final SwitchCompat set_allow_live_notifications = rootView.findViewById(R.id.set_allow_live_notifications); - set_allow_live_notifications.setChecked(allow_live_notifications); - set_allow_live_notifications.setOnClickListener(v -> { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putBoolean(Helper.SET_ALLOW_STREAM + userId + instance, set_allow_live_notifications.isChecked()); - editor.apply(); - if (set_allow_live_notifications.isChecked()) { - LiveNotificationDelayedService.totalAccount++; - } else { - LiveNotificationDelayedService.totalAccount--; - } - if (set_allow_live_notifications.isChecked()) { - LiveNotificationDelayedService.totalAccount++; - } else { - LiveNotificationDelayedService.totalAccount--; - } - Helper.startStreaming(context); - - }); - - final ImageButton set_allow_live_notifications_others = rootView.findViewById(R.id.set_allow_live_notifications_others); - set_allow_live_notifications_others.setOnClickListener(view -> { - Intent intent = new Intent(context, LiveNotificationSettingsAccountsActivity.class); - startActivity(intent); - }); boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); @@ -971,35 +924,22 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot editor.apply(); if (isChecked) { notification_container.setVisibility(View.VISIBLE); - notification_container.setVisibility(View.VISIBLE); - Helper.startStreaming(context); } else { - context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class)); - context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class)); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); notification_container.setVisibility(View.GONE); } + PushHelper.startStreaming(context); }); //Live notification mode final Spinner set_live_type = rootView.findViewById(R.id.set_live_type); - String[] labels = {context.getString(R.string.live_notif), context.getString(R.string.live_delayed), context.getString(R.string.no_live_notif)}; - ArrayAdapter adapterLive = new ArrayAdapter<>(Objects.requireNonNull(getActivity()), - android.R.layout.simple_spinner_dropdown_item, labels); + String[] labels = {context.getString(R.string.push_notif), context.getString(R.string.no_live_notif)}; + - LinearLayout live_notif_per_account = rootView.findViewById(R.id.live_notif_per_account); - set_live_type.setAdapter(adapterLive); - if (Helper.liveNotifType(context) == Helper.NOTIF_NONE) { - live_notif_per_account.setVisibility(View.GONE); - } TextView set_live_type_indication = rootView.findViewById(R.id.set_live_type_indication); switch (Helper.liveNotifType(context)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); + case Helper.NOTIF_PUSH: + set_live_type_indication.setText(R.string.set_push_notifications); break; case Helper.NOTIF_NONE: set_live_type_indication.setText(R.string.no_live_indication); @@ -1008,40 +948,25 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot set_live_type.setSelection(Helper.liveNotifType(context)); liveNotificationCount = 0; set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @SuppressLint("ApplySharedPref") @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (liveNotificationCount > 0) { SharedPreferences.Editor editor = sharedpreferences.edit(); - - context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class)); - context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class)); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); switch (position) { - case Helper.NOTIF_LIVE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); - live_notif_per_account.setVisibility(View.VISIBLE); + case Helper.NOTIF_PUSH: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true); editor.commit(); - set_live_type_indication.setText(R.string.live_notif_indication); - Helper.startStreaming(context); - break; - case Helper.NOTIF_DELAYED: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true); - live_notif_per_account.setVisibility(View.VISIBLE); - set_live_type_indication.setText(R.string.set_live_type_indication); - editor.commit(); - Helper.startStreaming(context); + set_live_type_indication.setText(R.string.set_push_notifications); + break; case Helper.NOTIF_NONE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false); editor.commit(); set_live_type_indication.setText(R.string.no_live_indication); - live_notif_per_account.setVisibility(View.GONE); - NotificationsSyncJob.schedule(false); break; } + PushHelper.startStreaming(context); } else { liveNotificationCount++; } diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java index ff72e13b1..726b2543f 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -55,12 +54,10 @@ import app.fedilab.android.drawers.NotificationsListAdapter; import app.fedilab.android.helper.Helper; import app.fedilab.android.interfaces.OnRetrieveMissingNotificationsInterface; import app.fedilab.android.interfaces.OnRetrieveNotificationsInterface; -import app.fedilab.android.services.LiveNotificationDelayedService; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; + import es.dmoral.toasty.Toasty; -import static android.content.Context.MODE_PRIVATE; + import static app.fedilab.android.activities.BaseMainActivity.countNewNotifications; @@ -314,12 +311,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve if (type == Type.ALL) { if (lastReadNotifications != null && notifications.get(0).getId().compareTo(lastReadNotifications) > 0) { countNewNotifications++; - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - Account accountdb = new AccountDAO(context, db).getUniqAccount(userId, instance); - LiveNotificationDelayedService.since_ids.put(accountdb.getAcct() + "@" + accountdb.getInstance(), notifications.get(0).getId()); } } for (Notification tmpNotification : notifications) { @@ -446,14 +437,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve MainActivity.lastNotificationId = notifications.get(0).getId(); updateNotificationLastId(notifications.get(0).getId()); } - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - Account account = new AccountDAO(context, db).getUniqAccount(userId, instance); - if (MainActivity.lastNotificationId != null && notifications.get(0).getId().compareTo(MainActivity.lastNotificationId) > 0) { - LiveNotificationDelayedService.since_ids.put(account.getAcct() + "@" + account.getInstance(), notifications.get(0).getId()); - } } if (textviewNoAction.getVisibility() == View.VISIBLE) { textviewNoAction.setVisibility(View.GONE); diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java index 6b21b7c88..41285d734 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java @@ -96,6 +96,9 @@ import app.fedilab.android.sqlite.SearchDAO; import app.fedilab.android.sqlite.Sqlite; import es.dmoral.toasty.Toasty; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + /** * Created by Thomas on 24/04/2017. @@ -756,7 +759,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter public void onResume() { super.onResume(); swipeRefreshLayout.setEnabled(true); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); + boolean liveNotifications = liveNotifType(context) == NOTIF_PUSH; if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) { if (this.isVisible()) { if (statuses != null && statuses.size() > 0) { @@ -861,7 +864,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter super.setMenuVisibility(visible); if (context == null) return; - int liveNotifications = Helper.liveNotifType(context); + int liveNotifications = liveNotifType(context); //Store last toot id for home timeline to avoid to notify for those that have been already seen if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) { if (visible) { diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index 0bb588585..a7d59ebfd 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -211,8 +211,8 @@ import app.fedilab.android.client.Entities.Status; import app.fedilab.android.client.Entities.Tag; import app.fedilab.android.client.Entities.TagTimeline; import app.fedilab.android.client.Tls12SocketFactory; -import app.fedilab.android.services.LiveNotificationDelayedService; -import app.fedilab.android.services.LiveNotificationService; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.MainMenuDAO; import app.fedilab.android.sqlite.Sqlite; @@ -239,6 +239,7 @@ import static app.fedilab.android.helper.ThemeHelper.getAttColor; import static app.fedilab.android.sqlite.StatusCacheDAO.ARCHIVE_CACHE; import static app.fedilab.android.sqlite.StatusCacheDAO.BOOKMARK_CACHE; import static app.fedilab.android.webview.ProxyHelper.setProxy; +import static cafe.adriel.androidaudiorecorder.Util.formatSeconds; import static com.koushikdutta.async.util.StreamUtility.copyStream; @@ -254,8 +255,8 @@ public class BaseHelper { public static final String TAG = "mastodon_etalab"; public static final String CLIENT_NAME_VALUE = "Fedilab"; - public static final String OAUTH_SCOPES = "read write follow"; - public static final String OAUTH_SCOPES_ADMIN = "read write follow admin:read admin:write admin"; + public static final String OAUTH_SCOPES = "read write follow push"; + public static final String OAUTH_SCOPES_ADMIN = "read write follow push admin:read admin:write admin"; public static final String OAUTH_SCOPES_PEERTUBE = "user"; public static final String PREF_KEY_OAUTH_TOKEN = "oauth_token"; @@ -277,6 +278,8 @@ public class BaseHelper { //Some definitions public static final String CLIENT_NAME = "client_name"; public static final String APP_PREFS = "app_prefs"; + public static final String SERVER_KEY = "server_key"; + public static final String SERVER_ENDPOINT = "server_endpoint"; public static final String ID = "id"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; @@ -334,7 +337,7 @@ public class BaseHelper { public static final String SET_TIME_FROM = "set_time_from"; public static final String SET_TIME_TO = "set_time_to"; public static final String SET_AUTO_STORE = "set_auto_store"; - public static final String SET_POPUP_PUSH = "set_popup_push_new"; + public static final String SET_POPUP_PUSH = "set_popup_push_new_push"; public static final String SET_POPUP_RELEASE_NOTES = "set_popup_push_release_notes"; public static final String SET_NSFW_TIMEOUT = "set_nsfw_timeout"; public static final String SET_MED_DESC_TIMEOUT = "set_med_desc_timeout"; @@ -346,8 +349,7 @@ public class BaseHelper { public static final String SET_SHOW_BOOSTS = "set_show_boost"; public static final String SET_SHOW_REPLIES = "set_show_replies"; public static final String SET_VIDEO_NSFW = "set_video_nsfw"; - public static final String SET_LIVE_NOTIFICATIONS = "set_live_notifications"; - public static final String SET_DELAYED_NOTIFICATIONS = "set_delayed_notifications"; + public static final String SET_PUSH_NOTIFICATIONS = "set_push_notifications"; public static final String SET_DISABLE_GIF = "set_disable_gif"; public static final String SET_DISABLE_ANIMATED_EMOJI = "set_disable_animated_emoji"; public static final String SET_CAPITALIZE = "set_capitalize"; @@ -404,8 +406,7 @@ public class BaseHelper { public static final int THEME_DARK = 2; public static final int THEME_BLACK = 3; - public static final int NOTIF_LIVE = 0; - public static final int NOTIF_DELAYED = 1; + public static final int NOTIF_PUSH = 3; public static final int NOTIF_NONE = 2; public static final int LED_COLOUR = 0; @@ -587,12 +588,9 @@ public class BaseHelper { public static int liveNotifType(Context context) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE); - boolean live = sharedpreferences.getBoolean(SET_LIVE_NOTIFICATIONS, false); - boolean delayed = sharedpreferences.getBoolean(SET_DELAYED_NOTIFICATIONS, true); - if (delayed) { - return NOTIF_DELAYED; - } else if (live) { - return NOTIF_LIVE; + boolean push = sharedpreferences.getBoolean(SET_PUSH_NOTIFICATIONS, false); + if (push) { + return NOTIF_PUSH; } else { return NOTIF_NONE; } @@ -3166,7 +3164,7 @@ public class BaseHelper { public static void openBrowser(Context context, String url) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, android.content.Context.MODE_PRIVATE); boolean embedded_browser = sharedpreferences.getBoolean(SET_EMBEDDED_BROWSER, true); - if (embedded_browser) { + if (embedded_browser && !url.toLowerCase().startsWith("gemini://")) { Intent intent = new Intent(context, WebviewActivity.class); Bundle b = new Bundle(); String finalUrl = url; @@ -4329,29 +4327,6 @@ public class BaseHelper { } - public static void startStreaming(Context context) { - int liveNotifications = liveNotifType(context); - Intent streamingIntent = null; - switch (liveNotifications) { - case NOTIF_LIVE: - streamingIntent = new Intent(context, LiveNotificationService.class); - break; - case NOTIF_DELAYED: - streamingIntent = new Intent(context, LiveNotificationDelayedService.class); - break; - } - if (streamingIntent != null) { - try { - if (Build.VERSION.SDK_INT >= 26) { - context.startForegroundService(streamingIntent); - } else { - context.startService(streamingIntent); - } - } catch (Exception ignored) { - context.startService(streamingIntent); - } - } - } public static boolean canFetchList(Context context, Account account) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java new file mode 100644 index 000000000..82cc80874 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -0,0 +1,298 @@ +package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.util.Base64; +import androidx.preference.PreferenceManager; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jce.spec.ECNamedCurveSpec; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; + +import javax.crypto.Cipher; +import javax.crypto.KeyAgreement; + + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + + + +public class ECDH { + + + public static final String kp_public = "kp_public"; + public static final String peer_public = "peer_public"; + public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; + + public static final String kp_private = "kp_private"; + public static final String KEGEN_ALG = "ECDH"; + + public static final String name = "prime256v1"; + + private static final String kp_public_affine_x = "kp_public_affine_x"; + private static final String kp_public_affine_y = "kp_public_affine_y"; + + private static ECDH instance; + + static { + Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); + } + + public final KeyFactory kf; + private final KeyPairGenerator kpg; + + + public ECDH() { + try { + kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER); + kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER); + + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public static synchronized ECDH getInstance() { + if (instance == null) { + instance = new ECDH(); + } + return instance; + } + + public static String base64Encode(byte[] b) { + + byte[] encoded = Base64.encode( + b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + return new String(encoded); + } + + static byte[] base64Decode(String str) { + return Base64.decode(str, Base64.DEFAULT); + } + + synchronized KeyPair generateKeyPair() + throws Exception { + ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(name); + kpg.initialize(ecParamSpec); + + return kpg.generateKeyPair(); + } + + private byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { + KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG); + keyAgreement.init(myPrivKey); + keyAgreement.doPhase(otherPubKey, true); + + return keyAgreement.generateSecret(); + } + + + synchronized KeyPair readKeyPair(Context context) + throws Exception { + return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); + } + + @SuppressLint("ApplySharedPref") + public KeyPair newPair(Context context) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + KeyPair kp; + try { + kp = generateKeyPair(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + ECPublicKey key = (ECPublicKey) kp.getPublic(); + byte[] x = key.getW().getAffineX().toByteArray(); + byte[] y = key.getW().getAffineY().toByteArray(); + BigInteger xbi = new BigInteger(1, x); + BigInteger ybi = new BigInteger(1, y); + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); + + ECCurve curve = x9.getCurve(); + ECPoint point = curve.createPoint(xbi, ybi); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); + + + ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters()); + byte[] privateKeyBytes = privateKey.getD().toByteArray(); + + String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + String keypString = base64Encode(privateKeyBytes); + prefsEditor.putString(kp_public, keyString); + prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString()); + prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString()); + prefsEditor.putString(kp_private, keypString); + prefsEditor.commit(); + return kp; + } + + + synchronized PublicKey readMyPublicKey(Context context) throws Exception { + + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0")); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN()); + java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); + return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); + } + + + public String uncryptMessage(Context context, String cyphered, String slug) { + getInstance(); + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + byte[] privateKey = getSharedSecret(context, account); + try { + Cipher outCipher = Cipher.getInstance("ECIES", PROVIDER); + // outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); + outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); + byte[] plaintext = outCipher.doFinal(Base64.decode(cyphered, Base64.DEFAULT)); + String finalText = new String(plaintext); + return finalText; + } catch (Exception e) { + e.printStackTrace(); + } + return ""; + + } + + + public PublicKey readPublicKey(String keyStr) throws Exception { + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name); + ECCurve curve = parameterSpec.getCurve(); + ECPoint point = curve.decodePoint(base64Decode(keyStr)); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, parameterSpec); + return kf.generatePublic(pubSpec); + } + + + public PrivateKey readPrivateKey(byte[] key) throws Exception { + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name); + ECPrivateKeySpec pubSpec = new ECPrivateKeySpec(new BigInteger(1, key), parameterSpec); + return kf.generatePrivate(pubSpec); + } + + synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN()); + return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); + } + + + private synchronized KeyPair getPair(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String strPub = prefs.getString(kp_public, ""); + String strPriv = prefs.getString(kp_private, ""); + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { + return newPair(context); + } + try { + return readKeyPair(context); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + PublicKey getServerKey(Context context, Account account) throws Exception { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); + return readPublicKey(serverKey); + } + + @SuppressWarnings({"unused", "RedundantSuppression"}) + public byte[] getSharedSecret(Context context, Account account) { + try { + KeyPair keyPair = getPair(context); + if (keyPair != null) { + return generateSecret(keyPair.getPrivate(), getServerKey(context, account)); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return null; + } + + public String getPublicKey(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + + return prefs.getString(kp_public, ""); + } + + @SuppressLint("ApplySharedPref") + public void saveServerKey(Context context, Account account, String strPeerPublic) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + + prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); + prefsEditor.commit(); + } +} diff --git a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java new file mode 100644 index 000000000..1e6113019 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java @@ -0,0 +1,311 @@ +package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.text.Html; +import android.text.SpannableString; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.Entities.Notification; +import app.fedilab.android.client.GNUAPI; +import app.fedilab.android.fragments.DisplayNotificationsFragment; + +import static android.text.Html.FROM_HTML_MODE_LEGACY; +import static app.fedilab.android.helper.BaseHelper.INTENT_ACTION; +import static app.fedilab.android.helper.BaseHelper.INTENT_TARGETED_ACCOUNT; +import static app.fedilab.android.helper.BaseHelper.NOTIFICATION_INTENT; +import static app.fedilab.android.helper.BaseHelper.PREF_INSTANCE; +import static app.fedilab.android.helper.BaseHelper.PREF_KEY_ID; +import static app.fedilab.android.helper.BaseHelper.getMainLogo; + +import static app.fedilab.android.helper.BaseHelper.notify_user; + + +public class NotificationsHelper { + + public static HashMap since_ids = new HashMap<>(); + + public static void task(Context context, Account account) { + APIResponse apiResponse; + String key = account.getUsername() + "@" + account.getInstance(); + + String last_notifid = null; + if (since_ids.containsKey(key)) { + last_notifid = since_ids.get(key); + } + + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + //Check which notifications the user wants to see + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + //User disagree with all notifications + if (!notif_follow && !notif_add && !notif_mention && !notif_share && !notif_poll) + return; //Nothing is done + //No account connected, the service is stopped + if (!Helper.isLoggedIn(context)) + return; + //If WIFI only and on WIFI OR user defined any connections to use the service. + if (!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(context)) { + if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { + API api = new API(context, account.getInstance(), account.getToken()); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false); + } else { + GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid); + } + onRetrieveNotifications(context, apiResponse, account); + } + } + + + public static void onRetrieveNotifications(Context context, APIResponse apiResponse, final Account account) { + List notificationsReceived = apiResponse.getNotifications(); + if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) + return; + String key = account.getUsername() + "@" + account.getInstance(); + since_ids.put(key, apiResponse.getNotifications().get(0).getId()); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + final List notifications = new ArrayList<>(); + int pos = 0; + for (Notification notif : notificationsReceived) { + if (max_id == null || notif.getId().compareTo(max_id) > 0) { + notifications.add(pos, notif); + pos++; + } + } + if (notifications.size() == 0) + return; + //No previous notifications in cache, so no notification will be sent + int newFollows = 0; + int newAdds = 0; + int newMentions = 0; + int newShare = 0; + int newPolls = 0; + int newStatus = 0; + String notificationUrl = null; + String title = null; + String message = null; + String targeted_account = null; + Helper.NotifType notifType = Helper.NotifType.MENTION; + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + for (Notification notification : notifications) { + switch (notification.getType()) { + case "mention": + notifType = Helper.NotifType.MENTION; + if (notif_mention) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_mention)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_mention)); + if (notification.getStatus() != null) { + if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); + } + } + newFollows++; + } + break; + case "status": + notifType = Helper.NotifType.STATUS; + if (notif_status) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_status)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_status)); + if (notification.getStatus() != null) { + if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); + } + } + newStatus++; + } + break; + case "reblog": + notifType = Helper.NotifType.BOOST; + if (notif_share) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_reblog)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_reblog)); + newShare++; + } + break; + case "favourite": + notifType = Helper.NotifType.FAV; + if (notif_add) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_favourite)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_favourite)); + newAdds++; + } + break; + case "follow_request": + notifType = Helper.NotifType.FOLLLOW; + if (notif_follow) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow_request)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow_request)); + targeted_account = notification.getAccount().getId(); + newFollows++; + } + break; + case "follow": + notifType = Helper.NotifType.FOLLLOW; + if (notif_follow) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow)); + targeted_account = notification.getAccount().getId(); + newFollows++; + } + break; + case "poll": + notifType = Helper.NotifType.POLL; + if (notif_poll) { + if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) + message = context.getString(R.string.notif_poll_self); + else + message = context.getString(R.string.notif_poll); + newPolls++; + } + break; + default: + } + + } + + int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus; + if (allNotifCount > 0) { + //Some others notification + final Intent intent = new Intent(context, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); + intent.putExtra(PREF_KEY_ID, account.getId()); + if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) + intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account); + intent.putExtra(PREF_INSTANCE, account.getInstance()); + notificationUrl = notifications.get(0).getAccount().getAvatar(); + if (notificationUrl != null) { + + Handler mainHandler = new Handler(Looper.getMainLooper()); + + final String finalNotificationUrl = notificationUrl; + Helper.NotifType finalNotifType = notifType; + String finalMessage = message; + String finalMessage1 = message; + Runnable myRunnable = () -> Glide.with(context) + .asBitmap() + .load(finalNotificationUrl) + .listener(new RequestListener() { + + @Override + public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + return false; + } + + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(), + getMainLogo(context)), finalNotifType, context.getString(R.string.top_notification), finalMessage1); + String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); + editor.apply(); + } + return false; + } + }) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, Transition transition) { + notify_user(context, account, intent, resource, finalNotifType, context.getString(R.string.top_notification), finalMessage); + String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); + editor.apply(); + } + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + + } + }); + mainHandler.post(myRunnable); + + } + + } + } +} diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java new file mode 100644 index 000000000..fd1902003 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -0,0 +1,37 @@ +package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + + +public class PushNotifications { + + + public void registerPushNotifications(Context context, String endpoint, String slug) { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + new PostSubscriptionAsyncTask(context, account, endpoint); + } + + + +} diff --git a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java index 78217c54a..3ce174f64 100644 --- a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java +++ b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java @@ -15,54 +15,26 @@ package app.fedilab.android.jobs; * see . */ import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Looper; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -import static app.fedilab.android.helper.Helper.INTENT_ACTION; -import static app.fedilab.android.helper.Helper.INTENT_TARGETED_ACCOUNT; -import static app.fedilab.android.helper.Helper.NOTIFICATION_INTENT; -import static app.fedilab.android.helper.Helper.PREF_INSTANCE; -import static app.fedilab.android.helper.Helper.PREF_KEY_ID; import static app.fedilab.android.helper.Helper.canNotify; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.notify_user; + /** @@ -137,223 +109,8 @@ public class BaseNotificationsSyncJob extends Job { return; //Retrieve users in db that owner has. for (Account account : accounts) { - APIResponse apiResponse; - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api = new API(getContext(), account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); - } else { - GNUAPI gnuApi = new GNUAPI(getContext(), account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); - } - onRetrieveNotifications(apiResponse, account); + NotificationsHelper.task(getContext(), account); } } } - - - private void onRetrieveNotifications(APIResponse apiResponse, final Account account) { - List notificationsReceived = apiResponse.getNotifications(); - if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) - return; - final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - final List notifications = new ArrayList<>(); - int pos = 0; - for (Notification notif : notificationsReceived) { - if (max_id == null || notif.getId().compareTo(max_id) > 0) { - notifications.add(pos, notif); - pos++; - } - } - if (notifications.size() == 0) - return; - //No previous notifications in cache, so no notification will be sent - int newFollows = 0; - int newAdds = 0; - int newMentions = 0; - int newShare = 0; - int newPolls = 0; - int newStatus = 0; - String notificationUrl = null; - String title = null; - final String message; - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - - for (Notification notification : notifications) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - newMentions++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_mention)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_mention)); - } - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - newStatus++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_status)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_status)); - } - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - newShare++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_reblog)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_reblog)); - - } - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - newAdds++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_favourite)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_favourite)); - } - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow_request)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - newPolls++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - title = getContext().getString(R.string.notif_poll_self); - else - title = getContext().getString(R.string.notif_poll); - } - } - break; - default: - } - } - - int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus; - if (allNotifCount > 0) { - //Some others notification - int other = allNotifCount - 1; - if (other > 0) - message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other); - else - message = ""; - final Intent intent = new Intent(getContext(), MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); - intent.putExtra(PREF_KEY_ID, account.getId()); - if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) - intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account); - intent.putExtra(PREF_INSTANCE, account.getInstance()); - if (notificationUrl != null) { - final String finalTitle = title; - Handler mainHandler = new Handler(Looper.getMainLooper()); - - final String finalNotificationUrl = notificationUrl; - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> Glide.with(getContext()) - .asBitmap() - .load(finalNotificationUrl) - .listener(new RequestListener() { - - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - - notify_user(getContext(), account, intent, BitmapFactory.decodeResource(getContext().getResources(), - getMainLogo(getContext())), finalNotifType, finalTitle, message); - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); - editor.apply(); - } - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - notify_user(getContext(), account, intent, resource, finalNotifType, finalTitle, message); - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); - editor.apply(); - } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - mainHandler.post(myRunnable); - - } - - } - } - } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java b/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java deleted file mode 100644 index 0665bb38d..000000000 --- a/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java +++ /dev/null @@ -1,536 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.preference.PreferenceManager; -import android.text.Html; -import android.text.SpannableString; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; -import com.koushikdutta.async.http.AsyncHttpClient; -import com.koushikdutta.async.http.AsyncHttpRequest; -import com.koushikdutta.async.http.Headers; -import com.koushikdutta.async.http.WebSocket; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.List; -import java.util.Objects; - -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; - -import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.getNotificationIcon; - - -/** - * Created by Thomas on 29/11/2017. - * Manage service for streaming api and new notifications - */ - -public abstract class BaseLiveNotificationService extends Service implements NetworkStateReceiver.NetworkStateReceiverListener { - - public static String CHANNEL_ID = "live_notifications"; - public static int totalAccount = 0; - public static int eventsCount = 0; - private static final HashMap lastNotification = new HashMap<>(); - private static final HashMap webSocketFutures = new HashMap<>(); - - - protected Account account; - private NetworkStateReceiver networkStateReceiver; - private NotificationChannel channel; - - public void onCreate() { - super.onCreate(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - networkStateReceiver = new NetworkStateReceiver(); - networkStateReceiver.addListener(this); - registerReceiver(networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION)); - - - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - } - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction(); - totalAccount = 0; - if (accountStreams != null) { - for (Account account : accountStreams) { - if (account.getSocial() == null || account.getSocial().equals("MASTODON") || account.getSocial().equals("PLEROMA")) { - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (allowStream) { - totalAccount++; - } - } - } - } - if (Build.VERSION.SDK_INT >= 26) { - Intent myIntent = new Intent(BaseLiveNotificationService.this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity( - BaseLiveNotificationService.this, - 0, - myIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(getString(R.string.top_notification)) - .setContentIntent(pendingIntent) - .setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - - if (notification != null) { - startForeground(2, notification); - } else { - return; - } - } - if (!notify) { - stopSelf(); - return; - } - if (totalAccount == 0) { - stopSelf(); - } - } - - private void startStream() { - - SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - if (liveNotifications) { - List accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction(); - if (accountStreams != null) { - for (final Account accountStream : accountStreams) { - if (accountStream.getSocial() == null || accountStream.getSocial().equals("MASTODON") || accountStream.getSocial().equals("PLEROMA")) { - startWork(accountStream); - } - } - } - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - - - if (!notify || intent == null || intent.getBooleanExtra("stop", false)) { - totalAccount = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_DETACH); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - assert notificationManager != null; - notificationManager.deleteNotificationChannel(CHANNEL_ID); - } - if (intent != null) { - intent.replaceExtras(new Bundle()); - } - stopSelf(); - } - if (totalAccount > 0) { - return START_STICKY; - } - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_live_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - if (networkStateReceiver != null) { - networkStateReceiver.removeListener(this); - unregisterReceiver(networkStateReceiver); - } - } - - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - - private void taks(Account account) { - - if (account != null) { - Headers headers = new Headers(); - headers.add("Authorization", "Bearer " + account.getToken()); - headers.add("Connection", "Keep-Alive"); - headers.add("method", "GET"); - headers.add("scheme", "https"); - String notif_url = "user:notification"; - if (account.getSocial().toUpperCase().equals("PLEROMA")) - notif_url = "user"; - String urlKey = "wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken(); - Uri url = Uri.parse(urlKey); - AsyncHttpRequest.setDefaultHeaders(headers, url); - String key = account.getAcct() + "@" + account.getInstance(); - if (webSocketFutures.get(key) == null || !Objects.requireNonNull(webSocketFutures.get(key)).isOpen()) { - AsyncHttpClient.getDefaultInstance().websocket("wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken(), "wss", (ex, webSocket) -> { - webSocketFutures.put(account.getAcct() + "@" + account.getInstance(), webSocket); - if (ex != null) { - return; - } - webSocket.setStringCallback(s -> { - try { - JSONObject eventJson = new JSONObject(s); - onRetrieveStreaming(account, eventJson); - } catch (JSONException ignored) { - } - }); - - webSocket.setClosedCallback(ex1 -> { - if (networkStateReceiver.connected) { - startWork(account); - } - }); - webSocket.setDataCallback((emitter, byteBufferList) -> { - // note that this data has been read - byteBufferList.recycle(); - }); - }); - } - } - } - - - private void startWork(Account accountStream) { - - String key = accountStream.getAcct() + "@" + accountStream.getInstance(); - Thread thread = Helper.getThreadByName("notif_live_" + key); - if (thread == null) { - thread = new Thread() { - @Override - public void run() { - taks(accountStream); - } - }; - thread.setName("notif_live_" + key); - thread.start(); - } else if (thread.getState() != Thread.State.RUNNABLE) { - thread.interrupt(); - thread = new Thread() { - @Override - public void run() { - taks(accountStream); - } - }; - thread.setName("notif_live_" + key); - thread.start(); - } - - - } - - private void onRetrieveStreaming(Account account, JSONObject response) { - if (response == null) - return; - final Notification notification; - Bundle b = new Bundle(); - boolean canSendBroadCast = true; - Helper.EventStreaming event; - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - try { - if ("notification".equals(response.get("event").toString())) { - eventsCount++; - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - if (notificationChannel != null) { - startForeground(2, notificationChannel); - } else { - return; - } - } - - event = Helper.EventStreaming.NOTIFICATION; - notification = API.parseNotificationResponse(BaseLiveNotificationService.this, new JSONObject(response.get("payload").toString())); - b.putParcelable("data", notification); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - boolean canNotify = Helper.canNotify(BaseLiveNotificationService.this); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false); - String key = account.getAcct() + "@" + account.getInstance(); - if (lastNotification.containsKey(key) && notification.getId().compareTo(Objects.requireNonNull(lastNotification.get(key))) <= 0) { - canNotify = false; - } - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (notification.getId().compareTo(Objects.requireNonNull(lastNotif)) <= 0) { - canNotify = false; - } - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (!allowStream) { - canNotify = false; - } - - if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && liveNotifications && canNotify && notify) { - lastNotification.put(key, notification.getId()); - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status); - String message = null; - if (somethingToPush) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog)); - } else { - canSendBroadCast = false; - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite)); - } else { - canSendBroadCast = false; - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - message = getString(R.string.notif_poll_self); - else - message = getString(R.string.notif_poll); - } else { - canSendBroadCast = false; - } - break; - default: - } - //Some others notification - final Intent intent = new Intent(BaseLiveNotificationService.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); - intent.putExtra(Helper.PREF_KEY_ID, account.getId()); - intent.putExtra(Helper.PREF_INSTANCE, account.getInstance()); - if (targeted_account != null) { - intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); - } - final String finalMessage = message; - Handler mainHandler = new Handler(Looper.getMainLooper()); - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> { - if (finalMessage != null) { - Glide.with(BaseLiveNotificationService.this) - .asBitmap() - .load(notification.getAccount().getAvatar()) - .listener(new RequestListener() { - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - assert e != null; - Helper.notify_user(BaseLiveNotificationService.this, account, intent, BitmapFactory.decodeResource(getResources(), - getMainLogo(BaseLiveNotificationService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - Helper.notify_user(BaseLiveNotificationService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - } - }; - mainHandler.post(myRunnable); - } - } - - if (canSendBroadCast) { - b.putString("userIdService", account.getId()); - Intent intentBC = new Intent(Helper.RECEIVE_DATA); - intentBC.putExtra("eventStreaming", event); - intentBC.putExtras(b); - b.putParcelable("data", notification); - LocalBroadcastManager.getInstance(BaseLiveNotificationService.this).sendBroadcast(intentBC); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId()); - editor.apply(); - } - } - } catch (Exception ignored) { - } - } - - @Override - public void networkAvailable() { - startStream(); - } - - @Override - public void networkUnavailable() { - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_live_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - } -} diff --git a/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java b/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java deleted file mode 100644 index f4ddeeb7b..000000000 --- a/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java +++ /dev/null @@ -1,520 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2019 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.text.Html; -import android.text.SpannableString; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; - -import java.util.HashMap; -import java.util.List; -import java.util.Objects; - -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; -import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; - -import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.getNotificationIcon; -import static app.fedilab.android.helper.Helper.sleeps; - - -/** - * Created by Thomas on 10/09/2019. - * Manage service for live notifications delayed - */ - -public class LiveNotificationDelayedService extends Service { - - - public static String CHANNEL_ID = "live_notifications"; - public static int totalAccount = 0; - public static int eventsCount = 0; - public static HashMap since_ids = new HashMap<>(); - protected Account account; - private NotificationChannel channel; - private boolean fetch; - - - public void onCreate() { - super.onCreate(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - } - - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction(); - totalAccount = 0; - if (accountStreams != null) { - for (Account account : accountStreams) { - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (allowStream) { - totalAccount++; - } - } - } - - if (Build.VERSION.SDK_INT >= 26) { - Intent myIntent = new Intent(LiveNotificationDelayedService.this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity( - LiveNotificationDelayedService.this, - 0, - myIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setShowWhen(false) - .setContentIntent(pendingIntent) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - if (notification != null) { - startForeground(1, notification); - } else { - return; - } - } - - - if (!notify) { - stopSelf(); - return; - } - if (totalAccount > 0) { - startStream(); - } else { - stopSelf(); - } - - } - - - @Override - public void onDestroy() { - super.onDestroy(); - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_delayed_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - if (!notify || intent == null || intent.getBooleanExtra("stop", false)) { - totalAccount = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_DETACH); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - assert notificationManager != null; - notificationManager.deleteNotificationChannel(CHANNEL_ID); - } - if (intent != null) { - intent.replaceExtras(new Bundle()); - } - stopSelf(); - } - if (totalAccount > 0) { - return START_STICKY; - } - return START_NOT_STICKY; - } - - - private void startStream() { - - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - if (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED) { - List accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - fetch = true; - if (accountStreams != null) { - for (final Account accountStream : accountStreams) { - String key = accountStream.getUsername() + "@" + accountStream.getInstance(); - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + accountStream.getId() + accountStream.getInstance(), true); - if (!allowStream) { - continue; - } - if (!sleeps.containsKey(key)) { - sleeps.put(key, 30000); - } - Thread thread = Helper.getThreadByName("notif_delayed_" + key); - if (thread == null) { - startThread(accountStream, key); - } else if (thread.getState() != Thread.State.RUNNABLE) { - thread.interrupt(); - startThread(accountStream, key); - } - } - } - } - } - - private void startThread(Account accountStream, String key) { - Thread thread = new Thread() { - @Override - public void run() { - while (fetch) { - task(accountStream); - fetch = (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED); - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - try { - Thread.sleep(sleeps.get(key)); - } catch (InterruptedException e) { - SystemClock.sleep(sleeps.get(key)); - } - } - } - } - }; - thread.setName("notif_delayed_" + key); - thread.start(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - - private void task(Account account) { - String key = account.getUsername() + "@" + account.getInstance(); - APIResponse apiResponse; - - String last_notifid = null; - if (since_ids.containsKey(key)) { - last_notifid = since_ids.get(key); - } - apiResponse = null; - try { - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api; - api = new API(LiveNotificationDelayedService.this, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false); - } else { - GNUAPI gnuApi; - gnuApi = new GNUAPI(LiveNotificationDelayedService.this, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid); - } - } catch (Exception ignored) { - } - - if (apiResponse != null && apiResponse.getNotifications() != null && apiResponse.getNotifications().size() > 0) { - since_ids.put(key, apiResponse.getNotifications().get(0).getId()); - for (Notification notification : apiResponse.getNotifications()) { - if (last_notifid != null && notification.getId().compareTo(last_notifid) > 0) { - onRetrieveStreaming(account, notification); - sleeps.put(key, 30000); - } else { - if (apiResponse.getNotifications().size() == 1) { //TODO: use min id with Pixelfed when available for removing this fix. - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - int newWaitTime = sleeps.get(key) + 30000; - if (newWaitTime > 900000) { - newWaitTime = 900000; - } - sleeps.put(key, newWaitTime); - } else { - sleeps.put(key, 60000); - } - } - break; - } - } - } else { - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - int newWaitTime = sleeps.get(key) + 30000; - if (newWaitTime > 900000) { - newWaitTime = 900000; - } - sleeps.put(key, newWaitTime); - } else { - sleeps.put(key, 60000); - } - } - } - - - private void onRetrieveStreaming(Account account, Notification notification) { - - Bundle b = new Bundle(); - boolean canSendBroadCast = true; - Helper.EventStreaming event; - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - try { - eventsCount++; - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID) - .setShowWhen(false) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - - if (notificationChannel != null) { - startForeground(1, notificationChannel); - } else { - return; - } - } - - event = Helper.EventStreaming.NOTIFICATION; - boolean canNotify = Helper.canNotify(LiveNotificationDelayedService.this); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false); - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (!allowStream) { - canNotify = false; - } - if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && canNotify && notify) { - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status); - - String message = null; - if (somethingToPush) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog)); - } else { - canSendBroadCast = false; - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite)); - } else { - canSendBroadCast = false; - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - message = getString(R.string.notif_poll_self); - else - message = getString(R.string.notif_poll); - } else { - canSendBroadCast = false; - } - break; - default: - } - - //Some others notification - final Intent intent = new Intent(LiveNotificationDelayedService.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); - intent.putExtra(Helper.PREF_KEY_ID, account.getId()); - intent.putExtra(Helper.PREF_INSTANCE, account.getInstance()); - if (targeted_account != null) { - intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); - } - final String finalMessage = message; - Handler mainHandler = new Handler(Looper.getMainLooper()); - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> { - if (finalMessage != null) { - Glide.with(LiveNotificationDelayedService.this) - .asBitmap() - .load(notification.getAccount().getAvatar()) - .listener(new RequestListener() { - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - Helper.notify_user(LiveNotificationDelayedService.this, account, intent, BitmapFactory.decodeResource(getResources(), - getMainLogo(LiveNotificationDelayedService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - - Helper.notify_user(LiveNotificationDelayedService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - } - }; - mainHandler.post(myRunnable); - } - } - - if (canSendBroadCast) { - b.putString("userIdService", account.getId()); - Intent intentBC = new Intent(Helper.RECEIVE_DATA); - intentBC.putExtra("eventStreaming", event); - intentBC.putExtras(b); - b.putParcelable("data", notification); - LocalBroadcastManager.getInstance(LiveNotificationDelayedService.this).sendBroadcast(intentBC); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId()); - editor.apply(); - } - } catch (Exception ignored) { - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java deleted file mode 100644 index 5ff6fb569..000000000 --- a/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java +++ /dev/null @@ -1,45 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import app.fedilab.android.helper.Helper; -import app.fedilab.android.jobs.NotificationsSyncJob; - -import static app.fedilab.android.helper.BaseHelper.startStreaming; - -/** - * Created by Thomas on 22/09/2017. - * BroadcastReceiver for restarting the service - */ - -public class RestartLiveNotificationReceiver extends BroadcastReceiver { - - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - int type = Helper.liveNotifType(context); - if (type == Helper.NOTIF_DELAYED || type == Helper.NOTIF_LIVE) { - startStreaming(context); - } else { - NotificationsSyncJob.schedule(false); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java deleted file mode 100644 index 2e1e6329f..000000000 --- a/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2019 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -/** - * Created by Thomas on 18/10/2019. - * BroadcastReceiver for restarting delayed notification service - */ - -public class StopDelayedNotificationReceiver extends BroadcastReceiver { - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java deleted file mode 100644 index 7c4b924e8..000000000 --- a/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -/** - * Created by Thomas on 22/09/2017. - * BroadcastReceiver for restarting the service - */ - -public class StopLiveNotificationReceiver extends BroadcastReceiver { - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java new file mode 100644 index 000000000..e9ffec29b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -0,0 +1,76 @@ +package app.fedilab.android.services; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * 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. + * + * Fedilab 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 Fedilab; if not, + * see . */ +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.unifiedpush.android.connector.MessagingReceiver; +import org.unifiedpush.android.connector.MessagingReceiverHandler; + + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.helper.ECDH; +import app.fedilab.android.helper.NotificationsHelper; +import app.fedilab.android.helper.PushNotifications; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + + +class handler implements MessagingReceiverHandler { + + + @Override + public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { + + new Thread(() -> { + if (context != null) { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + ECDH ecdh = ECDH.getInstance(); + //ecdh.uncryptMessage(context, s, slug); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + NotificationsHelper.task(context, account); + } + }).start(); + } + + @Override + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { + if (context != null) { + new PushNotifications() + .registerPushNotifications(context, endpoint, slug); + } + } + + @Override + public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { + } + + @Override + public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { + } + + @Override + public void onUnregistered(@Nullable Context context, @NotNull String s) { + } +} + +public class UnifiedPushService extends MessagingReceiver { + public UnifiedPushService() { + super(new handler()); + } +} diff --git a/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java b/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java deleted file mode 100644 index 98bd9f980..000000000 --- a/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2020 Thomas Schneider - * - * This file is a part of Fedilab - * - * 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. - * - * Fedilab 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 Fedilab; if not, - * see . */ - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -public class UpgradeReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - - if (intent.getAction() != null && intent.getAction().compareTo(Intent.ACTION_MY_PACKAGE_REPLACED) == 0) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - streamingServiceIntent = new Intent(context, LiveNotificationService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java index cd963c9cc..d2ade4fce 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java +++ b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java @@ -278,6 +278,22 @@ public class AccountDAO { } + /** + * Returns all Account in db + * + * @return Account List + */ + public List getPushNotificationAccounts() { + + try { + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, "(" + Sqlite.COL_SOCIAL + " = 'MASTODON' OR " + Sqlite.COL_SOCIAL + " = 'PLEROMA') AND " + Sqlite.COL_OAUTHTOKEN + " != 'null'", null, null, null, Sqlite.COL_INSTANCE + " ASC", null); + return cursorToListUser(c); + } catch (Exception e) { + return null; + } + } + + /** * Returns an Account by token * @@ -311,6 +327,22 @@ public class AccountDAO { } } + /** + * Returns an Account by token + * + * @param userName String + * @param instance String + * @return Account + */ + public Account getUniqAccountUsernameInstance(String userName, String instance) { + + try { + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_USERNAME + " = \"" + userName + "\" AND " + Sqlite.COL_INSTANCE + " = \"" + instance + "\"", null, null, null, null, "1"); + return cursorToUser(c); + } catch (Exception e) { + return null; + } + } /** * Test if the current user is already stored in data base diff --git a/app/src/main/res/layout/fragment_settings_notifications.xml b/app/src/main/res/layout/fragment_settings_notifications.xml index 57514173e..550392e07 100644 --- a/app/src/main/res/layout/fragment_settings_notifications.xml +++ b/app/src/main/res/layout/fragment_settings_notifications.xml @@ -89,102 +89,6 @@ android:layout_marginBottom="10dp" android:textColor="@color/mastodonC2" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - استبدال Instagram بـ Bibliogram Bibliogram هو بديل مفتوح المصدر لـ Instagram يركز على الخصوصية. أدخل مضيفك المخصص أو اتركه فارغًا لاستخدام bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it أخفِ شريط إشعارات Fedilab لإخفاء الإشعارات المتبقية على شريط الحالة ، اضغط على زر أيقونة العين ثم قم بإلغاء تحديد: \"العرض على شريط الحالة\" سيتم تأجيل الإشعارات كل 30 ثانية. الشيء الذي من شأنه أن يسمح باستنزاف أقل للبطارية. diff --git a/app/src/main/res/values-ber/strings.xml b/app/src/main/res/values-ber/strings.xml index 9a1f819e3..ec602b027 100644 --- a/app/src/main/res/values-ber/strings.xml +++ b/app/src/main/res/values-ber/strings.xml @@ -1005,6 +1005,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a67bead13..f72745b02 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 4aec5de5e..937136b65 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -1031,6 +1031,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e0273f6fe..4cbadf16e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -997,6 +997,9 @@ Ara ja pots connectar-te al compte escrivint %1$s en el primer camp i fen Substituir Instagram per Bibliogram Bibliogram és un frontal alternatiu de codi obert per Instagram que protegeix la privacitat. Introdueix aquí el teu servidor personalitzat o deixa-ho en blanc i s\'usarà bibliogram.art + Substituïu Reddit per Libreddit + Libreddit és una interfície alternativa a Reddit, és de codi obert i prioritza la privadesa. + Introduïu el vostre servidor personalitzat o deixeu-ho em blanc per utilitzar libredd.it Amaga la barra de notificacions de Fedilab Per amagar la notificació que queda a la barra d\'estat, toca la icona de l\'ull i després desactiva: \"Mostra a la barra d\'estat\" S\'ajornaran les notificacions cada 30 segons. Això permet gastar menys la pila. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9c37afec8..de6e2f2c0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1021,6 +1021,9 @@ Uživatelské jméno a heslo nejsou nikdy ukládány. Jsou použity pouze během Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index a6d3402f6..770ee512c 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -1038,6 +1038,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index ea8730614..357022be4 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1007,6 +1007,9 @@ Erstat Instagram med Bibliogram Bibliogram er et open source Instagram front-end alternativt fokuseret på datafortrolighed. Angiv din tilpassede vært eller lad stå tomt for at benytte bibliogram.art + Erstat Reddit med Libreddit + Libreddit er en fortrolighedorienteret, alternativ open-source Twitter front-end. + Angiv din tilpassede vært eller lad stå tomt for brug af libredd.it Skjul Fedilab-notifikationsbjælke For at skjule den resterende notifikation på statusbjælken, så klik på øjeikonknappen og fjern derefter markeringen: \"Vises på statusbjælke\" Notifikationer udskydes hvert 30. sekund. Dette vil betyde lavere batteridræning. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 855f4a377..1aa416c7c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -995,6 +995,9 @@ Sobald du die ersten Buchstaben eintippst, werden Namensvorschläge angezeigt\n\ Instagram durch Bibliogram ersetzen Bibliogram ist ein alternatives Open-Source-Frontend von Instagram, das sich auf den Datenschutz konzentriert. Geben Sie Ihren benutzerdefinierten Host ein oder lassen Sie das Feld für die Verwendung von bibliogram.art frei + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Fedilab Benachrichtigungsleiste ausblenden Um die verbleibende Benachrichtigung in der Statusleiste zu verstecken, klicken Sie auf die Augensymbol-Schaltfläche und deaktivieren Sie: \"In Statusleiste anzeigen\" Benachrichtigungen werden 30 Sekunden verzögert. Dadurch wird weniger Akku verbraucht. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 36cecd254..e5b57a634 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Απόκρυψη της γραμμής ειδοποιήσεων του Φέντιλαμπ Για απόκρυψη της υπόλοιπης ειδοποίησης στη γραμμή κατάστασης, πίεσε στο πλήκτρο με το μάτι, και μετά απο-επέλεξε την...: «Προβολή στη γραμμή κατάστασης» Οι ειδοποιήσεις θα καθυστερούν κάθε 30 δευτερόλεπτα. Αυτό θα βοηθήσει στην χαμηλότερη απομύζηση της μπαταρίας. diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index a006323cb..f1097a348 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7a8edecf2..c7639d9f5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en Reemplazar Instagram con Bibliogram Bibliogram es una interfaz alternativa de Instagram de código abierto centrada en la privacidad. Introduzca su servidor personalizado o déjelo en blanco para usar bibliogram.art + Reemplazar Reddit con Libreddit + Libreddit es una interfaz alternativa de código abierto de Reddit enfocada en la privacidad. + Introduce tu servidor personalizado o déjalo en blanco para usar libredd.it Ocultar la barra de notificaciones de Fedilab Para ocultar el resto de la notificación en la barra de estado, presione en el botón con el ícono de un ojo y luego desactivar \"Mostrar en la barra de estado\" Las notificaciones se retrasarán cada 30 segundos. Esto permitirá gastar menos batería. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1875fb203..9475083ef 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -1006,6 +1006,9 @@ Ordeztu Instagram Bibliogram-ekin Bibliogram pribatutasuna aintzat duen Instagram interfaze libre bat da. Sartu zure ostalari pertsonalizatua edo laga hutsik bibliogram.art erabiltzeko + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Ezkutatu Fedilab jakinarazpen-barra Egoera barran geratzen den jakinarazpena ezkutatzeko, sakatu begiaren ikonoa eta desmarkatu \"Erakutsi egoera-barran\" Jakinarazpenak 30 segundo atzeratuko dira. Honela bateria gutxiago erabiliko da. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6cbff0c2c..5ec13c8e8 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index edb9826ac..98a918ba6 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1004,6 +1004,9 @@ Le bouton de connexion s’activera une fois qu’un domaine valide sera renseig Remplacer Instagram par Bibliogram Bibliogram est une interface ouverte en alternative à Instagram axée sur la confidentialité. Entrez votre hôte personnalisé ou laissez vide pour utiliser bibliogram.art + Remplacer Reddit par Libreddit + Libreddit est une alternative ouverte à l\'interface de Reddit axée sur la protection de la vie privée. + Entrez votre hôte personnalisé ou laissez vide pour utiliser libredd.it Masquer la barre de notification de Fedilab Pour cacher le reste de la notification dans la barre de statut, cliquez sur le bouton en forme d\'œil, puis décochez: \"Afficher dans la barre de statut\" Les notifications seront mises à jour toutes les 30 secondes. Cela permet de réduire la consommation de la batterie. diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 40dbd0069..f454efe6c 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -1007,6 +1007,9 @@ Substituír Instagram con Bibliogram Bibliogram é unha alternativa de código aberto centrada na privacidade a interface de Instagram. Escribe o servidor personalizado ou deixa en branco para usar bibliogram.art + Substituir Reddit por Libreddit + Libreddit é unha alternativa de código aberto á interface de Reddit centrada na privacidade. + Escribe o teu servidor personalizado ou deixa baleiro para usar libredd.it Ocultar barra de notificacións de Fedilab Para ocultar as notificacións remanentes na barra de estado, pulsa na icona do ollo e desmarca: \"Mostrar en barra de estado\" As notificacións retrasaranse cada 30 segundos. Esto permite aforrar batería. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8bd4dd36c..a07ec3ff6 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -999,6 +999,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2314c6e5d..de49581c4 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1005,6 +1005,9 @@ A Yandexnek megvan a saját adatvédelmi szabályzata, ami itt található: http Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index e99f11e33..bfc37fb13 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -1006,6 +1006,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 2c10e7436..78ef65a08 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2d1b98d38..f80bb7a16 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1007,6 +1007,9 @@ Sostituisci Instagram con Bibliogram Bibliogram è un\'interfaccia alternativa e open source per Instagram, focalizzata sulla privacy. Inserisci il tuo host personalizzato o lascia vuoto per usare bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Nascondi barra di notifica di Fedilab Per nascondere le notifiche rimanenti nella barra di stato, premi il pulsante con l\'icona di un occhio e deseleziona: \"Mostra nella barra di stato\" Le notifiche saranno differite ogni 30 secondi. Questo consentirà un minore consumo di batteria. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 00cc800a1..8f317af85 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -990,6 +990,9 @@ InstagramをBibliogramに置き換える Bibliogramはプライバシーを重視したオープンソースのInstagramの代替フロントエンドです。 カスタムホストを入力してください。空欄にするとbibliogram.artが使用されます。 + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it 通知バーからFedilabを非表示にする ステータスバー上の通知を非表示にするには、目のアイコンボタンをタップして「ステータスバーに表示する」のチェックを外します 通知は30秒ごとに遅延が発生します。バッテリーの持ちが改善します。 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 4cdbaa019..54bcf9782 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2b01e6283..5216ea768 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -999,6 +999,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index fc52acdc8..9737cb1fe 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -1005,6 +1005,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b043050da..e608c232d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1006,6 +1006,9 @@ Je kunt beginnen met typen en er zullen namen gesuggereerd worden.\n\n Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index ff3d7affc..0bb1891a8 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -997,6 +997,9 @@ Adresser vil bli foreslått når du begynner å skrive.\n\n Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index d463b31db..a3d76011e 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1435945d1..e21305bf1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1020,6 +1020,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index fecdf65cd..1d661884e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ab6248538..f2a00db65 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1013,6 +1013,9 @@ Aceste date sunt strict confidențiale și pot fi folosite doar de aplicație. Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ba4798d13..ab249b41e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1023,6 +1023,9 @@ Заменить Instagram на Bibliogram Bibliogram это альтернативный front-end для Instagram с открытым исходным кодом и сфокусированный на приватности. Введите свой собственный хост или оставьте пустым для использования bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Скрыть панель уведомлений Fedilab Чтобы скрыть уведомление в строке состояния, нажмите на кнопку с пиктограммой глаза и снимите флажок: \"Показывать в строке состояния\" Уведомления будут появляться на 30 секунд позже. Это позволит уменьшить влияние на батарею. diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 7d42faaa1..2835c3a31 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -970,6 +970,9 @@ Remplasa Instagram cun Bibliogram Bibliogram est un\'interfache alternativa pro Instagram, a mitza aberta e progetada pro sa riservadesa. Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro bibliogram.art + Remplasa Reddit cun Libreddit + Libreddit est un\'interfache alternativa a mitza aberta pro Reddit progetada pro sa riservadesa. + Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro impreare libredd.it Cua sa barra de is notìficas de Fedilab Pro cuare is notìficas chi abarrant in sa barra de istadu toca in su butone cun s\'icona a forma de ogru e boga sa seletzione a: \"Ammustra in sa barra de istadu\" Is notìficas ant a èssere perlongadas cada 30 segundos. Custu at a permìtere de consumire de mancu sa bateria. diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index e610a2635..b3770d7e9 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 946744b31..e7a24b6bd 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -1023,6 +1023,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 6b574837f..6cc1f77d1 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -1015,6 +1015,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 16e023810..d2425fca8 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Dölj Fedilab meddelandefält För att dölja kvarvarande notifieringar i statusbaren, klicka på ögonikons knappen och kryssa ur: \"Visa i statusbar\" Notifieringar kommer fördröjas var 30:e sekund. Detta kommer att dra mindre ström. diff --git a/app/src/main/res/values-szl/strings.xml b/app/src/main/res/values-szl/strings.xml index bb03e6017..2321ddacb 100644 --- a/app/src/main/res/values-szl/strings.xml +++ b/app/src/main/res/values-szl/strings.xml @@ -1021,6 +1021,9 @@ Zamiyń Instagram na Bibliogram Bibliogram to ôtwartozdrzōdłowo, skupiōno na prywatności alternatywa do przodnich funkcyji Instagrama. Wkludź swōj włosny host abo ôstow prōzne, żeby używać bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Skryj posek powiadōmiyń Fedilab Żeby skryć permanyntne powiadōmiynie we posku statusu, tyknij knefel z ôkym i ôdznacz „Pokoż we posku statusu” Powiadōmiynia bydōm ôpōźniōne co 30 sekund. To przizwoli na myńsze wyużycie bateryje. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9874c96f9..3d5f12c90 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1001,6 +1001,9 @@ Instagram\'ı Bibliogram ile Değiştirin Bibliogram, mahremiyete odaklanan açık kaynaklı bir alternatif Instagram arayüzdür. Enter your custom host or leave blank for using bibliogram.art + Reddit yerine Libreddit kullan + Libreddit, mahremiyete odaklanan açık kaynaklı bir alternatif Reddit arayüzüdür. + Sunucu adresi girin ya da libredd.it sunucusunu kullanmak için boş bırakın Fedilab bildirim çubuğunu gizle Durum çubuğunda kalan bildirimi gizlemek için, göz simgesine dokunun ve ardından \"Durum çubuğunda göster\" seçeneğinin işaretini kaldırın Bildirimler 30 saniye geciktirilecek. Bu sayede pil harcaması azalacak. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1494e4791..03d767708 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1013,6 +1013,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e13158ff5..406945ade 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -996,6 +996,9 @@ và %d thông báo khác Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e01abc672..104e8f57e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -999,6 +999,9 @@ 将 Instagram 替换成 Bibliogram Bibliogram 是个注重隐私保护的 Instagram 开源替代前端。 输入你的自定义主机或者留空以使用 bibliogram.art + 用 Libreddit 替换 Reddit + Libreddit 是一个开源的 Reddit 替代前端,专注于隐私。 + 输入您的自定义主机或留空使用 libredd.it 隐藏 Fedilab 通知栏 要隐藏留在状态栏里的通知,请点击眼睛图标的按钮,然后取消勾选:“在状态栏内显示” 通知将被延迟为每30秒产生。这将允许耗尽较少电池。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a47bc69a0..96ce51bff 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -998,6 +998,9 @@ Yandex 有適當的隱私權政策,可以在這裡找到:https://yandex.ru/l 將 Instagram 以 Bibliogram 取代 Bibliogram 是一個尊重隱私的開放原始碼替代 Instagram 前端。 輸入您的自訂主機或留空以使用 bibliogram.art + 用 Libreddit 取代 Reddit + Libreddit 是一個尊重隱私的開放原始碼替代 Reddit 前端。 + 輸入您的自訂主機或留空以使用 libredd.it 隱藏 Fedilab 通知列 要隱藏狀態列中的其餘通知,點擊眼睛圖示的按鈕上然後取消勾選「在狀態列中顯示」 通知將會每30秒被延遲一次。這將會降低電池的消耗。 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 16f7dc261..d8fa2cf2d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -130,4 +130,13 @@ #f3f3f3 #606984 + + + #ff0000 + #ffa500 + #ffff00 + #008000 + #0000ff + #4b0082 + #ee82ee diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4423d094a..21e7f8ba4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,7 +82,7 @@ Information below may reflect the user\'s profile incompletely. Insert emoji The app did not collect custom emojis for the moment. - Live notifications + Push notifications Are you sure you want to logout? Are you sure you want to logout @%1$s@%2$s? @@ -984,7 +984,6 @@ If disabled, sensitive media will be hidden with a button Store media in full size with a long press on previews Add an ellipse button at the top right for listing all tags/instances/lists - Keep an open connection to the streaming API for live notifications. During the time slot, the app will send notifications. You can reverse (ie: silent) this time slot with the right spinner. Display a Fedilab button below profile picture. It is a shortcut for accessing in-app features. Allow to reply directly in timelines below statuses @@ -1121,8 +1120,7 @@ Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" - Notifications will be delayed every 30 seconds. That will allow to drain less battery. - Live notifications delayed + Use a push notifications system for getting notifications in real time. No live notifications Notifications will be fetched every 15 minutes. Add notes @@ -1243,5 +1241,8 @@ Video cache in MB, zero means no cache. Watermarks Automatically add a watermark at the bottom of pictures. The text can be customized for each account. + No distributors found! + You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message. + Select a distributor diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml new file mode 100644 index 000000000..b55a17c82 --- /dev/null +++ b/app/src/playstore/AndroidManifest.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java new file mode 100644 index 000000000..04735db05 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java @@ -0,0 +1,50 @@ +package app.fedilab.android.helper; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.sqlite.SQLiteDatabase; + +import org.unifiedpush.android.connector_fcm_added.RegistrationFCM; + +import java.util.List; + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + + +public class PushHelper { + + + public static void startStreaming(Context context) { + int liveNotifications = liveNotifType(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + NotificationsSyncJob.schedule(false); + switch (liveNotifications) { + case NOTIF_PUSH: + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + ((Activity) context).runOnUiThread(() -> { + RegistrationFCM registration = new RegistrationFCM(); + for (Account account : accounts) { + registration.registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + } + }); + }).start(); + break; + case NOTIF_NONE: + new RegistrationFCM().unregisterApp(context); + break; + } + } +} diff --git a/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java new file mode 100644 index 000000000..460989e47 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java @@ -0,0 +1,10 @@ +package app.fedilab.android.services; + +import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver; + +public class FakeDistributor extends FakeDistributorReceiver { + public FakeDistributor() { + super(new HandlerFCM()); + } + +} \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java new file mode 100644 index 000000000..ed6e3a53d --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java @@ -0,0 +1,18 @@ +package app.fedilab.android.services; + +import android.content.Context; + +import androidx.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector_fcm_added.GetEndpointHandler; + +public class HandlerFCM implements GetEndpointHandler { + + @Override + public @NotNull String getEndpoint(@Nullable Context context, @NotNull String token, @NotNull String instance) { + return "https://gotify.fedilab.app/FCM?token=" + token + "&instance=" + instance; + } + + +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index c95742af2..a83b27e67 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,8 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.google.gms:google-services:4.3.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -17,6 +18,7 @@ allprojects { repositories { jcenter() google() + maven { url 'https://jitpack.io' } } }