diff --git a/app/src/main/java/eu/faircode/email/AdapterRule.java b/app/src/main/java/eu/faircode/email/AdapterRule.java index 95fac8e5b5..f2fc3b91e4 100644 --- a/app/src/main/java/eu/faircode/email/AdapterRule.java +++ b/app/src/main/java/eu/faircode/email/AdapterRule.java @@ -225,6 +225,9 @@ public class AdapterRule extends RecyclerView.Adapter { } else if (type == EntityRule.TYPE_NOTES) { String notes = jaction.getString("notes"); setAction(getAction(type), notes); + } else if (type == EntityRule.TYPE_URL) { + String url = jaction.getString("url"); + setAction(getAction(type), url); } else setAction(getAction(type), null); @@ -577,6 +580,8 @@ public class AdapterRule extends RecyclerView.Adapter { return R.string.title_rule_local_only; case EntityRule.TYPE_NOTES: return R.string.title_rule_notes; + case EntityRule.TYPE_URL: + return R.string.title_rule_url; default: throw new IllegalArgumentException("Unknown action type=" + type); } diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java index d83c7e3212..32298c9097 100644 --- a/app/src/main/java/eu/faircode/email/Core.java +++ b/app/src/main/java/eu/faircode/email/Core.java @@ -3100,7 +3100,7 @@ class Core { } while (count > 0); } - private static void onRule(Context context, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException { + private static void onRule(Context context, JSONArray jargs, EntityMessage message) throws JSONException, MessagingException, IOException { // Deferred rule (download headers, body, etc) DB db = DB.getInstance(context); diff --git a/app/src/main/java/eu/faircode/email/EntityRule.java b/app/src/main/java/eu/faircode/email/EntityRule.java index 789ff3bb12..661cf0c6bf 100644 --- a/app/src/main/java/eu/faircode/email/EntityRule.java +++ b/app/src/main/java/eu/faircode/email/EntityRule.java @@ -32,6 +32,7 @@ import android.database.Cursor; import android.net.Uri; import android.provider.ContactsContract; import android.text.TextUtils; +import android.util.Patterns; import androidx.annotation.NonNull; import androidx.preference.PreferenceManager; @@ -48,6 +49,9 @@ import org.jsoup.nodes.Element; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -128,6 +132,7 @@ public class EntityRule { static final int TYPE_SOUND = 16; static final int TYPE_LOCAL_ONLY = 17; static final int TYPE_NOTES = 18; + static final int TYPE_URL = 19; static final String ACTION_AUTOMATION = BuildConfig.APPLICATION_ID + ".AUTOMATION"; static final String EXTRA_RULE = "rule"; @@ -139,6 +144,7 @@ public class EntityRule { static final String JSOUP_PREFIX = "jsoup:"; private static final long SEND_DELAY = 5000L; // milliseconds private static final int MAX_NOTES_LENGTH = 512; // characters + private static final int URL_TIMEOUT = 15 * 1000; // milliseconds static boolean needsHeaders(EntityMessage message, List rules) { return needsHeaders(rules); @@ -181,7 +187,7 @@ public class EntityRule { static int run(Context context, List rules, EntityMessage message, List
headers, String html) - throws JSONException, MessagingException { + throws JSONException, MessagingException, IOException { int applied = 0; List stopped = new ArrayList<>(); @@ -564,7 +570,7 @@ public class EntityRule { return matched; } - boolean execute(Context context, EntityMessage message, String html) throws JSONException { + boolean execute(Context context, EntityMessage message, String html) throws JSONException, IOException { boolean executed = _execute(context, message, html); if (this.id != null && executed) { DB db = DB.getInstance(context); @@ -573,7 +579,7 @@ public class EntityRule { return executed; } - private boolean _execute(Context context, EntityMessage message, String html) throws JSONException, IllegalArgumentException { + private boolean _execute(Context context, EntityMessage message, String html) throws JSONException, IllegalArgumentException, IOException { JSONObject jaction = new JSONObject(action); int type = jaction.getInt("type"); EntityLog.log(context, EntityLog.Type.Rules, message, @@ -616,6 +622,8 @@ public class EntityRule { return onActionLocalOnly(context, message, jaction); case TYPE_NOTES: return onActionNotes(context, message, jaction, html); + case TYPE_URL: + return onActionUrl(context, message, jaction, html); default: throw new IllegalArgumentException("Unknown rule type=" + type + " name=" + name); } @@ -699,6 +707,11 @@ public class EntityRule { if (TextUtils.isEmpty(notes)) throw new IllegalArgumentException(context.getString(R.string.title_rule_notes_missing)); return; + case TYPE_URL: + String url = jargs.optString("url"); + if (TextUtils.isEmpty(url) || !Patterns.WEB_URL.matcher(url).matches()) + throw new IllegalArgumentException(context.getString(R.string.title_rule_url_missing)); + return; default: throw new IllegalArgumentException("Unknown rule type=" + type); } @@ -1386,6 +1399,29 @@ public class EntityRule { return true; } + private boolean onActionUrl(Context context, EntityMessage message, JSONObject jargs, String html) throws JSONException, IOException { + String url = jargs.getString("url"); + + Log.i("GET " + url); + + HttpURLConnection connection = null; + try { + connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setRequestMethod("GET"); + connection.setDoOutput(false); + connection.setReadTimeout(URL_TIMEOUT); + connection.setConnectTimeout(URL_TIMEOUT); + connection.setInstanceFollowRedirects(true); + ConnectionHelper.setUserAgent(context, connection); + connection.connect(); + } finally { + if (connection != null) + connection.disconnect(); + } + + return true; + } + private static Calendar getRelativeCalendar(boolean all, int minutes, long reference) { int d = minutes / (24 * 60); int h = minutes / 60 % 24; diff --git a/app/src/main/java/eu/faircode/email/FragmentRule.java b/app/src/main/java/eu/faircode/email/FragmentRule.java index 5b2fefa86b..6968bbc876 100644 --- a/app/src/main/java/eu/faircode/email/FragmentRule.java +++ b/app/src/main/java/eu/faircode/email/FragmentRule.java @@ -172,6 +172,8 @@ public class FragmentRule extends FragmentBase { private EditText etNotes; private ViewButtonColor btnColorNotes; + private EditText etUrl; + private BottomNavigationView bottom_navigation; private ContentLoadingProgressBar pbWait; @@ -190,6 +192,7 @@ public class FragmentRule extends FragmentBase { private Group grpDelete; private Group grpLocalOnly; private Group grpNotes; + private Group grpUrl; private ArrayAdapter adapterGroup; private ArrayAdapter adapterDay; @@ -362,6 +365,8 @@ public class FragmentRule extends FragmentBase { etNotes = view.findViewById(R.id.etNotes); btnColorNotes = view.findViewById(R.id.btnColorNotes); + etUrl = view.findViewById(R.id.etUrl); + bottom_navigation = view.findViewById(R.id.bottom_navigation); pbWait = view.findViewById(R.id.pbWait); @@ -381,6 +386,7 @@ public class FragmentRule extends FragmentBase { grpDelete = view.findViewById(R.id.grpDelete); grpLocalOnly = view.findViewById(R.id.grpLocalOnly); grpNotes = view.findViewById(R.id.grpNotes); + grpUrl = view.findViewById(R.id.grpUrl); adapterGroup = new ArrayAdapter<>(getContext(), R.layout.spinner_item1_dropdown, android.R.id.text1); etGroup.setThreshold(1); @@ -646,6 +652,7 @@ public class FragmentRule extends FragmentBase { actions.add(new Action(EntityRule.TYPE_TTS, getString(R.string.title_rule_tts))); actions.add(new Action(EntityRule.TYPE_SOUND, getString(R.string.title_rule_sound))); actions.add(new Action(EntityRule.TYPE_AUTOMATION, getString(R.string.title_rule_automation))); + actions.add(new Action(EntityRule.TYPE_URL, getString(R.string.title_rule_url))); adapterAction.addAll(actions); spAction.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @@ -849,6 +856,7 @@ public class FragmentRule extends FragmentBase { grpDelete.setVisibility(View.GONE); grpLocalOnly.setVisibility(View.GONE); grpNotes.setVisibility(View.GONE); + grpUrl.setVisibility(View.GONE); pbWait.setVisibility(View.VISIBLE); @@ -1300,7 +1308,6 @@ public class FragmentRule extends FragmentBase { etKeyword.setText(jaction.getString("keyword")); rgKeyword.check(jaction.optBoolean("set", true) ? R.id.keyword_add : R.id.keyword_delete); - break; case EntityRule.TYPE_MOVE: @@ -1359,6 +1366,10 @@ public class FragmentRule extends FragmentBase { !jaction.has("color") || jaction.isNull("color") ? null : jaction.getInt("color")); break; + + case EntityRule.TYPE_URL: + etUrl.setText(jaction.getString("url")); + break; } for (int pos = 0; pos < adapterAction.getCount(); pos++) @@ -1417,6 +1428,7 @@ public class FragmentRule extends FragmentBase { grpDelete.setVisibility(type == EntityRule.TYPE_DELETE ? View.VISIBLE : View.GONE); grpLocalOnly.setVisibility(type == EntityRule.TYPE_LOCAL_ONLY ? View.VISIBLE : View.GONE); grpNotes.setVisibility(type == EntityRule.TYPE_NOTES ? View.VISIBLE : View.GONE); + grpUrl.setVisibility(type == EntityRule.TYPE_URL ? View.VISIBLE : View.GONE); } private void onActionDelete() { @@ -1764,6 +1776,10 @@ public class FragmentRule extends FragmentBase { if (ncolor != Color.TRANSPARENT) jaction.put("color", ncolor); break; + + case EntityRule.TYPE_URL: + jaction.put("url", etUrl.getText().toString().trim()); + break; } } diff --git a/app/src/main/res/layout/fragment_rule.xml b/app/src/main/res/layout/fragment_rule.xml index db729bb7db..743b1e8ca6 100644 --- a/app/src/main/res/layout/fragment_rule.xml +++ b/app/src/main/res/layout/fragment_rule.xml @@ -1281,6 +1281,26 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvColorNotes" /> + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43c546c7d0..a4c6ec27c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1920,6 +1920,7 @@ Add Delete Local notes + URL Move Copy (label) Reply/forward @@ -1981,6 +1982,7 @@ Keyword missing This will send the intent \'%1$s\' with the extras \'%2$s\' Local notes missing + URL missing or invalid Permanent deletion is irreversible, so make sure the rule conditions are correct! Try to avoid bridging notifications to other devices, such as smartwatches. Not all devices support this. Edit group …