0
0
mirror of https://github.com/TrianguloY/UrlChecker.git synced 2024-09-19 11:52:14 +02:00

Use the component (package+class) instead of the package only for intent-related operations.

Shows the icon + real label too

Hopefully fixes #196
This commit is contained in:
TrianguloY 2024-05-05 12:55:51 +02:00
parent 49a7bd164b
commit 143ddfb375
7 changed files with 141 additions and 107 deletions

View File

@ -1,9 +1,11 @@
package com.trianguloy.urlchecker.modules.companions;
import android.content.ComponentName;
import android.content.Context;
import com.trianguloy.urlchecker.utilities.generics.GenericPref;
import com.trianguloy.urlchecker.utilities.methods.JavaUtils;
import com.trianguloy.urlchecker.utilities.wrappers.IntentApp;
import java.net.URL;
import java.util.Arrays;
@ -44,27 +46,28 @@ public class LastOpened {
}
/**
* Sorts an existing list of [packages] with the preferred order
* Sorts an existing list of [intentApps] with the preferred order
*/
public void sort(List<String> packages, String url) {
Collections.sort(packages, (from, another) -> comparePrefer(from, another, url));
public void sort(List<IntentApp> intentApps, String url) {
Collections.sort(intentApps, (from, another) ->
comparePrefer(from.getComponent(), another.getComponent(), url));
}
/**
* Marks the [prefer] package as preferred over [others].
* Marks the [prefer] intentApp as preferred over [others].
*/
public void prefer(String prefer, List<String> others, String url) {
for (String other : others) {
prefer(prefer, other, 1, url);
public void prefer(IntentApp prefer, List<IntentApp> others, String url) {
for (var other : others) {
prefer(prefer.getComponent(), other.getComponent(), 1, url);
}
}
/* ------------------- private ------------------- */
/**
* Marks that [prefer] package is preferred over [other] as much as [amount] more
* Marks that [prefer] component is preferred over [other] as much as [amount] more
*/
private void prefer(String prefer, String other, int amount, String url) {
private void prefer(ComponentName prefer, ComponentName other, int amount, String url) {
// skip prefer over ourselves, it's useless
if (prefer.equals(other)) return;
@ -80,10 +83,10 @@ public class LastOpened {
}
/**
* Returns the current preference between these two packages.
* Returns the current preference between these two components.
* Equivalent result as [from].compareTo([another])
*/
private int comparePrefer(String from, String another, String url) {
private int comparePrefer(ComponentName from, ComponentName another, String url) {
// switch order if not lexicographically sorted
if (from.compareTo(another) > 0) {
return -comparePrefer(another, from, url);
@ -94,10 +97,10 @@ public class LastOpened {
}
/**
* The preference between two packages. ([left] must be lexicographically less than [right])
* The preference between two components. ([left] must be lexicographically less than [right])
*/
private GenericPref.Int getPref(String left, String right, String url) {
String prefName = String.format(PREFIX, left, right);
private GenericPref.Int getPref(ComponentName left, ComponentName right, String url) {
String prefName = String.format(PREFIX, left.flattenToShortString(), right.flattenToShortString());
if (perDomainPref.get()) {
prefName = getDomain(url) + " " + prefName;
}

View File

@ -22,11 +22,14 @@ import com.trianguloy.urlchecker.modules.companions.LastOpened;
import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.generics.GenericPref;
import com.trianguloy.urlchecker.utilities.methods.AndroidUtils;
import com.trianguloy.urlchecker.utilities.methods.JavaUtils;
import com.trianguloy.urlchecker.utilities.methods.PackageUtils;
import com.trianguloy.urlchecker.utilities.methods.UrlUtils;
import com.trianguloy.urlchecker.utilities.wrappers.IntentApp;
import com.trianguloy.urlchecker.utilities.wrappers.RejectionDetector;
import java.util.List;
import java.util.Objects;
/**
* This module contains an open and share buttons
@ -92,7 +95,7 @@ class OpenDialog extends AModuleDialog {
private final Incognito incognito;
private final RejectionDetector rejectionDetector;
private List<String> packages;
private List<IntentApp> intentApps;
private Button btn_open;
private ImageButton btn_openWith;
private View openParent;
@ -171,26 +174,27 @@ class OpenDialog extends AModuleDialog {
// ------------------- Spinner -------------------
/**
* Populates the spinner with the apps that can open it, in preference order
*/
/** Populates the spinner with the apps that can open it, in preference order */
private void updateSpinner(String url) {
packages = PackageUtils.getOtherPackages(UrlUtils.getViewIntent(url, null), getActivity());
intentApps = IntentApp.getOtherPackages(UrlUtils.getViewIntent(url, null), getActivity());
// remove referrer
if (noReferrerPref.get()) {
packages.remove(AndroidUtils.getReferrer(getActivity()));
var referrer = AndroidUtils.getReferrer(getActivity());
JavaUtils.removeIf(intentApps, ri -> Objects.equals(ri.getPackage(), referrer));
}
// remove rejected if desired (and is not a non-view action, like share)
// note: this will be called each time, so a rejected package will not be rejected again if the user changes the url and goes back. This is expected
if (rejectedPref.get() && Intent.ACTION_VIEW.equals(getActivity().getIntent().getAction())) {
packages.remove(rejectionDetector.getPrevious(url));
var rejected = rejectionDetector.getPrevious(url);
JavaUtils.removeIf(intentApps, ri -> Objects.equals(ri.getComponent(), rejected));
}
// check no apps
if (packages.isEmpty()) {
if (intentApps.isEmpty()) {
btn_open.setText(R.string.mOpen_noapps);
btn_open.setCompoundDrawables(null, null, null, null);
AndroidUtils.setEnabled(openParent, false);
btn_open.setEnabled(false);
btn_openWith.setVisibility(View.GONE);
@ -198,19 +202,24 @@ class OpenDialog extends AModuleDialog {
}
// sort
lastOpened.sort(packages, getUrl());
lastOpened.sort(intentApps, getUrl());
// set
btn_open.setText(getActivity().getString(R.string.mOpen_with, PackageUtils.getPackageName(packages.get(0), getActivity())));
var label = intentApps.get(0).getLabel(getActivity());
// label = getActivity().getString(R.string.mOpen_with, label);
btn_open.setText(label);
btn_open.setCompoundDrawables(intentApps.get(0).getIcon(getActivity()), null, null, null);
AndroidUtils.setEnabled(openParent, true);
btn_open.setEnabled(true);
menu.clear();
if (packages.size() == 1) {
if (intentApps.size() == 1) {
btn_openWith.setVisibility(View.GONE);
} else {
btn_openWith.setVisibility(View.VISIBLE);
for (int i = 1; i < packages.size(); i++) {
menu.add(Menu.NONE, i, i, getActivity().getString(R.string.mOpen_with, PackageUtils.getPackageName(packages.get(i), getActivity())));
for (int i = 1; i < intentApps.size(); i++) {
label = intentApps.get(i).getLabel(getActivity());
// label = getActivity().getString(R.string.mOpen_with, label);
menu.add(Menu.NONE, i, i, label);//.setIcon(intentApps.get(i).getIcon(getActivity()));
}
}
@ -221,23 +230,22 @@ class OpenDialog extends AModuleDialog {
/**
* Open url in a specific app
*
* @param index index from the packages list of the app to use
* @param index index from the intentApps list of the app to use
*/
private void openUrl(int index) {
// get
if (index < 0 || index >= packages.size()) return;
var chosen = packages.get(index);
if (index < 0 || index >= intentApps.size()) return;
var chosen = intentApps.get(index);
// update as preferred over the rest
lastOpened.prefer(chosen, packages, getUrl());
lastOpened.prefer(chosen, intentApps, getUrl());
// open
var intent = new Intent(getActivity().getIntent());
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
// preserve original VIEW intent
intent.setData(Uri.parse(getUrl()));
intent.setComponent(null);
intent.setPackage(chosen);
intent.setComponent(chosen.getComponent());
} else {
// replace with new VIEW intent
intent = UrlUtils.getViewIntent(getUrl(), chosen);

View File

@ -85,16 +85,10 @@ public interface JavaUtils {
return text;
}
/**
* Same as Arrays.compare, which is not available in api < 33
*/
static int compareArrays(int[] l, int[] r) {
int i = 0;
while (i < l.length && i < r.length) {
if (l[i] != r[i]) return Integer.signum(r[i] - l[i]);
i++;
}
return Integer.signum(r.length - l.length);
/** Removes elements from a collection matching a predicate */
static <E> void removeIf(Collection<E> collection, Function<E, Boolean> predicate) {
var iterator = collection.iterator();
while (iterator.hasNext()) if (predicate.apply(iterator.next())) iterator.remove();
}
/**

View File

@ -3,62 +3,13 @@ package com.trianguloy.urlchecker.utilities.methods;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Static utilities related to packages
*/
public interface PackageUtils {
/**
* Returns a list of packages that can open an intent, removing this app from the list
*
* @param baseIntent intent that the packages will be able to open
* @param cntx base context (and the app that will be filtered)
* @return the list of other packages
*/
static List<String> getOtherPackages(Intent baseIntent, Context cntx) {
List<String> packages = new ArrayList<>();
// get all packages
PackageManager packageManager = cntx.getPackageManager();
List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(baseIntent, Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL : 0);
// filter the current app
for (ResolveInfo resolveInfo : resolveInfos) {
if (!resolveInfo.activityInfo.packageName.equals(cntx.getPackageName())) {
packages.add(resolveInfo.activityInfo.packageName);
}
}
return packages;
}
/**
* Returns the app name of a package
*
* @param pack packagename to search
* @param cntx base context
* @return that app name
*/
static String getPackageName(String pack, Context cntx) {
final PackageManager pm = cntx.getPackageManager();
try {
// try getting the app label
return pm.getApplicationLabel(pm.getApplicationInfo(pack, PackageManager.GET_META_DATA)).toString();
} catch (PackageManager.NameNotFoundException e) {
// can't get the label
e.printStackTrace();
return cntx.getString(android.R.string.unknownName);
}
}
/**
* Wrapper for {@link Context#startActivity(Intent)} to catch thrown exceptions and show a toast instead
*/

View File

@ -7,6 +7,7 @@ import android.os.Parcelable;
import android.widget.Toast;
import com.trianguloy.urlchecker.R;
import com.trianguloy.urlchecker.utilities.wrappers.IntentApp;
import java.util.ArrayList;
import java.util.List;
@ -19,13 +20,13 @@ public interface UrlUtils {
/**
* Returns an intent that will open the given url, with an optional package
*
* @param url the url that will be opened
* @param packageName the package that will be opened, null to let android choose
* @param url the url that will be opened
* @param intentApp the intentApp that will be opened, null to let android choose
* @return the converted intent
*/
static Intent getViewIntent(String url, String packageName) {
static Intent getViewIntent(String url, IntentApp intentApp) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (packageName != null) intent.setPackage(packageName);
if (intentApp != null) intent.setComponent(intentApp.getComponent());
return intent;
}
@ -37,9 +38,9 @@ public interface UrlUtils {
*/
static void openUrlRemoveThis(String url, Context cntx) {
// get packages that can open the url
// get intents that can open the url
List<Intent> intents = new ArrayList<>();
for (String pack : PackageUtils.getOtherPackages(getViewIntent(url, null), cntx)) {
for (var pack : IntentApp.getOtherPackages(getViewIntent(url, null), cntx)) {
intents.add(getViewIntent(url, pack));
}

View File

@ -0,0 +1,78 @@
package com.trianguloy.urlchecker.utilities.wrappers;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.drawable.Drawable;
import android.os.Build;
import com.trianguloy.urlchecker.dialogs.MainDialog;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Represents a way to open something (A wrapper of {@link ResolveInfo}) */
public class IntentApp {
/**
* Returns a list of packages that can open an intent, removing this app from the list
*
* @param baseIntent intent that the packages will be able to open
* @param cntx base context (and the app that will be filtered)
* @return the list of other packages
*/
public static List<IntentApp> getOtherPackages(Intent baseIntent, Context cntx) {
// get all packages
var resolveInfos = cntx.getPackageManager().queryIntentActivityOptions(
new ComponentName(cntx, MainDialog.class.getName()),
null,
baseIntent,
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL : 0);
var intentApps = new ArrayList<IntentApp>();
for (var resolveInfo : resolveInfos) intentApps.add(new IntentApp(resolveInfo));
return intentApps;
}
/* ------------------- wrapper ------------------- */
private final ResolveInfo resolveInfo;
private static final Map<ComponentName, CharSequence> labelsCache = new HashMap<>();
private static final Map<ComponentName, Drawable> iconsCache = new HashMap<>();
private IntentApp(ResolveInfo resolveInfo) {
this.resolveInfo = resolveInfo;
}
/** Returns the package only */
public String getPackage() {
return resolveInfo.activityInfo.packageName;
}
/** Returns the component (package + class) */
public ComponentName getComponent() {
return new ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
}
/** Returns the label, cached */
public CharSequence getLabel(Context activity) {
var component = getComponent();
if (!labelsCache.containsKey(component)) labelsCache.put(component, resolveInfo.loadLabel(activity.getPackageManager()));
return labelsCache.get(component);
}
/** Returns the drawable, cached */
public Drawable getIcon(Context activity) {
var component = getComponent();
if (!iconsCache.containsKey(component)) {
var icon = resolveInfo.loadIcon(activity.getPackageManager());
icon.setBounds(0, 0, 50, 50);
iconsCache.put(component, icon);
}
return iconsCache.get(component);
}
}

View File

@ -1,6 +1,7 @@
package com.trianguloy.urlchecker.utilities.wrappers;
import android.app.Activity;
import android.content.ComponentName;
import com.trianguloy.urlchecker.utilities.generics.GenericPref;
import com.trianguloy.urlchecker.utilities.methods.AndroidUtils;
@ -16,7 +17,7 @@ import java.util.Objects;
public class RejectionDetector {
private static final int TIMEFRAME = 5000;
private final GenericPref.LstStr rejectLast; // [openedTimeMillis, package, url]
private final GenericPref.LstStr rejectLast; // [openedTimeMillis, component, url]
private final Activity cntx;
public RejectionDetector(Activity cntx) {
@ -24,21 +25,19 @@ public class RejectionDetector {
this.cntx = cntx;
}
/**
* Marks a url as opened to a package (at this moment)
*/
public void markAsOpen(String url, String packageName) {
rejectLast.set(List.of(Long.toString(System.currentTimeMillis()), packageName, url));
/** Marks a url as opened from an intentApp (at this moment) */
public void markAsOpen(String url, IntentApp intentApp) {
rejectLast.set(List.of(Long.toString(System.currentTimeMillis()), intentApp.getComponent().toShortString(), url));
}
/**
* returns the last package that opened the url if
* returns the last component that opened the url if
* - it happened in a short amount of time
* - (and) the url is the same
* - (and) the referrer app is the same
* null otherwise
*/
public String getPrevious(String url) {
public ComponentName getPrevious(String url) {
try {
var data = rejectLast.get();
@ -49,7 +48,7 @@ public class RejectionDetector {
&& Objects.equals(data.get(2), url)
&& Objects.equals(AndroidUtils.getReferrer(cntx), data.get(1))
? data.get(1)
? ComponentName.unflattenFromString(data.get(1))
: null;
} catch (Exception ignore) {
// just ignore errors while retrieving the data