Select browser on opening link

This commit is contained in:
M66B 2022-06-15 15:36:29 +02:00
parent 00b04423de
commit d494c31e3c
15 changed files with 209 additions and 54 deletions

View File

@ -52,6 +52,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.INSERT" />
</intent>

View File

@ -52,6 +52,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.INSERT" />
</intent>

View File

@ -52,6 +52,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.INSERT" />
</intent>

View File

@ -52,6 +52,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.INSERT" />
</intent>

View File

@ -5673,8 +5673,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
fragment.setArguments(args);
fragment.show(parentFragment.getParentFragmentManager(), "open:link");
} else {
boolean browse_links = prefs.getBoolean("browse_links", false);
Helper.view(context, UriHelper.guessScheme(uri), browse_links, browse_links);
boolean tabs = prefs.getBoolean("open_with_tabs", true);
Helper.view(context, UriHelper.guessScheme(uri), !tabs, !tabs);
}
}

View File

@ -620,6 +620,9 @@ public class ApplicationEx extends Application
int class_min_difference = prefs.getInt("class_min_difference", 50);
if (class_min_difference == 0)
editor.putBoolean("classification", false);
} else if (version < 1918) {
editor.remove("confirm_links");
editor.remove("browse_links");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !BuildConfig.DEBUG)

View File

@ -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<Package> 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<ResolveInfo> 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<Package> 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;
}
}
}

View File

@ -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();

View File

@ -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());

View File

@ -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) {

View File

@ -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" />
<TextView
android:id="@+id/tvOpenWith"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/title_browse"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbNotAgain" />
<Spinner
android:id="@+id/spOpenWith"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvOpenWith" />
<ImageButton
android:id="@+id/ibMore"
android:layout_width="wrap_content"
@ -205,7 +224,7 @@
android:background="?android:attr/selectableItemBackgroundBorderless"
android:padding="3dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cbNotAgain"
app:layout_constraintTop_toBottomOf="@id/spOpenWith"
app:srcCompat="@drawable/expander" />
<TextView
@ -326,6 +345,12 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnDefault" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpOpenWith"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="tvOpenWith,spOpenWith" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpDifferent"
android:layout_width="0dp"

View File

@ -112,18 +112,6 @@
app:layout_constraintTop_toBottomOf="@id/tvConfirmLinksHint"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swBrowseLinks"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_browse_links"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swCheckLinksDbl"
app:switchPadding="12dp" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swConfirmImages"
android:layout_width="0dp"
@ -133,7 +121,7 @@
android:text="@string/title_advanced_confirm_images"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/swBrowseLinks"
app:layout_constraintTop_toBottomOf="@+id/swCheckLinksDbl"
app:switchPadding="12dp" />
<TextView

View File

@ -161,6 +161,13 @@
app:showAsAction="never">
<menu />
</item>
<item
android:id="@+id/menu_confirm_links"
android:checkable="true"
android:icon="@drawable/twotone_insert_link_45_24"
android:title="@string/title_setup_reset_links"
app:showAsAction="never" />
</group>
<group android:id="@+id/group_operations">

View File

@ -653,7 +653,6 @@
<string name="title_advanced_confirm_links">Confirm opening links</string>
<string name="title_advanced_check_links_dbl">Check domain block lists for suspicious links</string>
<string name="title_advanced_browse_links">Delegate opening links to Android</string>
<string name="title_advanced_confirm_images">Show no images by default</string>
<string name="title_advanced_ask_images">Confirm showing images</string>
<string name="title_advanced_confirm_html">Show reformatted messages by default</string>
@ -1253,7 +1252,6 @@
<string name="title_reply_hard_bounce">Send hard bounce</string>
<string name="title_reply_template">Reply with template</string>
<string name="title_move_undo">Moving to %1$s (%2$d)</string>
<string name="title_open_with">Open with</string>
<string name="title_authentication_failed">%1$s authentication failed</string>
<string name="title_on_blocklist">On spam block list</string>
@ -1873,6 +1871,7 @@
<string name="title_insert_arrow" translatable="false">Insert arrow</string>
<string name="title_add">Add</string>
<string name="title_browse">Open with</string>
<string name="title_browse_embedded">%1$s (embedded)</string>
<string name="title_info">Info</string>
<string name="title_download">Download</string>
<string name="title_report">Report</string>

View File

@ -52,6 +52,11 @@
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.INSERT" />
</intent>