diff --git a/app/src/amazon/AndroidManifest.xml b/app/src/amazon/AndroidManifest.xml
index 20a369edb2..c987d1f0da 100644
--- a/app/src/amazon/AndroidManifest.xml
+++ b/app/src/amazon/AndroidManifest.xml
@@ -52,6 +52,11 @@
+
+
+
+
+
diff --git a/app/src/fdroid/AndroidManifest.xml b/app/src/fdroid/AndroidManifest.xml
index aabac4262b..ee3f695cf7 100644
--- a/app/src/fdroid/AndroidManifest.xml
+++ b/app/src/fdroid/AndroidManifest.xml
@@ -52,6 +52,11 @@
+
+
+
+
+
diff --git a/app/src/github/AndroidManifest.xml b/app/src/github/AndroidManifest.xml
index 0fa9fd3659..afc490d16b 100644
--- a/app/src/github/AndroidManifest.xml
+++ b/app/src/github/AndroidManifest.xml
@@ -52,6 +52,11 @@
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 426d2484b6..02973f2a1b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -52,6 +52,11 @@
+
+
+
+
+
diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java
index 5fd5a8208b..83b8896734 100644
--- a/app/src/main/java/eu/faircode/email/AdapterMessage.java
+++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java
@@ -5673,8 +5673,8 @@ public class AdapterMessage extends RecyclerView.Adapter= Build.VERSION_CODES.O && !BuildConfig.DEBUG)
diff --git a/app/src/main/java/eu/faircode/email/FragmentDialogOpenLink.java b/app/src/main/java/eu/faircode/email/FragmentDialogOpenLink.java
index e59c11e1b9..766604afec 100644
--- a/app/src/main/java/eu/faircode/email/FragmentDialogOpenLink.java
+++ b/app/src/main/java/eu/faircode/email/FragmentDialogOpenLink.java
@@ -19,14 +19,17 @@ package eu.faircode.email;
Copyright 2018-2022 by Marcel Bokhorst (M66B)
*/
+import static androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION;
+
import android.app.Dialog;
-import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.net.Uri;
@@ -46,11 +49,14 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageButton;
+import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
@@ -66,7 +72,9 @@ import androidx.preference.PreferenceManager;
import java.net.IDN;
import java.net.InetAddress;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -104,6 +112,46 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
final Uri uri = UriHelper.guessScheme(_uri);
+ String def = null;
+ List pkgs = new ArrayList<>();
+ if (UriHelper.isHyperLink(uri)) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ Intent intent = new Intent(Intent.ACTION_VIEW)
+ .addCategory(Intent.CATEGORY_BROWSABLE)
+ .setData(uri);
+
+ ResolveInfo main = pm.resolveActivity(intent, 0);
+ if (main != null)
+ def = main.activityInfo.packageName;
+
+ int flags = (Build.VERSION.SDK_INT < Build.VERSION_CODES.M ? 0 : PackageManager.MATCH_ALL);
+ List ris = pm.queryIntentActivities(intent, flags);
+ for (ResolveInfo ri : ris) {
+ CharSequence label = pm.getApplicationLabel(ri.activityInfo.applicationInfo);
+ if (label == null)
+ continue;
+ pkgs.add(new Package(label.toString(), ri.activityInfo.packageName, false));
+
+ try {
+ Intent serviceIntent = new Intent();
+ serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
+ serviceIntent.setPackage(ri.activityInfo.packageName);
+ boolean tabs = (pm.resolveService(serviceIntent, 0) != null);
+ if (tabs)
+ pkgs.add(new Package(
+ getString(R.string.title_browse_embedded, label),
+ ri.activityInfo.packageName,
+ tabs));
+ } catch (Throwable ex) {
+ Log.e(ex);
+ }
+ }
+ } catch (Throwable ex) {
+ Log.e(ex);
+ }
+ }
+
// Process link
final Uri sanitized;
if (uri.isOpaque())
@@ -139,6 +187,7 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
final CheckBox cbSecure = dview.findViewById(R.id.cbSecure);
final CheckBox cbSanitize = dview.findViewById(R.id.cbSanitize);
final CheckBox cbNotAgain = dview.findViewById(R.id.cbNotAgain);
+ final Spinner spOpenWith = dview.findViewById(R.id.spOpenWith);
ibMore = dview.findViewById(R.id.ibMore);
tvMore = dview.findViewById(R.id.tvMore);
@@ -152,6 +201,7 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
btnDefault = dview.findViewById(R.id.btnDefault);
tvReset = dview.findViewById(R.id.tvReset);
+ final Group grpOpenWith = dview.findViewById(R.id.grpOpenWith);
final Group grpDifferent = dview.findViewById(R.id.grpDifferent);
// Wire
@@ -298,6 +348,44 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
}
});
+ ArrayAdapter adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_spinner_item, android.R.id.text1);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ adapter.addAll(pkgs);
+ spOpenWith.setAdapter(adapter);
+
+ String open_with_pkg = prefs.getString("open_with_pkg", null);
+ boolean open_with_tabs = prefs.getBoolean("open_with_tabs", true);
+ for (int position = 0; position < pkgs.size(); position++) {
+ Package pkg = pkgs.get(position);
+ if (Objects.equals(pkg.name, open_with_pkg) && pkg.tabs == open_with_tabs) {
+ spOpenWith.setSelection(position);
+ break;
+ }
+ if (Objects.equals(def, pkg.name))
+ spOpenWith.setSelection(position);
+ }
+
+ spOpenWith.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView> parent, View view, int position, long id) {
+ Package pkg = pkgs.get(position);
+ prefs.edit()
+ .putString("open_with_pkg", pkg.name)
+ .putBoolean("open_with_tabs", pkg.tabs)
+ .apply();
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView> parent) {
+ prefs.edit()
+ .remove("open_with_pkg")
+ .remove("open_with_tabs")
+ .apply();
+ }
+ });
+
+ grpOpenWith.setVisibility(pkgs.size() > 0 ? View.VISIBLE : View.GONE);
+
View.OnClickListener onMore = new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -469,24 +557,10 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
@Override
public void onClick(DialogInterface dialog, int which) {
Uri uri = Uri.parse(etLink.getText().toString());
- Log.i("Open link uri=" + uri);
- Helper.view(context, uri, false);
- }
- })
- .setNeutralButton(R.string.title_browse, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- // https://developer.android.com/training/basics/intents/sending#AppChooser
- Uri uri = Uri.parse(etLink.getText().toString());
- Log.i("Open link with uri=" + uri);
- Intent view = new Intent(Intent.ACTION_VIEW, uri);
- Intent chooser = Intent.createChooser(view, context.getString(R.string.title_select_app));
- try {
- startActivity(chooser);
- } catch (ActivityNotFoundException ex) {
- Log.w(ex);
- Helper.view(context, uri, true, true);
- }
+ Package pkg = (Package) spOpenWith.getSelectedItem();
+ Log.i("Open link uri=" + uri + " with=" + pkg);
+ boolean tabs = (pkg != null && pkg.tabs);
+ Helper.view(context, uri, !tabs, !tabs);
}
})
.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@@ -567,4 +641,21 @@ public class FragmentDialogOpenLink extends FragmentDialogBase {
return ssb;
}
+
+ private static class Package {
+ String title;
+ String name;
+ boolean tabs;
+
+ public Package(String title, String name, boolean tabs) {
+ this.title = title;
+ this.name = name;
+ this.tabs = tabs;
+ }
+
+ @Override
+ public String toString() {
+ return title;
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java
index ce385af387..05a6345694 100644
--- a/app/src/main/java/eu/faircode/email/FragmentMessages.java
+++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java
@@ -4985,6 +4985,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
String filter_language = prefs.getString("filter_language", null);
boolean perform_expunge = prefs.getBoolean("perform_expunge", true);
boolean compact = prefs.getBoolean("compact", false);
+ boolean confirm_links = prefs.getBoolean("confirm_links", true);
int zoom = prefs.getInt("view_zoom", compact ? 0 : 1);
int padding = prefs.getInt("view_padding", compact || !cards ? 0 : 1);
boolean quick_filter = prefs.getBoolean("quick_filter", false);
@@ -5100,6 +5101,10 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
menu.findItem(R.id.menu_theme).setVisible(viewType == AdapterMessage.ViewType.UNIFIED);
+ menu.findItem(R.id.menu_confirm_links)
+ .setChecked(confirm_links)
+ .setVisible(viewType == AdapterMessage.ViewType.THREAD);
+
menu.findItem(R.id.menu_select_all).setVisible(folder);
menu.findItem(R.id.menu_select_found).setVisible(viewType == AdapterMessage.ViewType.SEARCH);
menu.findItem(R.id.menu_mark_all_read).setVisible(folder);
@@ -5241,6 +5246,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} else if (itemId == R.id.menu_theme) {
onMenuTheme();
return true;
+ } else if (itemId == R.id.menu_confirm_links) {
+ onMenuConfirmLinks();
+ return true;
} else if (itemId == R.id.menu_select_all || itemId == R.id.menu_select_found) {
onMenuSelectAll();
return true;
@@ -5570,6 +5578,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
new FragmentDialogTheme().show(getParentFragmentManager(), "messages:theme");
}
+ private void onMenuConfirmLinks() {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
+ boolean compact = !prefs.getBoolean("confirm_links", true);
+ prefs.edit().putBoolean("confirm_links", compact).apply();
+ invalidateOptionsMenu();
+ }
+
private void clearMeasurements() {
sizes.clear();
heights.clear();
diff --git a/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java b/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java
index 7f40293042..11f70f87a3 100644
--- a/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java
+++ b/app/src/main/java/eu/faircode/email/FragmentOptionsPrivacy.java
@@ -66,7 +66,6 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
private ImageButton ibHelp;
private SwitchCompat swConfirmLinks;
private SwitchCompat swCheckLinksDbl;
- private SwitchCompat swBrowseLinks;
private SwitchCompat swConfirmImages;
private SwitchCompat swAskImages;
private SwitchCompat swHtmlImages;
@@ -107,7 +106,7 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
private final static int BIP39_WORDS = 6;
private final static String[] RESET_OPTIONS = new String[]{
- "confirm_links", "check_links_dbl", "browse_links",
+ "confirm_links", "check_links_dbl",
"confirm_images", "ask_images", "html_always_images", "confirm_html", "ask_html",
"disable_tracking",
"pin", "biometrics", "biometrics_timeout", "autolock", "autolock_nav",
@@ -131,7 +130,6 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
ibHelp = view.findViewById(R.id.ibHelp);
swConfirmLinks = view.findViewById(R.id.swConfirmLinks);
swCheckLinksDbl = view.findViewById(R.id.swCheckLinksDbl);
- swBrowseLinks = view.findViewById(R.id.swBrowseLinks);
swConfirmImages = view.findViewById(R.id.swConfirmImages);
swAskImages = view.findViewById(R.id.swAskImages);
swHtmlImages = view.findViewById(R.id.swHtmlImages);
@@ -197,13 +195,6 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
}
});
- swBrowseLinks.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- @Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
- prefs.edit().putBoolean("browse_links", checked).apply();
- }
- });
-
swConfirmImages.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
@@ -546,7 +537,6 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
swConfirmLinks.setChecked(prefs.getBoolean("confirm_links", true));
swCheckLinksDbl.setChecked(prefs.getBoolean("check_links_dbl", BuildConfig.PLAY_STORE_RELEASE));
swCheckLinksDbl.setEnabled(swConfirmLinks.isChecked());
- swBrowseLinks.setChecked(prefs.getBoolean("browse_links", false));
swConfirmImages.setChecked(prefs.getBoolean("confirm_images", true));
swAskImages.setChecked(prefs.getBoolean("ask_images", true));
swAskImages.setEnabled(swConfirmImages.isChecked());
diff --git a/app/src/main/java/eu/faircode/email/Helper.java b/app/src/main/java/eu/faircode/email/Helper.java
index 31519ef2c2..1980b18e41 100644
--- a/app/src/main/java/eu/faircode/email/Helper.java
+++ b/app/src/main/java/eu/faircode/email/Helper.java
@@ -410,7 +410,7 @@ public class Helper {
return permissions.toArray(new String[0]);
}
- static boolean hasCustomTabs(Context context, Uri uri) {
+ private static boolean hasCustomTabs(Context context, Uri uri, String pkg) {
String scheme = (uri == null ? null : uri.getScheme());
if (!"http".equals(scheme) && !"https".equals(scheme))
return false;
@@ -423,6 +423,8 @@ public class Helper {
Intent intent = new Intent();
intent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
intent.setPackage(info.activityInfo.packageName);
+ if (pkg != null && !pkg.equals(info.activityInfo.packageName))
+ continue;
if (pm.resolveService(intent, 0) != null)
return true;
}
@@ -821,8 +823,12 @@ public class Helper {
return;
}
- boolean has = hasCustomTabs(context, uri);
- Log.i("View=" + uri + " browse=" + browse + " task=" + task + " has=" + has);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ String open_with_pkg = prefs.getString("open_with_pkg", null);
+
+ boolean has = hasCustomTabs(context, uri, open_with_pkg);
+
+ Log.i("View=" + uri + " browse=" + browse + " task=" + task + " pkg=" + open_with_pkg + " has=" + has);
if (browse || !has) {
try {
@@ -833,12 +839,14 @@ public class Helper {
view.setDataAndType(uri, mimeType);
if (task)
view.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (UriHelper.isHyperLink(uri) &&
+ open_with_pkg != null && isInstalled(context, open_with_pkg))
+ view.setPackage(open_with_pkg);
context.startActivity(view);
} catch (Throwable ex) {
reportNoViewer(context, uri, ex);
}
} else {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean navbar_colorize = prefs.getBoolean("navbar_colorize", false);
int colorPrimary = resolveColor(context, R.attr.colorPrimary);
int colorPrimaryDark = resolveColor(context, R.attr.colorPrimaryDark);
@@ -881,6 +889,10 @@ public class Helper {
CustomTabsIntent customTabsIntent = builder.build();
customTabsIntent.intent.putExtra(Browser.EXTRA_HEADERS, headers);
+
+ if (open_with_pkg != null && isInstalled(context, open_with_pkg))
+ customTabsIntent.intent.setPackage(open_with_pkg);
+
try {
customTabsIntent.launchUrl(context, uri);
} catch (Throwable ex) {
diff --git a/app/src/main/res/layout/dialog_open_link.xml b/app/src/main/res/layout/dialog_open_link.xml
index 503aa7b0b3..de2187e594 100644
--- a/app/src/main/res/layout/dialog_open_link.xml
+++ b/app/src/main/res/layout/dialog_open_link.xml
@@ -75,8 +75,6 @@
android:id="@+id/tvLink"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="3dp"
- android:layout_marginEnd="12dp"
android:ellipsize="end"
android:singleLine="true"
android:text="Original link"
@@ -197,6 +195,27 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbSanitize" />
+
+
+
+
+
+
-
-
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5e215bb4d6..a2dfca488f 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -653,7 +653,6 @@
Confirm opening links
Check domain block lists for suspicious links
- Delegate opening links to Android
Show no images by default
Confirm showing images
Show reformatted messages by default
@@ -1253,7 +1252,6 @@
Send hard bounce
Reply with template
Moving to %1$s (%2$d)
- Open with
%1$s authentication failed
On spam block list
@@ -1873,6 +1871,7 @@
Insert arrow
Add
Open with
+ %1$s (embedded)
Info
Download
Report
diff --git a/app/src/play/AndroidManifest.xml b/app/src/play/AndroidManifest.xml
index ee4969ac6b..26cab85f8b 100644
--- a/app/src/play/AndroidManifest.xml
+++ b/app/src/play/AndroidManifest.xml
@@ -52,6 +52,11 @@
+
+
+
+
+