From a9d5d9b2a538260f11a48bf41ec5379f056691a2 Mon Sep 17 00:00:00 2001 From: Pablo Ortigosa <55493443+PabloOQ@users.noreply.github.com> Date: Sun, 17 Dec 2023 15:33:00 +0000 Subject: [PATCH 01/19] Added auto clipoard borrower to incognito Added fenix forks support Added chromium support --- .../modules/companions/Incognito.java | 133 +++++++++++++++++- .../openUrlHelpers/AutoClipboard.java | 43 ++++++ .../openUrlHelpers/ClipboardBorrower.java | 125 ++++++++++++++++ .../companions/openUrlHelpers/UrlHelper.java | 41 ++++++ .../urlchecker/modules/list/OpenModule.java | 8 +- .../utilities/methods/AndroidUtils.java | 83 ++++++++++- .../utilities/methods/JavaUtils.java | 16 +++ app/src/main/res/values/strings.xml | 8 ++ 8 files changed, 448 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/AutoClipboard.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelper.java diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java index 9ec64682..b03f96c2 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java @@ -1,13 +1,25 @@ package com.trianguloy.urlchecker.modules.companions; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelper.compatibility.compatible; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelper.compatibility.notCompatible; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelper.compatibility.urlNeedsHelp; + +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.view.View; import android.widget.ImageButton; import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelper; import com.trianguloy.urlchecker.utilities.generics.GenericPref; import com.trianguloy.urlchecker.utilities.methods.AndroidUtils; +import com.trianguloy.urlchecker.utilities.methods.JavaUtils; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; /** * Manages the incognito feature @@ -28,6 +40,78 @@ public Incognito(Context cntx) { this.pref = PREF(cntx); } + + private static final Set possibleExtras = new HashSet<>(); + private static final Map> + findPackage = new HashMap<>(); + private static final Map> + transform = new HashMap<>(); + + static { + // There are 2 functions: + // - One checks if the app/fork is the same as the one we want to open in incognito + // - The other applies the necessary changes so it opens on incognito, it returns if + // the app needs help to input the URL + + // fenix (firefox) + { + String key = "fenix"; + // https://bugzilla.mozilla.org/show_bug.cgi?id=1807531 + // https://github.com/search?q=repo%3Amozilla-mobile%2Ffirefox-android+PRIVATE_BROWSING_MODE&type=code + String extra = "private_browsing_mode"; + possibleExtras.add(extra); + + // all firefox apps share the same home activity + String activity = "org.mozilla.fenix.HomeActivity"; + // exclude tor browser, as it is always in incognito + Set exclude = new HashSet<>(); + exclude.add("org.torproject.torbrowser"); + JavaUtils.BiFunction isThis = (context, intent) -> { + String pckg = intent.getPackage(); + if (exclude.contains(pckg)) return false; + Set activities = AndroidUtils.getActivities(context, pckg); + return activities.contains(activity); + }; + + JavaUtils.Function setIncognito = (intent) -> { + intent.putExtra("private_browsing_mode", true); + return false; + }; + + findPackage.put(key, isThis); + transform.put(key, setIncognito); + } + + //chromium (chrome) + { + String key = "chromium"; + // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java;l=346;drc=fb3fab0be2804a2864783c326518f1acb0402968 + String extra = "com.google.android.apps.chrome.EXTRA_OPEN_NEW_INCOGNITO_TAB"; + String extra2 = "EXTRA_OPEN_NEW_INCOGNITO_TAB"; // just in case, but this shouldn't be needed + possibleExtras.add(extra); + possibleExtras.add(extra2); + + // all chromium apps have the same main activity + String activity = "com.google.android.apps.chrome.Main"; + JavaUtils.BiFunction isThis = (context, intent) -> { + String pckg = intent.getPackage(); + String mainActivity = AndroidUtils.getMainActivity(context, pckg); + return mainActivity.equals(activity); + }; + + // extras do not work in chromium + // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java;drc=e39fffa6900a679961f5992b8f24a084853b811a;l=1036 + // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:chrome/android/java/src/org/chromium/chrome/browser/IntentHandler.java;l=988;drc=fb3fab0be2804a2864783c326518f1acb0402968 + JavaUtils.Function setIncognito = (intent) -> { + intent.setComponent(new ComponentName(intent.getPackage(), "org.chromium.chrome.browser.incognito.IncognitoTabLauncher")); + return true; + }; + + findPackage.put(key, isThis); + transform.put(key, setIncognito); + } + } + /** * Initialization from a given intent and a button to toggle */ @@ -37,13 +121,11 @@ public void initFrom(Intent intent, ImageButton button) { switch (pref.get()) { case AUTO: default: - // for Firefox - state = intent.getBooleanExtra("private_browsing_mode", false); + state = isIncognito(intent); visible = true; break; case HIDDEN: - // for Firefox - state = intent.getBooleanExtra("private_browsing_mode", false); + state = isIncognito(intent); visible = false; break; case DEFAULT_ON: @@ -79,11 +161,48 @@ public void initFrom(Intent intent, ImageButton button) { } } + /** + * Returns if an intent will launch incognito for a given intent/app, only checks extras + */ + private boolean isIncognito(Intent intent) { + boolean incognito = false; + // Find any extra + for (String extra : possibleExtras) { + incognito = incognito | intent.getBooleanExtra(extra, false); + } + + return incognito; + } + + + /** + * Cleans the intent before applying incognito + */ + private void removeIncognito(Intent intent) { + // Mimics the matching in isIncognito + for (String extra : possibleExtras) { + // remove all incognito extras + intent.removeExtra(extra); + } + } + + /** * applies the setting to a given intent */ - public void apply(Intent intent) { - // for Firefox - intent.putExtra("private_browsing_mode", state); + public UrlHelper.compatibility apply(Context context, Intent intent) { + removeIncognito(intent); + if (state) { + for (var entry : findPackage.entrySet()) { + if (entry.getValue().apply(context, intent)) { + if (transform.get(entry.getKey()).apply(intent)) { + return urlNeedsHelp; + } + return compatible; + } + } + + } + return notCompatible; } } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/AutoClipboard.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/AutoClipboard.java new file mode 100644 index 00000000..07424901 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/AutoClipboard.java @@ -0,0 +1,43 @@ +package com.trianguloy.urlchecker.modules.companions.openUrlHelpers; + +import android.content.Context; +import android.os.Looper; + +import com.trianguloy.urlchecker.utilities.methods.JavaUtils; + +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +// TODO: Only works reliably on Android 9 or less, with bubbles or notifications it +// might work on other versions too + +public class AutoClipboard implements JavaUtils.BiConsumer { + // Only one clipboard, only one thread + static ScheduledThreadPoolExecutor executor = null; + static ScheduledFuture task = null; + + /** + * Copies URL into clipboard and launches a thread that will restore the clipboard some time + * later. If called while another thread is waiting to restore the clipboard, that one will + * be cancelled an a new one launched. + */ + @Override + public void accept(Context context, String url) { + if (executor == null) { + executor = new ScheduledThreadPoolExecutor(1); + } + if (task != null) { + task.cancel(false); + } + ClipboardBorrower borrower = ClipboardBorrower.getClipboardBorrower(); + borrower.borrowClipboard(context, url); + task = executor.schedule(() -> { + Looper.prepare(); + borrower.releaseClipboard(context); + executor = null; + task = null; + Looper.loop(); + }, UrlHelper.timerSeconds, TimeUnit.SECONDS); + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java new file mode 100644 index 00000000..215b1ce3 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java @@ -0,0 +1,125 @@ +package com.trianguloy.urlchecker.modules.companions.openUrlHelpers; + +import android.content.ClipData; +import android.content.Context; +import android.widget.Toast; + +import com.trianguloy.urlchecker.BuildConfig; +import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.utilities.methods.AndroidUtils; + +public class ClipboardBorrower { + // There is only one clipboard, so there is only one borrower + private static ClipboardBorrower instance = null; + + private ClipboardBorrower() { + } + + public static ClipboardBorrower getClipboardBorrower() { + if (instance == null) { + instance = new ClipboardBorrower(); + } + return instance; + } + + // Class + private ClipData previous; + private ClipData borrowerData; + private boolean init = false; + + // XXX: For debugging purposes: emulator shared clipboard may trip this up, as it is constantly + // syncing and messes up the label. + // It is recommended to disable it. + // - Android studio: File -> Settings -> Tools -> Emulator -> Enable clipboard sharing + // + // Still it didn't work for me and it kept changing the label + private static final String label = "clipboard_borrower_text"; + + // TODO: Bubbles and maybe notifications allows the app to read the clipboard even in background, + // some checks for android versions or the possibility to force compatibility with this would be + // great, don't know yet if this is a good place to put these things + + /** + * Puts content into the clipboard for the user to use. Can be called multiple times before + * releaseClipboard. + * + * @param context + * @param text The text which we will put into the clipboard + */ + public boolean borrowClipboard(Context context, String text) { + return borrowClipboard(context, ClipData.newPlainText(label, text)); + } + + /** + * Puts content into the clipboard for the user to use. Can be called multiple times before + * releaseClipboard. + * + * @param context + * @param borrowerData The content which we will put into the clipboard + */ + private boolean borrowClipboard(Context context, ClipData borrowerData) { + if (storeClipboard(context)) { + this.borrowerData = borrowerData; + AndroidUtils.setPrimaryClip(context, R.string.borrow_borrowSet, this.borrowerData); + return true; + } else { + Toast.makeText(context, R.string.borrow_borrowError, Toast.LENGTH_LONG).show(); + return false; + } + } + + /** + * Restores the content of the clipboard. If the user has copied something to the clipboard + * between the first call to borrowClipboard and this, we will restore that content instead. + * If the user has copied multiple things in between calls we will restore the last content + * the user copied. + */ + public boolean releaseClipboard(Context context) { + // XXX: As a side effect, this makes technically possible to call release without borrowing, + // as the method also initializes things, in practise it doesn't make sense and it achieves + // nothing + boolean success = storeClipboard(context); + if (success) { + AndroidUtils.setPrimaryClip(context, R.string.borrow_releaseSet, previous); + } else { + Toast.makeText(context, R.string.borrow_releaseError, Toast.LENGTH_LONG).show(); + // TODO: warn user in description that if they see this message too much (always) they + // should not use this borrower setting as it doesn't work in their device + } + instance = null; // Kill the borrower + return success; + } + + /** + * To keep the last thing the user copied to the clipboard + * + * @return if the clipboard is accessible + */ + private boolean storeClipboard(Context context) { + // Retrieve clip data + ClipData current = AndroidUtils.getPrimaryClip(context, R.string.borrow_storeGet); + + // Clipboard not accessible + if (current == null) { + return false; + } + + // If it is the first time this method is called in this lifecycle + if (!init) { + // Store clip data + previous = current; + init = true; + } else { + final int index = 0; + if (current.getDescription().getMimeType(index).equals("text/plain")) { + var currentText = current.getItemAt(index).coerceToText(context); + var borrowerText = this.borrowerData.getItemAt(index).coerceToText(context); + if (!currentText.equals(borrowerText)) { + previous = current; + } + } + } + + return true; + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelper.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelper.java new file mode 100644 index 00000000..8fda045b --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelper.java @@ -0,0 +1,41 @@ +package com.trianguloy.urlchecker.modules.companions.openUrlHelpers; + +import android.content.Context; + +import com.trianguloy.urlchecker.utilities.Enums; +import com.trianguloy.urlchecker.utilities.methods.JavaUtils; + + +public enum UrlHelper implements Enums.IdEnum{ + // 0 reserved for auto + autoClipboard(1, new AutoClipboard()), + manualClipboard(2, null), + semiAutoClipboard(3,null); + // open another app? + // open another app in incognito? + + public static final int timerSeconds = 10; + private int id; + private JavaUtils.BiConsumer helper; + + UrlHelper(int id, JavaUtils.BiConsumer helper) { + this.id = id; + this.helper = helper; + } + + @Override + public int getId() { + return id; + } + + public JavaUtils.BiConsumer getHelper(){ + return helper; + } + + public enum compatibility { + notCompatible, + urlNeedsHelp, + compatible + + } +} 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 600d1d0e..523d6c89 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 @@ -19,6 +19,7 @@ import com.trianguloy.urlchecker.modules.companions.Flags; import com.trianguloy.urlchecker.modules.companions.Incognito; import com.trianguloy.urlchecker.modules.companions.LastOpened; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelper; import com.trianguloy.urlchecker.url.UrlData; import com.trianguloy.urlchecker.utilities.generics.GenericPref; import com.trianguloy.urlchecker.utilities.methods.AndroidUtils; @@ -239,7 +240,7 @@ private void openUrl(int index) { cTabs.apply(intent); // incognito - incognito.apply(intent); + var incogCompat = incognito.apply(getActivity(), intent); // Get flags from global data (probably set by flags module, if active) var flags = Flags.getGlobalFlagsNullable(this); @@ -253,6 +254,11 @@ private void openUrl(int index) { // open PackageUtils.startActivity(intent, R.string.toast_noApp, getActivity()); + if (incogCompat == UrlHelper.compatibility.urlNeedsHelp){ + var helper = UrlHelper.autoClipboard.getHelper(); + helper.accept(getActivity(), getUrl()); + } + // finish activity if (closeOpenPref.get()) { this.getActivity().finish(); diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java index a0b889cd..e9dacdd5 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java @@ -5,6 +5,9 @@ import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; @@ -21,6 +24,7 @@ import com.trianguloy.urlchecker.BuildConfig; import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.utilities.generics.GenericPref; import java.text.DateFormat; import java.util.Date; @@ -146,6 +150,54 @@ static void copyToClipboard(Activity activity, String toast, String text) { Toast.makeText(activity, toast, Toast.LENGTH_LONG).show(); } + /** + * Get primary clip from clipboard, retrieves string from id + */ + static ClipData getPrimaryClip(Context context, int id) { + return getPrimaryClip(context, context.getString(id)); + } + + /** + * Get primary clip from clipboard + */ + static ClipData getPrimaryClip(Context context, String toast) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard == null) return null; + + // NOTE: according to https://stackoverflow.com/a/38965870 + // if the clipboard is empty, it will return null, however there is no mention of this in + // the documentation https://developer.android.com/reference/android/content/ClipboardManager#getPrimaryClip(). + // If there is a way to know it is empty for sure we should instead return ClipData.newPlainText("", "") + // or similar. We want to keep null for cases in which we cannot access the clipboard + ClipData res = clipboard.getPrimaryClip(); + + // show toast to notify it was read (except on Android 13+, where the device shows a popup itself) + if (Build.VERSION.SDK_INT < /*Build.VERSION_CODES.TIRAMISU*/33) + Toast.makeText(context, toast, Toast.LENGTH_LONG).show(); + return res; + } + + /** + * Set primary clip from clipboard, retrieves string from id + */ + static void setPrimaryClip(Context context, int id, ClipData clipData) { + setPrimaryClip(context, context.getString(id), clipData); + } + + /** + * Set primary clip from clipboard + */ + static void setPrimaryClip(Context context, String toast, ClipData clipData) { + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard == null) return; + + clipboard.setPrimaryClip(clipData); + + // show toast to notify it was read (except on Android 13+, where the device shows a popup itself) + if (Build.VERSION.SDK_INT < /*Build.VERSION_CODES.TIRAMISU*/33) + Toast.makeText(context, toast, Toast.LENGTH_LONG).show(); + } + /** * Get the (possible) referrer activity from an existing one. * Null if can't find @@ -191,7 +243,8 @@ static void toggleableListener(V view, JavaUtils.Consumer to static void longTapForDescription(View view) { view.setOnLongClickListener(v -> { var contentDescription = v.getContentDescription(); - if (contentDescription == null) AndroidUtils.assertError("No content description for view " + view); + if (contentDescription == null) + AndroidUtils.assertError("No content description for view " + view); Toast.makeText(v.getContext(), contentDescription, Toast.LENGTH_SHORT).show(); return true; }); @@ -230,4 +283,32 @@ static Set getLinksFromText(CharSequence text) { while (matcher.find()) links.add(matcher.group()); return links; } + + /** + * Returns the main activity of a package + */ + static String getMainActivity(Context cntx, String pckg) { + return cntx.getPackageManager() + .getLaunchIntentForPackage(pckg) + .getComponent().getClassName(); + } + + /** + * Returns a Set with all the activities of a package + */ + static Set getActivities(Context cntx, String pckg) { + Set activities = new HashSet<>(); + try { + ActivityInfo[] activityInfos = (cntx.getPackageManager() + .getPackageInfo(pckg, PackageManager.GET_ACTIVITIES).activities); + + for (var act : activityInfos) { + activities.add(act.name); + } + } catch (PackageManager.NameNotFoundException e) { + return null; + } + return activities; + } + } 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 06d74e2f..3bb07939 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 @@ -122,6 +122,14 @@ interface Consumer { void accept(T t); } + /** + * java.util.function.Consumer requires api 24 + */ + @FunctionalInterface + interface BiConsumer { + void accept(T t, U u); + } + /** * java.util.function.Function requires api 24 */ @@ -130,6 +138,14 @@ interface Function { R apply(T t); } + /** + * java.util.function.BiFunction requires api 24 + */ + @FunctionalInterface + interface BiFunction{ + R apply(T t, U u); + } + /** * java.util.function.UnaryOperator requires api 24 */ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a71d64cc..6c6186df 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -108,6 +108,14 @@ Hope you find the app useful! And don't hesitate to suggest features, report bug --> [Beta feature] This is an advanced editor, the content must be formatted into valid JSON. You can press the top-right button to format and validate it. Advanced editor + + Clipboard borrowed + "Clipboard not available can't borrow" + Clipboard released + "Clipboard not available can't restore" + "Clipboard content stored" From 5af7c73c8b398272ee3dca2379a6a439af36f4e2 Mon Sep 17 00:00:00 2001 From: Pablo Ortigosa <55493443+PabloOQ@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:04:36 +0000 Subject: [PATCH 02/19] Added incognito support for all android versions (via clipboard borrower) --- app/src/main/AndroidManifest.xml | 1 + .../activities/ShortcutsActivity.java | 1 + .../modules/companions/Incognito.java | 11 +- .../openUrlHelpers/AutoClipboard.java | 43 ----- .../openUrlHelpers/ClipboardBorrower.java | 170 ++++++++++++------ .../openUrlHelpers/HelperManager.java | 52 ++++++ .../companions/openUrlHelpers/UrlHelper.java | 39 +--- .../helpers/AutoBackground.java | 77 ++++++++ .../openUrlHelpers/helpers/ManualBubble.java | 62 +++++++ .../helpers/SemiautoBubble.java | 93 ++++++++++ .../urlchecker/modules/list/OpenModule.java | 22 ++- .../utilities/methods/AndroidUtils.java | 83 +++++++-- .../urlchecker/utilities/wrappers/Bubble.java | 108 +++++++++++ app/src/main/res/layout/bubble.xml | 19 ++ 14 files changed, 628 insertions(+), 153 deletions(-) delete mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/AutoClipboard.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/HelperManager.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/AutoBackground.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/ManualBubble.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/SemiautoBubble.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/utilities/wrappers/Bubble.java create mode 100644 app/src/main/res/layout/bubble.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c88153c3..bee627b4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android"> + Status code By pressing the check button, a network request will be made to retrieve and display the site status code. If the result is a redirection, you will be able to view the destination URL. The URL content is obtained (GET), but not evaluated, so redirections based on JavaScript won\'t be detected. From 6e31585d720e5600d3971f4b67cbf3eb9203b6eb Mon Sep 17 00:00:00 2001 From: Pablo Ortigosa <55493443+PabloOQ@users.noreply.github.com> Date: Wed, 29 May 2024 17:24:03 +0100 Subject: [PATCH 05/19] Add menu settings --- .../modules/companions/Incognito.java | 18 ++- .../openUrlHelpers/ClipboardBorrower.java | 21 ++- .../openUrlHelpers/HelperManager.java | 56 ------- .../openUrlHelpers/UrlHelperCompanion.java | 139 ++++++++++++++++++ .../helpers/AutoBackground.java | 4 +- .../openUrlHelpers/helpers/NoneHelper.java | 13 ++ .../helpers/SemiautoBubble.java | 5 +- .../urlchecker/modules/list/OpenModule.java | 38 +++-- .../utilities/methods/AndroidUtils.java | 25 +++- app/src/main/res/layout/config_open.xml | 8 + app/src/main/res/layout/config_urlhelper.xml | 82 +++++++++++ app/src/main/res/values/strings.xml | 37 ++++- 12 files changed, 358 insertions(+), 88 deletions(-) delete mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/HelperManager.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelperCompanion.java create mode 100644 app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/NoneHelper.java create mode 100644 app/src/main/res/layout/config_urlhelper.xml diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java index 49e16cd1..b8a44b7e 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java @@ -1,8 +1,8 @@ package com.trianguloy.urlchecker.modules.companions; -import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager.Compatibility.compatible; -import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager.Compatibility.notCompatible; -import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager.Compatibility.urlNeedsHelp; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion.Compatibility.compatible; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion.Compatibility.notCompatible; +import static com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion.Compatibility.urlNeedsHelp; import android.content.ComponentName; import android.content.Context; @@ -11,7 +11,7 @@ import android.widget.ImageButton; import com.trianguloy.urlchecker.R; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion; import com.trianguloy.urlchecker.utilities.generics.GenericPref; import com.trianguloy.urlchecker.utilities.methods.AndroidUtils; import com.trianguloy.urlchecker.utilities.methods.JavaUtils; @@ -164,7 +164,7 @@ public void initFrom(Intent intent, ImageButton button) { /** * Returns if an intent will launch incognito for a given intent/app, only checks extras */ - private boolean isIncognito(Intent intent) { + public static boolean isIncognito(Intent intent) { boolean incognito = false; // Find any extra for (String extra : possibleExtras) { @@ -186,11 +186,15 @@ private void removeIncognito(Intent intent) { } } + public UrlHelperCompanion.Compatibility willNeedHelp(Context context, Intent intent){ + Intent simulation = new Intent(intent); + return apply(context, simulation); + } /** * Applies the setting to a given intent */ - public HelperManager.Compatibility apply(Context context, Intent intent) { + public UrlHelperCompanion.Compatibility apply(Context context, Intent intent) { // FIXME: ctabs compatibility removeIncognito(intent); if (state) { @@ -198,7 +202,7 @@ public HelperManager.Compatibility apply(Context context, Intent intent) { if (entry.getValue().apply(context, intent)) { // Package can be opened in incognito - // Apply transformation, the function also tells us if we will need help to input + // Apply transformation, the function also tells us if we will need help // to input the URL return transform.get(entry.getKey()).apply(intent) ? urlNeedsHelp : compatible; diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java index 03fd4d20..b4c6a4c1 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/ClipboardBorrower.java @@ -4,10 +4,12 @@ import android.content.Context; import android.graphics.PixelFormat; import android.os.Build; +import android.provider.Settings; import android.view.LayoutInflater; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.widget.Toast; import com.trianguloy.urlchecker.BuildConfig; import com.trianguloy.urlchecker.R; @@ -57,7 +59,7 @@ public synchronized static boolean borrow(Context context, ClipData borrowerData * clipboard. * * @param storeBeforeRestore Check first if the clipboard has changed, since the last call to - * {@link #release} or {@link #borrow}. Mainly used to avoid the toast. + * {@link #release} or {@link #borrow}. Mainly used to avoid the toast. */ public synchronized static boolean release(Context context, boolean storeBeforeRestore) { // Read clipboard and check if there has been any changes since we last borrowed it, store @@ -90,6 +92,10 @@ public synchronized static boolean release(Context context, boolean storeBeforeR * steal the scrolling from the user. */ public static void releaseFromBubble(Context context) { + if (!Settings.canDrawOverlays(context)) { + AndroidUtils.safeToast(context, R.string.borrow_drawError, Toast.LENGTH_LONG); + return; + } var windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); var floatView = LayoutInflater.from(context).inflate(R.layout.bubble, null); var layoutParams = new LayoutParams( @@ -122,8 +128,7 @@ public boolean onPreDraw() { } public synchronized static boolean releaseSmart(Context context) { - // TODO: read pref from disk - var storeBeforeRelease = true; + var storeBeforeRelease = UrlHelperCompanion.STOREBEFORERELEASE_PREF(context).get(); return releaseSmart(context, storeBeforeRelease); } @@ -138,7 +143,15 @@ public synchronized static boolean releaseSmart(Context context, boolean storeBe if (releaseNormally) { release(context, storeBeforeRelease); } else { - releaseFromBubble(context); + if (Settings.canDrawOverlays(context)) { + releaseFromBubble(context); + } else { + // No permissions, we can't read the clipboard, so we are unable to respect changes + // to the clipboard + // TODO: recommend user give the app drawing permissions? + // Last resort, ignore user preferences + release(context, false); + } } return releaseNormally; } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/HelperManager.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/HelperManager.java deleted file mode 100644 index 6c7e763b..00000000 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/HelperManager.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.trianguloy.urlchecker.modules.companions.openUrlHelpers; - -import android.content.Context; - -import com.trianguloy.urlchecker.R; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.ManualBubble; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.AutoBackground; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.SemiautoBubble; -import com.trianguloy.urlchecker.utilities.Enums; -import com.trianguloy.urlchecker.utilities.methods.JavaUtils; - -public class HelperManager { - public static final int timerSeconds = 10; - - // TODO: add string resource - public enum Helper implements Enums.IdEnum, Enums.StringEnum { - autoBackground(1, R.string.auto, new AutoBackground()), - manualBubble(2, R.string.auto, new ManualBubble()), - semiAutoBubble(3, R.string.auto, new SemiautoBubble()); - - // ----- - - private final int id; - private final int stringResource; - private final JavaUtils.BiConsumer function; - - Helper(int id, int stringResource, JavaUtils.BiConsumer function) { - this.id = id; - this.stringResource = stringResource; - this.function = function; - } - - @Override - public int getId() { - return id; - } - - @Override - public int getStringResource() { - return stringResource; - } - - public JavaUtils.BiConsumer getFunction() { - return function; - } - - - } - - public enum Compatibility { - notCompatible, - urlNeedsHelp, - compatible - - } -} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelperCompanion.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelperCompanion.java new file mode 100644 index 00000000..28a865c2 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/UrlHelperCompanion.java @@ -0,0 +1,139 @@ +package com.trianguloy.urlchecker.modules.companions.openUrlHelpers; + +import static com.trianguloy.urlchecker.utilities.methods.PackageUtils.startActivity; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import android.view.MotionEvent; +import android.view.View; +import android.view.WindowManager; +import android.widget.Button; + +import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.AutoBackground; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.ManualBubble; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.NoneHelper; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers.SemiautoBubble; +import com.trianguloy.urlchecker.utilities.Enums; +import com.trianguloy.urlchecker.utilities.generics.GenericPref; +import com.trianguloy.urlchecker.utilities.methods.JavaUtils; + +public class UrlHelperCompanion { + public static GenericPref.Enumeration CURRENT_PREF(Context cntx) { + return new GenericPref.Enumeration<>("urlHelper_urlHelper", + Helper.none, + Helper.class, + cntx); + } + + public static GenericPref.Bool STOREBEFORERELEASE_PREF(Context cntx) { + return new GenericPref.Bool("urlHelper_storeBeforeRelease", + true, + cntx); + } + + public static GenericPref.Int TIMER_PREF(Context cntx) { + return new GenericPref.Int("urlHelper_timer", + 10, + cntx); + } + + // --- + public static void showSettings(Activity cntx) { + new UrlHelperConfig(cntx).showSettings(); + } + + /* ------------------- enums ------------------- */ + public enum Helper implements Enums.IdEnum, Enums.StringEnum { + none(0, R.string.cHelper_helperNone, new NoneHelper()), + autoBackground(1, R.string.cHelper_helperAuto, new AutoBackground()), + manualBubble(2, R.string.cHelper_helperManual, new ManualBubble()), + semiAutoBubble(3, R.string.cHelper_helperSemiauto, new SemiautoBubble()); + + // ----- + + private final int id; + private final int stringResource; + private final JavaUtils.BiConsumer function; + + Helper(int id, int stringResource, JavaUtils.BiConsumer function) { + this.id = id; + this.stringResource = stringResource; + this.function = function; + } + + @Override + public int getId() { + return id; + } + + @Override + public int getStringResource() { + return stringResource; + } + + public JavaUtils.BiConsumer getFunction() { + return function; + } + + + } + + public enum Compatibility { + notCompatible, + urlNeedsHelp, + compatible + + } +} + +class UrlHelperConfig { + + /* ------------------- prefs ------------------- */ + private final GenericPref.Enumeration current; + private final GenericPref.Bool storeBeforeRestore; + private final GenericPref.Int timer; + + /* ------------------- constructor ------------------- */ + + private final Activity cntx; + + public UrlHelperConfig(Activity cntx) { + this.cntx = cntx; + + current = UrlHelperCompanion.CURRENT_PREF(cntx); + storeBeforeRestore = UrlHelperCompanion.STOREBEFORERELEASE_PREF(cntx); + timer = UrlHelperCompanion.TIMER_PREF(cntx); + } + + /** + * Show the settings dialog + */ + public void showSettings() { + // prepare dialog content + View views = cntx.getLayoutInflater().inflate(R.layout.config_urlhelper, null); + + // configure + current.attachToSpinner(views.findViewById(R.id.helperType_pref), null); + storeBeforeRestore.attachToSwitch(views.findViewById(R.id.storeBeforeRelease_pref)); + timer.attachToEditText(views.findViewById(R.id.seconds_pref), 0); + views.findViewById(R.id.draw_permissions).setOnClickListener(view -> { + Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + cntx.getPackageName())); + startActivity(intent, 1, cntx); + }); + + // prepare dialog + AlertDialog dialog = new AlertDialog.Builder(cntx) + .setView(views) + .setTitle(R.string.cHelper_title) // TODO + .show(); + + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } +} + diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/AutoBackground.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/AutoBackground.java index 210ada0a..b60bceb9 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/AutoBackground.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/AutoBackground.java @@ -3,7 +3,7 @@ import android.content.Context; import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.ClipboardBorrower; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion; import com.trianguloy.urlchecker.utilities.methods.JavaUtils; import java.util.concurrent.ScheduledFuture; @@ -43,7 +43,7 @@ public void accept(Context context, String url) { task = null; } } - }, HelperManager.timerSeconds, TimeUnit.SECONDS); + }, UrlHelperCompanion.TIMER_PREF(context).get(), TimeUnit.SECONDS); } } } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/NoneHelper.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/NoneHelper.java new file mode 100644 index 00000000..5c96a4e1 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/NoneHelper.java @@ -0,0 +1,13 @@ +package com.trianguloy.urlchecker.modules.companions.openUrlHelpers.helpers; + +import android.content.Context; + +import com.trianguloy.urlchecker.utilities.methods.JavaUtils; + +public class NoneHelper implements JavaUtils.BiConsumer { + + @Override + public void accept(Context context, String s) { + // Nothing + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/SemiautoBubble.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/SemiautoBubble.java index 8e3d1dce..de2ca719 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/SemiautoBubble.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/openUrlHelpers/helpers/SemiautoBubble.java @@ -3,7 +3,7 @@ import android.content.Context; import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.ClipboardBorrower; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion; import com.trianguloy.urlchecker.utilities.methods.JavaUtils; import com.trianguloy.urlchecker.utilities.wrappers.Bubble; @@ -47,6 +47,7 @@ public void accept(Context context, String url) { // FIXME: breakpoint here, run app, open in incognito, tap bubble, wait for "URLCheck isn't // responding", continue execution, can't access clipboard // just after getPrimaryClip() on getPrimaryClip() when URLCheck isn't responding +// Probably can't be fixed, it just loses clipboard access. ClipboardBorrower.releaseSmart(bubbleContext); task.cancel(true); executor = null; @@ -62,7 +63,7 @@ public void accept(Context context, String url) { bubble.pop(); } } - }, HelperManager.timerSeconds, TimeUnit.SECONDS); + }, UrlHelperCompanion.TIMER_PREF(context).get(), TimeUnit.SECONDS); } } } 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 3289d931..7634ff13 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 @@ -19,10 +19,12 @@ import com.trianguloy.urlchecker.modules.companions.Flags; import com.trianguloy.urlchecker.modules.companions.Incognito; import com.trianguloy.urlchecker.modules.companions.LastOpened; -import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.HelperManager; +import com.trianguloy.urlchecker.modules.companions.OnOffConfig; +import com.trianguloy.urlchecker.modules.companions.openUrlHelpers.UrlHelperCompanion; 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.RejectionDetector; @@ -240,7 +242,16 @@ private void openUrl(int index) { cTabs.apply(intent); // incognito - var incogCompat = incognito.apply(getActivity(), intent); + var currentUrlHelper = UrlHelperCompanion.CURRENT_PREF(getActivity()).get(); + var intentClone = new Intent(intent); + var incogCompat = incognito.apply(getActivity(), intentClone); + // If helper is none, and url needs help, do not apply incognito + if (currentUrlHelper == UrlHelperCompanion.Helper.none && + incogCompat == UrlHelperCompanion.Compatibility.urlNeedsHelp) { + incogCompat = UrlHelperCompanion.Compatibility.notCompatible; + } else { + intent = intentClone; + } // Get flags from global data (probably set by flags module, if active) var flags = Flags.getGlobalFlagsNullable(this); @@ -252,20 +263,22 @@ private void openUrl(int index) { rejectionDetector.markAsOpen(getUrl(), chosen); // If the URL needs help, get a helper - var urlHelper = incogCompat == HelperManager.Compatibility.urlNeedsHelp ? - HelperManager.Helper.semiAutoBubble.getFunction() : + var urlHelper = incogCompat == UrlHelperCompanion.Compatibility.urlNeedsHelp ? + currentUrlHelper : null; - if (urlHelper != null) { + if (urlHelper == UrlHelperCompanion.Helper.autoBackground || + urlHelper == UrlHelperCompanion.Helper.manualBubble || + urlHelper == UrlHelperCompanion.Helper.semiAutoBubble) { // For helpers that borrow the clipboard, we should borrow before opening the app, // to ensure we are still in foreground - urlHelper.accept(getActivity(), getUrl()); + urlHelper.getFunction().accept(getActivity(), getUrl()); } // open PackageUtils.startActivity(intent, R.string.toast_noApp, getActivity()); - // We borrow the clipboard before launching the activity, if the activity does not start, - // it doesn't make sense that we borrowed the clipboard + // FIXME: We borrow the clipboard before launching the activity, if the activity does not + // start, it doesn't make sense that we borrowed the clipboard // finish activity if (closeOpenPref.get()) { @@ -333,7 +346,14 @@ public void onInitialize(View views) { } else { views.findViewById(R.id.ctabs_parent).setVisibility(View.GONE); } - Incognito.PREF(getActivity()).attachToSpinner(views.findViewById(R.id.incognito_pref), null); + var incognitoButton = (Button) views.findViewById(R.id.urlHelper_settings); + incognitoButton.setOnClickListener(v -> UrlHelperCompanion.showSettings(getActivity())); + JavaUtils.Consumer buttonEnabled = onOffConfig -> { + incognitoButton.setEnabled(OnOffConfig.ALWAYS_OFF != onOffConfig); + }; + buttonEnabled.accept(Incognito.PREF(getActivity()).get()); + + Incognito.PREF(getActivity()).attachToSpinner(views.findViewById(R.id.incognito_pref), buttonEnabled); OpenModule.CLOSEOPEN_PREF(getActivity()).attachToSwitch(views.findViewById(R.id.closeopen_pref)); OpenModule.CLOSESHARE_PREF(getActivity()).attachToSwitch(views.findViewById(R.id.closeshare_pref)); OpenModule.CLOSECOPY_PREF(getActivity()).attachToSwitch(views.findViewById(R.id.closecopy_pref)); diff --git a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java index fa198596..efd7bb2b 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java +++ b/app/src/main/java/com/trianguloy/urlchecker/utilities/methods/AndroidUtils.java @@ -142,7 +142,7 @@ static void copyToClipboard(Activity activity, int id, String text) { static void copyToClipboard(Activity activity, String toast, String text) { ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard == null) { - Toast.makeText(activity, "TODO: copyToClipboard failed", Toast.LENGTH_LONG).show(); + Toast.makeText(activity, R.string.clipboard_copyError, Toast.LENGTH_LONG).show(); return; } @@ -157,16 +157,27 @@ static void copyToClipboard(Activity activity, String toast, String text) { * Get primary clip from clipboard, retrieves string from id */ static ClipData getPrimaryClip(Context context, int id) { - return getPrimaryClip(context, context.getString(id)); + return getPrimaryClip(context, context.getString(id), context.getString(R.string.clipboard_getError)); + } + + /** + * Get primary clip from clipboard, retrieves string from id + */ + static ClipData getPrimaryClip(Context context, int id, int failId) { + return getPrimaryClip(context, context.getString(id), context.getString(failId)); } /** * Get primary clip from clipboard */ static ClipData getPrimaryClip(Context context, String toast) { + return getPrimaryClip(context, toast, context.getString(R.string.clipboard_getError)); + } + + static ClipData getPrimaryClip(Context context, String toast, String failToast) { ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard == null) { - safeToast(context, "TODO: getSystemService failed", Toast.LENGTH_LONG); + safeToast(context, R.string.clipboard_serviceError, Toast.LENGTH_LONG); return null; } @@ -182,7 +193,7 @@ static ClipData getPrimaryClip(Context context, String toast) { safeToast(context, toast, Toast.LENGTH_LONG); } else { // FIXME: does android show toast on failed read? - safeToast(context, "TODO: getPrimaryClip failed", Toast.LENGTH_LONG); + safeToast(context, failToast, Toast.LENGTH_LONG); } return res; } @@ -200,7 +211,7 @@ static void setPrimaryClip(Context context, int id, ClipData clipData) { static void setPrimaryClip(Context context, String toast, ClipData clipData) { ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); if (clipboard == null) { - safeToast(context, "TODO: setPrimaryClip failed", Toast.LENGTH_LONG); + safeToast(context, R.string.clipboard_setError, Toast.LENGTH_LONG); return; } @@ -217,7 +228,9 @@ static void setPrimaryClip(Context context, String toast, ClipData clipData) { */ static boolean canReadClip(Context context) { // FIXME: find a better way, this creates an unnecessary toast - return AndroidUtils.getPrimaryClip(context, "TODO: canUseClip - TRUE") != null; + return AndroidUtils.getPrimaryClip(context, + R.string.clipboard_canReadTrue, + R.string.clipboard_canReadFalse) != null; } /** diff --git a/app/src/main/res/layout/config_open.xml b/app/src/main/res/layout/config_open.xml index d4c4323c..fca2a564 100644 --- a/app/src/main/res/layout/config_open.xml +++ b/app/src/main/res/layout/config_open.xml @@ -50,6 +50,14 @@ +