diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/LastOpened.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/LastOpened.java index 8946e44..17e7ab1 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/LastOpened.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/LastOpened.java @@ -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 packages, String url) { - Collections.sort(packages, (from, another) -> comparePrefer(from, another, url)); + public void sort(List 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 others, String url) { - for (String other : others) { - prefer(prefer, other, 1, url); + public void prefer(IntentApp prefer, List 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; } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/list/OpenModule.java b/app/src/main/java/com/trianguloy/urlchecker/modules/list/OpenModule.java index 1fe422d..228d01e 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/list/OpenModule.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/list/OpenModule.java @@ -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 packages; + private List 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); diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java index 7af8015..41c0cd2 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/JavaUtils.java @@ -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 void removeIf(Collection collection, Function predicate) { + var iterator = collection.iterator(); + while (iterator.hasNext()) if (predicate.apply(iterator.next())) iterator.remove(); } /** diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/PackageUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/PackageUtils.java index e945831..131624d 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/PackageUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/PackageUtils.java @@ -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 getOtherPackages(Intent baseIntent, Context cntx) { - List packages = new ArrayList<>(); - - // get all packages - PackageManager packageManager = cntx.getPackageManager(); - List 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 */ diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/UrlUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/UrlUtils.java index 8fa77a3..e69f95f 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/UrlUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/UrlUtils.java @@ -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 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)); } diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/IntentApp.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/IntentApp.java new file mode 100644 index 0000000..2d1a2b8 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/IntentApp.java @@ -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 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(); + for (var resolveInfo : resolveInfos) intentApps.add(new IntentApp(resolveInfo)); + return intentApps; + } + + /* ------------------- wrapper ------------------- */ + + private final ResolveInfo resolveInfo; + private static final Map labelsCache = new HashMap<>(); + private static final Map 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); + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/RejectionDetector.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/RejectionDetector.java index e4daeb2..5b43cf4 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/RejectionDetector.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/RejectionDetector.java @@ -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