diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml index 9067675..5a1a6f2 100644 --- a/.github/workflows/codacy.yml +++ b/.github/workflows/codacy.yml @@ -39,7 +39,7 @@ jobs: # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis - name: Run Codacy Analysis CLI - uses: codacy/codacy-analysis-cli-action@master + uses: codacy/codacy-analysis-cli-action@v4 timeout-minutes: 5 with: # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository diff --git a/.github/workflows/comment-download-link.yml b/.github/workflows/comment-download-link.yml new file mode 100644 index 0000000..34726a6 --- /dev/null +++ b/.github/workflows/comment-download-link.yml @@ -0,0 +1,57 @@ +# This action will send a comment to the pr for download the built apk. +# Needs to be as a separate action, artifacts are not available on the action used to upload them +name: Comment Artifact URL on PR + +on: + workflow_run: + workflows: + - "Validate gradle build test" + types: + - "completed" + +permissions: + actions: read # need to read the artifacts url + pull-requests: write # need to write the comment + +jobs: + comment-on-pr: + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + steps: + + - name: Post url to artifact + env: + GITHUB_TOKEN: ${{ github.token }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + CURRENT_JOB_ID: ${{ github.run_id }} + PREVIOUS_JOB_ID: ${{ github.event.workflow_run.id }} + PREVIOUS_SUITE_ID: ${{ github.event.workflow_run.check_suite_id }} + run: | + + # get artifacts + read PR_NUMBER ARTIFACT_ID EXPIRES_AT <<< $(gh api "/repos/$OWNER/$REPO/actions/runs/$PREVIOUS_JOB_ID/artifacts" --jq '"\(.artifacts[0].name) \(.artifacts[1].id) \(.artifacts[1].expires_at)"') + if [ "$ARTIFACT_ID" == "null" ]; then + echo "No artifacts, (probably) not a PR, exiting" + exit + fi + echo "PR NUMBER: $PR_NUMBER" + echo "ARTIFACT ID: $ARTIFACT_ID" + echo "EXPIRES_AT: $EXPIRES_AT" + + # post link comment + gh api "/repos/$OWNER/$REPO/issues/$PR_NUMBER/comments" -F body=@- < + + This is an automatic comment created by a [Github Action](https://github.com/$OWNER/$REPO/actions/runs/$CURRENT_JOB_ID) + EOF \ No newline at end of file diff --git a/.github/workflows/validate-gradle-build-test.yml b/.github/workflows/validate-gradle-build-test.yml index 7c87c60..82b72bb 100644 --- a/.github/workflows/validate-gradle-build-test.yml +++ b/.github/workflows/validate-gradle-build-test.yml @@ -1,4 +1,5 @@ # This actions validates the gradle files and runs a build test to ensure the app is not corrupted +# if succeeded and the source is a pull request, builds an evaluation apk name: Validate gradle build test on: @@ -9,6 +10,11 @@ on: branches: - master +# parameters +env: + VARIANT: evaluation + NAME: URLCheck_evaluation.apk + jobs: build: runs-on: ubuntu-latest @@ -35,3 +41,28 @@ jobs: arguments: > build test + + # the following steps will only run for PRs + + - name: Generate apk + if: ${{ github.event_name == 'pull_request' }} + uses: gradle/gradle-build-action@v2 + with: + arguments: > + assemble${{ env.VARIANT }} + + - name: Upload apk as artifact + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v3 + with: + path: ./app/build/outputs/apk/${{ env.VARIANT }}/app-${{ env.VARIANT }}.apk + name: ${{ env.NAME }} + retention-days: 14 + + - name: Upload PR id as artifact + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v3 + with: + path: /dev/null + name: ${{ github.event.number }} + retention-days: 1 diff --git a/README.md b/README.md index 628e9e6..b3e9e7d 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,46 @@ -
+ + # URLCheck -### Android app made by [TrianguloY](https://github.com/TrianguloY) - -With contributions from: [Ilithy](https://github.com/Ilithy), [PabloOQ](https://github.com/PabloOQ) \ -And translations by: [Tiago Carmo](https://github.com/ReduxFlakes), [Ilithy](https://github.com/Ilithy), Idris, [Metezd](https://github.com/metezd), Nhman Mazuz, [404potato](https://github.com/404potato), [dperruso](https://github.com/dperruso), [Seviche CC](https://github.com/Sevichecc) and more users on [Weblate](https://hosted.weblate.org/engage/urlcheck/) - -
-
-
- [![App-Code Size](https://img.shields.io/github/languages/code-size/trianguloy/urlchecker.svg)](https://api.github.com/repos/TrianguloY/UrlChecker) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/TrianguloY/UrlChecker)](https://github.com/TrianguloY/UrlChecker/pulse/monthly) -[![Weblate (translation percentage)](https://hosted.weblate.org/widgets/urlcheck/-/svg-badge.svg)](https://hosted.weblate.org/engage/urlcheck/) \ +[![Weblate (translation percentage)](https://hosted.weblate.org/widgets/urlcheck/-/svg-badge.svg)](https://hosted.weblate.org/engage/urlcheck/) [![GitHub version (gradle versionName)](https://img.shields.io/badge/dynamic/json?label=Latest%20version&color=white&query=version&url=https%3A%2F%2Fgithub.com%2FTrianguloY%2FUrlChecker%2Freleases%2Fdownload%2Flatest%2Fshields.json)](https://github.com/TrianguloY/UrlChecker/blob/master/app/build.gradle) [![F-Droid](https://img.shields.io/f-droid/v/com.trianguloy.urlchecker?label=F-Droid%20version)](https://gitlab.com/fdroid/fdroiddata/-/blob/master/metadata/com.trianguloy.urlchecker.yml) [![PlayShields](https://img.shields.io/endpoint?color=green&url=https%3A%2F%2Fplay.cuzi.workers.dev%2Fplay%3Fi%3Dcom.trianguloy.urlchecker%26l%3DPlay%2520Store%2520version%26m%3Dv%24version)](https://play.google.com/store/apps/details?id=com.trianguloy.urlchecker) - -
- -
- [Get it on F-Droid](https://f-droid.org/packages/com.trianguloy.urlchecker) [Get it on Google Play](https://play.google.com/store/apps/details?id=com.trianguloy.urlchecker) -
- + +### Android app made by [TrianguloY](https://github.com/TrianguloY) + +Special contributors: [Ilithy](https://github.com/Ilithy), [PabloOQ](https://github.com/PabloOQ) + +All contributors: \ +[![Contributors](https://contrib.rocks/image?repo=TrianguloY/UrlChecker&anon=1)](https://github.com/TrianguloY/UrlChecker/graphs/contributors) \ +and [404potato](https://github.com/404potato) and maybe some more from [Weblate](https://hosted.weblate.org/engage/urlcheck/). + +
-
- **URLCheck** acts as an amazingly customizable and powerful intermediary when opening url links, allowing, among other things: to remove trackers, affiliate links, unnecessary elements, check Hosts, facilitating link holding and sharing, protecting against certain phishing techniques and many more... -
-
-
@@ -64,7 +52,7 @@ to remove trackers, affiliate links, unnecessary elements, check Hosts, facilita
-
+ diff --git a/app/build.gradle b/app/build.gradle index 45ea5cd..2986a87 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,17 +1,17 @@ plugins { id 'com.android.application' - id 'com.github.triplet.play' version '3.7.0' + id 'com.github.triplet.play' version '3.8.3' } android { namespace 'com.trianguloy.urlchecker' - compileSdkVersion 32 + compileSdkVersion 33 defaultConfig { applicationId "com.trianguloy.urlchecker" minSdkVersion 14 - targetSdkVersion 32 - versionCode 22 - versionName "2.10" + targetSdkVersion 33 + versionCode 23 + versionName "2.11" // generate buildconfig field with all locales @@ -52,6 +52,11 @@ android { applicationIdSuffix '.alpha' versionNameSuffix '-ALPHA' } + evaluation { + initWith(buildTypes.debug) + applicationIdSuffix '.test' + versionNameSuffix '-TEST' + } } lint { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8120a26..9f60fcd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,6 +29,7 @@ android:name=".dialogs.MainDialog" android:exported="true" android:noHistory="true" + android:windowSoftInputMode="adjustResize|stateHidden" android:theme="@android:style/Theme.DeviceDefault.Dialog"> @@ -82,6 +83,9 @@ + + + inflate(R.layout.about_link, v_links, this); + var v_link = Inflater.inflate(R.layout.about_link, v_links); v_link.setText(link.first); AndroidUtils.setAsClickable(v_link); v_link.setTag(link.second.replace("{package}", getPackageName())); diff --git a/app/src/main/java/com/trianguloy/urlchecker/activities/ModulesActivity.java b/app/src/main/java/com/trianguloy/urlchecker/activities/ModulesActivity.java index c0df663..d788155 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/activities/ModulesActivity.java +++ b/app/src/main/java/com/trianguloy/urlchecker/activities/ModulesActivity.java @@ -98,7 +98,7 @@ public class ModulesActivity extends Activity { final AModuleConfig config = module.getConfig(this); // inflate - View parent = Inflater.inflate(R.layout.config_module, list, this); + View parent = Inflater.inflate(R.layout.config_module, list); parent.setTag(module.getId()); Animations.enableAnimations(parent); @@ -142,7 +142,7 @@ public class ModulesActivity extends Activity { AndroidUtils.longTapForDescription(toggleDecorations); // configuration of the module - var child = Inflater.inflate(config.getLayoutId(), parent.findViewById(R.id.box), this); + var child = Inflater.inflate(config.getLayoutId(), parent.findViewById(R.id.box)); config.onInitialize(child); // configure toggleable description diff --git a/app/src/main/java/com/trianguloy/urlchecker/activities/ShortcutsActivity.java b/app/src/main/java/com/trianguloy/urlchecker/activities/ShortcutsActivity.java index ba5a23c..b8d45fb 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/activities/ShortcutsActivity.java +++ b/app/src/main/java/com/trianguloy/urlchecker/activities/ShortcutsActivity.java @@ -35,8 +35,9 @@ public class ShortcutsActivity extends Activity { if (Intent.ACTION_CREATE_SHORTCUT.equals(getIntent().getAction())) { // old android method setResult(RESULT_OK, new Intent() - .putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(this, getClass())) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(Intent.EXTRA_SHORTCUT_INTENT, new Intent(this, getClass()) + .setAction(Intent.ACTION_VIEW) + ) .putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.shortcut_checkClipboard)) .putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.mipmap.clipboard_launcher)) ); diff --git a/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java b/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java index 7911d0e..32dd33d 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java +++ b/app/src/main/java/com/trianguloy/urlchecker/dialogs/JsonEditor.java @@ -3,6 +3,7 @@ package com.trianguloy.urlchecker.dialogs; import android.app.Activity; import android.app.AlertDialog; import android.view.View; +import android.view.WindowManager; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; @@ -57,6 +58,8 @@ public class JsonEditor { .setCancelable(false) .show(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + // prepare more dialog // these are configured here to disable automatic auto-closing when they are pressed dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { diff --git a/app/src/main/java/com/trianguloy/urlchecker/dialogs/MainDialog.java b/app/src/main/java/com/trianguloy/urlchecker/dialogs/MainDialog.java index 5f04b2a..e49425d 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/dialogs/MainDialog.java +++ b/app/src/main/java/com/trianguloy/urlchecker/dialogs/MainDialog.java @@ -227,7 +227,7 @@ public class MainDialog extends Activity { // set module block if (ModuleManager.getDecorationsPrefOfModule(moduleData, this).get()) { // init decorations - View block = Inflater.inflate(R.layout.dialog_module, ll_mods, this); + View block = Inflater.inflate(R.layout.dialog_module, ll_mods); final TextView title = block.findViewById(R.id.title); title.setText(getString(R.string.dd, getString(moduleData.getName()))); parent = block.findViewById(R.id.mod); @@ -237,7 +237,7 @@ public class MainDialog extends Activity { } // set module content - child = Inflater.inflate(layoutId, parent, this); + child = Inflater.inflate(layoutId, parent); views.add(child); } @@ -261,7 +261,7 @@ public class MainDialog extends Activity { * Adds a separator component to the list of mods */ private View addSeparator() { - return Inflater.inflate(R.layout.separator, ll_mods, this); + return Inflater.inflate(R.layout.separator, ll_mods); } /** diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/ModuleManager.java b/app/src/main/java/com/trianguloy/urlchecker/modules/ModuleManager.java index f4d6609..b64a51c 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/ModuleManager.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/ModuleManager.java @@ -14,6 +14,7 @@ import com.trianguloy.urlchecker.modules.list.RemoveQueriesModule; import com.trianguloy.urlchecker.modules.list.StatusModule; import com.trianguloy.urlchecker.modules.list.TextInputModule; import com.trianguloy.urlchecker.modules.list.UnshortenModule; +import com.trianguloy.urlchecker.modules.list.UriPartsModule; import com.trianguloy.urlchecker.modules.list.VirusTotalModule; import com.trianguloy.urlchecker.utilities.GenericPref; @@ -41,6 +42,7 @@ public class ModuleManager { modules.add(new VirusTotalModule()); modules.add(new ClearUrlModule()); modules.add(new RemoveQueriesModule()); + modules.add(new UriPartsModule()); modules.add(new PatternModule()); modules.add(new HostsModule()); // new modules should preferably be added directly above this line diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/CTabs.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/CTabs.java index 25f3722..2e50e78 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/CTabs.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/CTabs.java @@ -1,11 +1,15 @@ package com.trianguloy.urlchecker.modules.companions; import android.content.Context; +import android.content.Intent; import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.widget.ImageButton; import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.utilities.AndroidUtils; import com.trianguloy.urlchecker.utilities.GenericPref; -import com.trianguloy.urlchecker.utilities.TranslatableEnum; /** * Static elements related to CTabs @@ -16,13 +20,13 @@ public class CTabs { /** * Ctabs extra intent */ - public static final String EXTRA = "android.support.customtabs.extra.SESSION"; + private static final String EXTRA = "android.support.customtabs.extra.SESSION"; /** * CTabs preference */ - public static GenericPref.Enumeration PREF(Context cntx) { - return new GenericPref.Enumeration<>("open_ctabs", Config.AUTO, Config.class, cntx); + public static GenericPref.Enumeration PREF(Context cntx) { + return new GenericPref.Enumeration<>("open_ctabs", OnOffConfig.AUTO, OnOffConfig.class, cntx); } /** @@ -32,35 +36,82 @@ public class CTabs { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; } + + /* ------------------- state ------------------- */ + + private final GenericPref.Enumeration pref; + private boolean state = false; + + public CTabs(Context cntx) { + pref = PREF(cntx); + } + /** - * CTabs configuration + * Initialization from a given intent and a button to toggle */ - public enum Config implements TranslatableEnum { - AUTO(0, R.string.auto), - ON(1, R.string.mOpen_ctabsOn), - OFF(2, R.string.mOpen_ctabsOff), - ENABLED(3, R.string.mOpen_ctabsEn), - DISABLED(4, R.string.mOpen_ctabsDis), - ; - - // ----- - - private final int id; - private final int stringResource; - - Config(int id, int stringResource) { - this.id = id; - this.stringResource = stringResource; + public void initFrom(Intent intent, ImageButton button) { + boolean visible; + if (CTabs.isAvailable()) { + // configure + switch (pref.get()) { + case AUTO: + default: + // If auto we get it from the intent + state = intent.hasExtra(CTabs.EXTRA); + visible = true; + break; + case DEFAULT_ON: + state = true; + visible = true; + break; + case DEFAULT_OFF: + state = false; + visible = true; + break; + case ALWAYS_ON: + state = true; + visible = false; + break; + case ALWAYS_OFF: + state = false; + visible = false; + break; + } + } else { + // not available, just ignore + visible = false; } - @Override - public int getId() { - return id; + // set + if (visible) { + // show + AndroidUtils.longTapForDescription(button); + AndroidUtils.toggleableListener(button, + o -> state = !state, + view -> view.setImageResource(state ? R.drawable.ctabs_on : R.drawable.ctabs_off) + ); + button.setVisibility(View.VISIBLE); + } else { + // hide + button.setVisibility(View.GONE); + } + } + + /** + * applies the setting to a given intent + */ + public void apply(Intent intent) { + if (!CTabs.isAvailable()) return; + if (state && !intent.hasExtra(CTabs.EXTRA)) { + // enable Custom tabs + Bundle extras = new Bundle(); + extras.putBinder(CTabs.EXTRA, null); // Set to null for no session + intent.putExtras(extras); } - @Override - public int getStringResource() { - return stringResource; + if (!state && intent.hasExtra(CTabs.EXTRA)) { + // disable Custom tabs + intent.removeExtra(CTabs.EXTRA); } } } diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java index 0a53bbe..8c02898 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/ClearUrlCatalog.java @@ -5,6 +5,7 @@ import android.app.AlertDialog; import android.util.Log; import android.util.Pair; import android.view.View; +import android.view.WindowManager; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; @@ -223,6 +224,8 @@ public class ClearUrlCatalog { .setCancelable(true) .show(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + // prepare more dialog // these are configured here to allow auto-closing the dialog when they are pressed Button updateNow = dialog.getButton(AlertDialog.BUTTON_POSITIVE); diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Flags.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Flags.java new file mode 100644 index 0000000..41c7780 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Flags.java @@ -0,0 +1,269 @@ +package com.trianguloy.urlchecker.modules.companions; + +import static com.trianguloy.urlchecker.utilities.JavaUtils.valueOrDefault; + +import android.app.Activity; +import android.content.Intent; + +import com.trianguloy.urlchecker.modules.AModuleDialog; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * Represents intent flags + * Stores all the flags and it states, can be retrieved as list or as int + * Non-compatible flags are ignored + */ +public class Flags { + + // ------------------- static ------------------- + + // https://github.com/MuntashirAkon/AppManager/blob/19782da4c8556c817ba5795554a1cc21f38af13a/app/src/main/java/io/github/muntashirakon/AppManager/intercept/ActivityInterceptor.java#L92 + private static final Set ALL_FLAGS = Set.of( + // all have a 'FLAG_' prefix + "GRANT_READ_URI_PERMISSION", + "GRANT_WRITE_URI_PERMISSION", + "FROM_BACKGROUND", + "DEBUG_LOG_RESOLUTION", + "EXCLUDE_STOPPED_PACKAGES", + "INCLUDE_STOPPED_PACKAGES", + "GRANT_PERSISTABLE_URI_PERMISSION", + "GRANT_PREFIX_URI_PERMISSION", + "DIRECT_BOOT_AUTO", + "IGNORE_EPHEMERAL", + "ACTIVITY_NO_HISTORY", + "ACTIVITY_SINGLE_TOP", + "ACTIVITY_NEW_TASK", + "ACTIVITY_MULTIPLE_TASK", + "ACTIVITY_CLEAR_TOP", + "ACTIVITY_FORWARD_RESULT", + "ACTIVITY_PREVIOUS_IS_TOP", + "ACTIVITY_EXCLUDE_FROM_RECENTS", + "ACTIVITY_BROUGHT_TO_FRONT", + "ACTIVITY_RESET_TASK_IF_NEEDED", + "ACTIVITY_LAUNCHED_FROM_HISTORY", + "ACTIVITY_NEW_DOCUMENT", + "ACTIVITY_NO_USER_ACTION", + "ACTIVITY_REORDER_TO_FRONT", + "ACTIVITY_NO_ANIMATION", + "ACTIVITY_CLEAR_TASK", + "ACTIVITY_TASK_ON_HOME", + "ACTIVITY_RETAIN_IN_RECENTS", + "ACTIVITY_LAUNCH_ADJACENT", + "ACTIVITY_MATCH_EXTERNAL", + "ACTIVITY_REQUIRE_NON_BROWSER", + "ACTIVITY_REQUIRE_DEFAULT" + ); + + private static final Map compatibleFlags = new TreeMap<>(); // TreeMap to have the entries sorted by key + + static { + // Only get flags that are present in the current Android version + for (var flag : ALL_FLAGS) { + try { + compatibleFlags.put(flag, (Integer) Intent.class.getField("FLAG_" + flag).get(null)); + } catch (NoSuchFieldException ignored) { + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + public static Map getCompatibleFlags() { + return new TreeMap<>(compatibleFlags); + } + + // ------------------- CRUD ------------------- + + private Set flags; + + // TODO store non-compatible flags? as String "0x001000", int 0x001000 + + public Flags() { + this(0x00000000); + } + + public Flags(int hex) { + setFlags(hex); + } + + public boolean isSet(String flag) { + return flags.contains(flag); + } + + public int getFlagsAsInt() { + return flagsSetToHex(flags); + } + + public Set getFlagsAsSet() { + return new TreeSet<>(flags); // TreeSet to have the entries sorted + } + + /** + * Replaces the stored flags with the received hex + */ + public void setFlags(int hex) { + this.flags = hexFlagsToSet(hex); + } + + /** + * Replaces the stored flags with the received list + */ + public void setFlags(Set names) { + Set res = new HashSet<>(names); + res.retainAll(compatibleFlags.keySet()); + this.flags = res; + } + + /** + * Sets the flag to the received boolean + */ + public boolean setFlag(String flag, boolean bool) { + if (compatibleFlags.containsKey(flag)) { + if (bool) { + return flags.add(flag); + } else { + return flags.remove(flag); + } + } else { + return false; + } + } + + /** + * Add flags by applying a mask + */ + public void addFlags(int hex) { + flags.addAll(hexFlagsToSet(hex)); + } + + /** + * Add a list of flags based on its name + */ + public void addFlags(Set flags) { + this.flags.addAll(flags); + } + + /** + * Add a flag based on its name + */ + public boolean addFlag(String name) { + if (compatibleFlags.containsKey(name)) { + return flags.add(name); + } else { + return false; + } + } + + /** + * Remove flags by applying a mask + */ + public boolean removeFlags(int hex) { + return flags.removeAll(hexFlagsToSet(hex)); + } + + /** + * Remove a list of flags based on its name + */ + public boolean removeFlags(Set flags) { + return this.flags.removeAll(flags); + } + + /** + * Remove a flag based on its name + */ + public boolean removeFlag(String name) { + return flags.remove(name); + } + + // ------------------- utils ------------------- + + /** + * Decode an int as set + */ + private static Set hexFlagsToSet(int hex) { + var foundFlags = new HashSet(); + for (var flag : compatibleFlags.entrySet()) { + // check if flag is present + if ((hex & flag.getValue()) != 0) { + foundFlags.add(flag.getKey()); + } + } + return foundFlags; + } + + /** + * Decode a set as hex + */ + private static int flagsSetToHex(Set set) { + int hex = 0x00000000; + for (var flag : set) { + if (compatibleFlags.containsKey(flag)) { + hex = hex | compatibleFlags.get(flag); + } + } + return hex; + } + + + // ------------------- store/load flags ------------------- + // this handles the store and load of the flags, if something wants to get the flags + // it should always use these methods. + + private static final String DATA_FLAGS = "flagsEditor.flags"; + private static final String REGEX = "(0x)?[a-fA-F\\d]{1,8}"; + private static final int BASE = 16; + + /** + * parses a text as an hexadecimal flags string. + * Returns null if invalid + */ + public static Integer toInteger(String text) { + if (text != null && text.matches(REGEX)) { + return Integer.parseInt(text.replaceAll("^0x", ""), BASE); + } else { + return null; + } + } + + /** + * Converts an int flags to string + */ + public static String toHexString(int flags) { + return "0x" + Integer.toString(flags, BASE); + } + + /** + * Retrieves the flags from GlobalData, if it is not defined it will return null + */ + public static Integer getGlobalFlagsNullable(AModuleDialog instance) { + return toInteger(instance.getData(DATA_FLAGS)); + } + + /** + * Loads the flags from GlobalData, if none were found it gets the flags from the intent that + * started this activity + */ + public static int getGlobalFlagsNonNull(AModuleDialog instance, Activity cntx) { + return getGlobalFlagsOrDefault(instance, cntx.getIntent().getFlags()); + } + + /** + * Loads the flags from GlobalData, if none were found it gets the flags from default + * Can be used by other modules + */ + public static int getGlobalFlagsOrDefault(AModuleDialog instance, int defaultFlags) { + return valueOrDefault(toInteger(instance.getData(DATA_FLAGS)), defaultFlags); + } + + /** + * Stores the flags in GlobalData + */ + public static void setGlobalFlags(Flags flags, AModuleDialog instance) { + instance.putData(DATA_FLAGS, flags == null ? null : toHexString(flags.getFlagsAsInt())); + } +} 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 new file mode 100644 index 0000000..8581cda --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/Incognito.java @@ -0,0 +1,84 @@ +package com.trianguloy.urlchecker.modules.companions; + +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.utilities.AndroidUtils; +import com.trianguloy.urlchecker.utilities.GenericPref; + +/** + * Manages the incognito feature + */ +public class Incognito { + + /** + * preference + */ + public static GenericPref.Enumeration PREF(Context cntx) { + return new GenericPref.Enumeration<>("open_incognito", OnOffConfig.AUTO, OnOffConfig.class, cntx); + } + + private final GenericPref.Enumeration pref; + private boolean state = false; + + public Incognito(Context cntx) { + this.pref = PREF(cntx); + } + + /** + * Initialization from a given intent and a button to toggle + */ + public void initFrom(Intent intent, ImageButton button) { + // init state + boolean visible; + switch (pref.get()) { + case AUTO: + default: + // for Firefox + state = intent.getBooleanExtra("private_browsing_mode", false); + visible = true; + break; + case DEFAULT_ON: + state = true; + visible = true; + break; + case DEFAULT_OFF: + state = false; + visible = true; + break; + case ALWAYS_ON: + state = true; + visible = false; + break; + case ALWAYS_OFF: + state = false; + visible = false; + break; + } + + // init button + if (visible) { + // show and configure + button.setVisibility(View.VISIBLE); + AndroidUtils.longTapForDescription(button); + AndroidUtils.toggleableListener(button, + imageButton -> state = !state, + v -> v.setImageResource(state ? R.drawable.incognito : R.drawable.no_incognito) + ); + } else { + // hide + button.setVisibility(View.GONE); + } + } + + /** + * applies the setting to a given intent + */ + public void apply(Intent intent) { + // for Firefox + intent.putExtra("private_browsing_mode", state); + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/companions/OnOffConfig.java b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/OnOffConfig.java new file mode 100644 index 0000000..a1bf507 --- /dev/null +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/companions/OnOffConfig.java @@ -0,0 +1,36 @@ +package com.trianguloy.urlchecker.modules.companions; + +import com.trianguloy.urlchecker.R; +import com.trianguloy.urlchecker.utilities.Enums; + +/** + * Enum for a toggle with on/off x default/always and auto states + */ +public enum OnOffConfig implements Enums.IdEnum, Enums.StringEnum { + AUTO(0, R.string.auto), + DEFAULT_ON(1, R.string.defaultOn), + DEFAULT_OFF(2, R.string.defaultOff), + ALWAYS_ON(3, R.string.alwaysOn), + ALWAYS_OFF(4, R.string.alwaysOff), + ; + + // ----- + + private final int id; + private final int stringResource; + + OnOffConfig(int id, int stringResource) { + this.id = id; + this.stringResource = stringResource; + } + + @Override + public int getId() { + return id; + } + + @Override + public int getStringResource() { + return stringResource; + } +} diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/list/FlagsModule.java b/app/src/main/java/com/trianguloy/urlchecker/modules/list/FlagsModule.java index 4ddb6a8..c460906 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/list/FlagsModule.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/list/FlagsModule.java @@ -1,14 +1,16 @@ package com.trianguloy.urlchecker.modules.list; +import static com.trianguloy.urlchecker.utilities.JavaUtils.valueOrDefault; + +import android.app.AlertDialog; import android.content.Context; -import android.content.Intent; +import android.text.Editable; import android.view.View; -import android.widget.ArrayAdapter; -import android.widget.AutoCompleteTextView; -import android.widget.Button; +import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.EditText; import android.widget.ImageButton; -import android.widget.LinearLayout; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -18,15 +20,27 @@ import com.trianguloy.urlchecker.dialogs.MainDialog; import com.trianguloy.urlchecker.modules.AModuleConfig; import com.trianguloy.urlchecker.modules.AModuleData; import com.trianguloy.urlchecker.modules.AModuleDialog; +import com.trianguloy.urlchecker.modules.companions.Flags; import com.trianguloy.urlchecker.url.UrlData; import com.trianguloy.urlchecker.utilities.AndroidUtils; +import com.trianguloy.urlchecker.utilities.DefaultTextWatcher; +import com.trianguloy.urlchecker.utilities.Enums; import com.trianguloy.urlchecker.utilities.GenericPref; import com.trianguloy.urlchecker.utilities.Inflater; +import com.trianguloy.urlchecker.utilities.InternalFile; +import com.trianguloy.urlchecker.utilities.JavaUtils; +import com.trianguloy.urlchecker.views.CycleImageButton; + +import org.json.JSONException; +import org.json.JSONObject; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.TreeMap; +import java.util.Set; +import java.util.TreeSet; /** * This module allows flag edition @@ -37,6 +51,8 @@ public class FlagsModule extends AModuleData { return new GenericPref.Str("flagsEditor_defaultFlags", null, cntx); } + public static final String DEFAULT_GROUP = "default"; + @Override public String getId() { return "flagsEditor"; @@ -65,279 +81,398 @@ public class FlagsModule extends AModuleData { class FlagsDialog extends AModuleDialog { - public static final String DATA_FLAGS = "flagsEditor.flags"; + private final Flags defaultFlags; + private final Flags currentFlags; - // https://github.com/MuntashirAkon/AppManager/blob/19782da4c8556c817ba5795554a1cc21f38af13a/app/src/main/java/io/github/muntashirakon/AppManager/intercept/ActivityInterceptor.java#L92 - private static final List ALL_FLAGS = List.of( - "FLAG_GRANT_READ_URI_PERMISSION", - "FLAG_GRANT_WRITE_URI_PERMISSION", - "FLAG_FROM_BACKGROUND", - "FLAG_DEBUG_LOG_RESOLUTION", - "FLAG_EXCLUDE_STOPPED_PACKAGES", - "FLAG_INCLUDE_STOPPED_PACKAGES", - "FLAG_GRANT_PERSISTABLE_URI_PERMISSION", - "FLAG_GRANT_PREFIX_URI_PERMISSION", - "FLAG_DIRECT_BOOT_AUTO", - "FLAG_IGNORE_EPHEMERAL", - "FLAG_ACTIVITY_NO_HISTORY", - "FLAG_ACTIVITY_SINGLE_TOP", - "FLAG_ACTIVITY_NEW_TASK", - "FLAG_ACTIVITY_MULTIPLE_TASK", - "FLAG_ACTIVITY_CLEAR_TOP", - "FLAG_ACTIVITY_FORWARD_RESULT", - "FLAG_ACTIVITY_PREVIOUS_IS_TOP", - "FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS", - "FLAG_ACTIVITY_BROUGHT_TO_FRONT", - "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED", - "FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY", - "FLAG_ACTIVITY_NEW_DOCUMENT", - "FLAG_ACTIVITY_NO_USER_ACTION", - "FLAG_ACTIVITY_REORDER_TO_FRONT", - "FLAG_ACTIVITY_NO_ANIMATION", - "FLAG_ACTIVITY_CLEAR_TASK", - "FLAG_ACTIVITY_TASK_ON_HOME", - "FLAG_ACTIVITY_RETAIN_IN_RECENTS", - "FLAG_ACTIVITY_LAUNCH_ADJACENT", - "FLAG_ACTIVITY_MATCH_EXTERNAL", - "FLAG_ACTIVITY_REQUIRE_NON_BROWSER", - "FLAG_ACTIVITY_REQUIRE_DEFAULT" - ); - private final Map flagMap = new TreeMap<>(); // TreeMap to have the entries sorted by key + private Map flagsStatePref; - private final GenericPref.Str defaultFlagsPref; + private ViewGroup shownFlagsVG; + private EditText searchInput; + private ViewGroup hiddenFlagsVG; + private ImageView overflowButton; - private EditText flagsHexText; - private AutoCompleteTextView flagNameText; - private ImageButton more; - private LinearLayout box; + private JSONObject groups; public FlagsDialog(MainDialog dialog) { super(dialog); - - defaultFlagsPref = FlagsModule.DEFAULTFLAGS_PREF(dialog); - - try { - // Only get flags that are present in the current Android version - for (var field : Intent.class.getFields()) { - if (ALL_FLAGS.contains(field.getName())) { - flagMap.put(field.getName(), (Integer) field.get(null)); - } - } - } catch (IllegalAccessException e) { - e.printStackTrace(); - } + defaultFlags = new Flags(getActivity().getIntent().getFlags()); + currentFlags = new Flags(); } @Override public int getLayoutId() { - return R.layout.dialog_editflags; + return R.layout.dialog_flags; } @Override public void onInitialize(View views) { - box = views.findViewById(R.id.box); - flagsHexText = views.findViewById(R.id.flagsHexText); + initGroups(); - // set the flags to the adapter of the input text - flagNameText = views.findViewById(R.id.flagText); - flagNameText.setAdapter(new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_dropdown_item, new ArrayList<>( - flagMap.keySet() - ))); - // so the dropdown gets the maximum width possible - flagNameText.setDropDownAnchor(R.id.addFlagLayout); - // FIXME better search, currently it is an autofill, not a search - // FIXME sometimes its hidden behind keyboard + shownFlagsVG = views.findViewById(R.id.shownFlags); + searchInput = views.findViewById(R.id.search); + hiddenFlagsVG = views.findViewById(R.id.hiddenFlags); - // get initial flags - var defaultFlagsStr = defaultFlagsPref.get(); - if (defaultFlagsStr != null) { - setFlags(toInteger(defaultFlagsStr)); - } + // Button to open the `box` with the hidden flags (more indicator) + overflowButton = views.findViewById(R.id.overflowButton); - // press add to add a flag - views.