From 0d4bfb7bd5dfc640fa73e3aa197578e34fa155f3 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 26 Apr 2021 01:11:35 +0200 Subject: [PATCH 01/37] Replace jcenter() with mavenCentral() This is since JCenter is being shut down. --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 78a9f1b233..01d0fe73a4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ buildscript { repositories { - jcenter() + mavenCentral() google() } dependencies { @@ -11,7 +11,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } From 93e1b132786d5cc7b314f2e42fa3430fb0a4103a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 26 Apr 2021 12:43:55 +0500 Subject: [PATCH 02/37] Update README --- README.md | 117 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 82 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 7b1ef24b26..a961421bee 100644 --- a/README.md +++ b/README.md @@ -6,40 +6,46 @@ [Termux](https://termux.com) is an Android terminal application and Linux environment. -- [Termux Reddit community](https://reddit.com/r/termux) -- [Termux Wiki](https://wiki.termux.com/wiki/) -- [Termux Twitter](http://twitter.com/termux/) - -Note that this repository is for the app itself (the user interface and the -terminal emulation). For the packages installable inside the app, see -[termux/termux-packages](https://github.com/termux/termux-packages) +Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages). *** -**@termux is looking for Termux Application maintainer for implementing new features, -fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.** +**@termux is looking for Termux Application maintainers for implementing new features, fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.** Issue https://github.com/termux/termux-app/issues/1072 needs extra attention. *** -## Installation +### Contents +- [Termux App and Plugins](#Termux-App-and-Plugins) +- [Installation](#Installation) +- [Uninstallation](#Uninstallation) +- [Important Links](#Important-Links) +## -Termux can be obtained through various sources listed below. -The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from F-Droid and another one from a different source. Android Package Manager will also normally not allow installation of APKs with a different signatures and you will get an error on installation but this restriction can be bypassed with root or with custom roms. If you wish to install from a different source, then you must uninstall any and all existing Termux app or its plugin APKs from your device first, then install all new APKs from the same new source. -Following is a list of Termux app and its plugins. +## Termux App and Plugins + +The core [Termux](https://github.com/termux/termux-app) app comes with the following optional plugin apps. -- [Termux](https://github.com/termux/termux-app) - [Termux:API](https://github.com/termux/termux-api) - [Termux:Boot](https://github.com/termux/termux-boot) - [Termux:Float](https://github.com/termux/termux-float) - [Termux:Styling](https://github.com/termux/termux-styling) - [Termux:Tasker](https://github.com/termux/termux-tasker) - [Termux:Widget](https://github.com/termux/termux-widget) +## - If you wish to install Termux from a difference source, you must uninstall all the apps listed above before installing from new source. Go to `Android Settings` -> `Applications` and then look for the following apps. You can also use the search feature if its available on your device and search `termux` in the applications list. Even if you think you have not installed any of the plugins, its strongly suggesting to go through the application list in Android settings and double check if installation is failing. + + +## Installation + +Termux can be obtained through various sources listed below. + +The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from F-Droid and another one from a different source. Android Package Manager will also normally not allow installation of APKs with a different signatures and you will get an error on installation but this restriction can be bypassed with root or with custom roms. + +If you wish to install from a different source, then you must uninstall **any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#Uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before uninstallation. ### F-Droid @@ -48,48 +54,89 @@ Termux application can be obtained from F-Droid [here](https://f-droid.org/en/pa ### Debug Builds For users who don't want to wait for F-Droid releases and want to try out the latest features immediately or want to test their pull requests can get the APKs from [Github Actions](https://github.com/termux/termux-app/actions) page from the workflow runs labeled `Build`. The APK will be listed under `Artifacts` section. These are published for each commit done to the repository. These APKs are [debuggable](https://developer.android.com/studio/debug) and are also not compatible with other sources. + +### Google Playstore **(Deprecated)** + +**Termux and its plugins are no longer updated on playstore due to [android 10 issues](https://play.google.com/store/apps/details?id=com.termux).** There are currently no immediate plans to resume updates on playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. + +If for some reason you don't want to switch, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to switch to a different mirror and then run `pkg upgrade` command to update all packages to the latest available versions if you want, or at least update `termux-tools` package with `pkg install termux-tools` command. +## + + + +## Uninstallation + +Uninstallation may be required if a user doesn't want Termux installed in their device anymore or is switching to a different [install source](#Installation). You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before uninstallation. + +To uninstall Termux completely, you must uninstall **any and all existing Termux or its plugin app APKs** listed in [Termux App and Plugins](#Termux-App-and-Plugins). + +Go to `Android Settings` -> `Applications` and then look for those apps. You can also use the search feature if its available on your device and search `termux` in the applications list. + +Even if you think you have not installed any of the plugins, its strongly suggesting to go through the application list in Android settings and double check. ## -## Terminal resources +## Important Links + +### Community +All community links are available [here](https://wiki.termux.com/wiki/Community). + +The main ones are the following. + +- [Termux Reddit community](https://reddit.com/r/termux) +- [Termux Matrix Channel](https://matrix.to/#termux_termux:gitter.im) +- [Termux Dev Matrix Channel](https://matrix.to/#termux_dev:gitter.im) +- [Termux Twitter](http://twitter.com/termux/) +- [Termux Reports Email](mailto:termuxreports@groups.io) + +### Wikis + +- [Termux Wiki](https://wiki.termux.com/wiki/) +- [Termux App Wiki](https://github.com/termux/termux-app/wiki) +- [Termux Packages Wiki](https://github.com/termux/termux-packages/wiki) + +### Miscellaneous +- [FAQ](https://wiki.termux.com/wiki/FAQ) +- [Termux File System Layout](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout) +- [Differences From Linux](https://wiki.termux.com/wiki/Differences_from_Linux) +- [Package Management](https://wiki.termux.com/wiki/Package_Management) +- [Remote_Access](https://wiki.termux.com/wiki/Remote_Access) +- [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) +- [Terminal Settings](https://wiki.termux.com/wiki/Terminal_Settings) +- [Touch Keyboard](https://wiki.termux.com/wiki/Touch_Keyboard) +- [Android Storage and Sharing Data with Other Apps](https://wiki.termux.com/wiki/Internal_and_external_storage) +- [Android APIs](https://wiki.termux.com/wiki/Termux:API) +- [Moved Termux Packages Hosting From Bintray to IPFS](https://github.com/termux/termux-packages/issues/6348) +- [Termux and Android 10](https://github.com/termux/termux-packages/wiki/Termux-and-Android-10) + +### Terminal resources - [XTerm control sequences](http://invisible-island.net/xterm/ctlseqs/ctlseqs.html) - [vt100.net](http://vt100.net/) - [Terminal codes (ANSI and terminfo equivalents)](http://wiki.bash-hackers.org/scripting/terminalcodes) -## Terminal emulators +### Terminal emulators -- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. - [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), - and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED). +- VTE (libvte): Terminal emulator widget for GTK+, mainly used in gnome-terminal. [Source](https://github.com/GNOME/vte), [Open Issues](https://bugzilla.gnome.org/buglist.cgi?quicksearch=product%3A%22vte%22+), and [All (including closed) issues](https://bugzilla.gnome.org/buglist.cgi?bug_status=RESOLVED&bug_status=VERIFIED&chfield=resolution&chfieldfrom=-2000d&chfieldvalue=FIXED&product=vte&resolution=FIXED). -- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), - [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) - (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)). +- iTerm 2: OS X terminal application. [Source](https://github.com/gnachman/iTerm2), [Issues](https://gitlab.com/gnachman/iterm2/issues) and [Documentation](http://www.iterm2.com/documentation.html) (which includes [iTerm2 proprietary escape codes](http://www.iterm2.com/documentation-escape-codes.html)). -- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), - in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), - [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) - and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole). +- Konsole: KDE terminal application. [Source](https://projects.kde.org/projects/kde/applications/konsole/repository), in particular [tests](https://projects.kde.org/projects/kde/applications/konsole/repository/revisions/master/show/tests), [Bugs](https://bugs.kde.org/buglist.cgi?bug_severity=critical&bug_severity=grave&bug_severity=major&bug_severity=crash&bug_severity=normal&bug_severity=minor&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole) and [Wishes](https://bugs.kde.org/buglist.cgi?bug_severity=wishlist&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&product=konsole). -- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), - including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), - and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm). +- hterm: JavaScript terminal implementation from Chromium. [Source](https://github.com/chromium/hterm), including [tests](https://github.com/chromium/hterm/blob/master/js/hterm_vt_tests.js), and [Google group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-hterm). -- xterm: The grandfather of terminal emulators. - [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz). +- xterm: The grandfather of terminal emulators. [Source](http://invisible-island.net/datafiles/release/xterm.tar.gz). - Connectbot: Android SSH client. [Source](https://github.com/connectbot/connectbot) -- Android Terminal Emulator: Android terminal app which Termux terminal handling - is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator). +- Android Terminal Emulator: Android terminal app which Termux terminal handling is based on. Inactive. [Source](https://github.com/jackpal/Android-Terminal-Emulator). ## ## For Devs and Contributors -The [termux-shared](termux-shared) library was added in [`v0.109`](https://github.com/termux/termux-app/releases/tag/v0.109). It defines shared constants and utils of Termux app and its plugins. It was created to allow for removal of all hardcoded paths in Termux app. The termux plugins will hopefully use this in future as well. If you are contributing code that is using a constant or a util that may be shared, then define it in `termux-shared` library if it currently doesn't exist and reference it from there. Update the relevant changelogs as well. Pull requests using hardcoded values **will not** be accepted. +The [termux-shared](termux-shared) library was added in [`v0.109`](https://github.com/termux/termux-app/releases/tag/v0.109). It defines shared constants and utils of Termux app and its plugins. It was created to allow for removal of all hardcoded paths in Termux app. The termux plugins will hopefully use this in future as well. If you are contributing code that is using a constant or a util that may be shared, then define it in `termux-shared` library if it currently doesn't exist and reference it from there. Update the relevant changelogs as well. Pull requests using hardcoded values **will/should not** be accepted. The main Termux constants are defined by [`TermuxConstants`](https://github.com/termux/termux-app/blob/master/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java) class. It also contains information on how to fork Termux or build it with your own package name. Changing the package name will require building the bootstrap zip packages and other packages with the new `$PREFIX`, check [Building Packages](https://github.com/termux/termux-packages/wiki/Building-packages) for more info. From cfebb3358d4c7675a7d69aecad8e05487baa5ef4 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Tue, 27 Apr 2021 15:50:57 +0500 Subject: [PATCH 03/37] Update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a961421bee..fdb49de0b4 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Issue https://github.com/termux/termux-app/issues/1072 needs extra attention. - [Installation](#Installation) - [Uninstallation](#Uninstallation) - [Important Links](#Important-Links) +- [For Devs and Contributors](#For-Devs-and-Contributors) ## @@ -41,9 +42,9 @@ The core [Termux](https://github.com/termux/termux-app) app comes with the follo ## Installation -Termux can be obtained through various sources listed below. +Termux can be obtained through various sources listed below for **only** Android `>= 7`. Support was dropped for Android `5` and `6` on [2020-01-01](https://www.reddit.com/r/termux/comments/dnzdbs/end_of_android56_support_on_20200101/) at `v0.83`, old builds are available on [archive.org](https://archive.org/details/termux-repositories-legacy). -The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from F-Droid and another one from a different source. Android Package Manager will also normally not allow installation of APKs with a different signatures and you will get an error on installation but this restriction can be bypassed with root or with custom roms. +The APK files of different sources are signed with different signature keys. The `Termux` app and all its plugins use the same [sharedUserId](https://developer.android.com/guide/topics/manifest/manifest-element) `com.termux` and so all their APKs installed on a device must have been signed with the same signature key to work together and so they must all be installed from the same source. Do not attempt to mix them together, i.e do not try to install an app or plugin from F-Droid and another one from a different source. Android Package Manager will also normally not allow installation of APKs with a different signatures and you will get errors on installation like `App not installed`, `Failed to install due to an unknown error`, `INSTALL_FAILED_UPDATE_INCOMPATIBLE`, `INSTALL_FAILED_SHARED_USER_INCOMPATIBLE`, `signatures do not match previously installed version`, etc. This restriction can be bypassed with root or with custom roms. If you wish to install from a different source, then you must uninstall **any and all existing Termux or its plugin app APKs** from your device first, then install all new APKs from the same new source. Check [Uninstallation](#Uninstallation) section for details. You may also want to consider [Backing up Termux](https://wiki.termux.com/wiki/Backing_up_Termux) before uninstallation. @@ -57,7 +58,7 @@ For users who don't want to wait for F-Droid releases and want to try out the la ### Google Playstore **(Deprecated)** -**Termux and its plugins are no longer updated on playstore due to [android 10 issues](https://play.google.com/store/apps/details?id=com.termux).** There are currently no immediate plans to resume updates on playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. +**Termux and its plugins are no longer updated on [Google playstore](https://play.google.com/store/apps/details?id=com.termux) due to [android 10 issues](https://play.google.com/store/apps/details?id=com.termux).** The last version released for Android `>= 7` was `v0.101`. There are currently no immediate plans to resume updates on Google playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. If for some reason you don't want to switch, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to switch to a different mirror and then run `pkg upgrade` command to update all packages to the latest available versions if you want, or at least update `termux-tools` package with `pkg install termux-tools` command. ## From b84854af92b11f4ec3f1ce35483d52953a9ab6cd Mon Sep 17 00:00:00 2001 From: agnostic-apollo <31106828+agnostic-apollo@users.noreply.github.com> Date: Sat, 1 May 2021 19:35:52 +0500 Subject: [PATCH 04/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fdb49de0b4..8736b56193 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ For users who don't want to wait for F-Droid releases and want to try out the la ### Google Playstore **(Deprecated)** -**Termux and its plugins are no longer updated on [Google playstore](https://play.google.com/store/apps/details?id=com.termux) due to [android 10 issues](https://play.google.com/store/apps/details?id=com.termux).** The last version released for Android `>= 7` was `v0.101`. There are currently no immediate plans to resume updates on Google playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. +**Termux and its plugins are no longer updated on [Google playstore](https://play.google.com/store/apps/details?id=com.termux) due to [android 10 issues](https://github.com/termux/termux-packages/wiki/Termux-and-Android-10).** The last version released for Android `>= 7` was `v0.101`. There are currently no immediate plans to resume updates on Google playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. If for some reason you don't want to switch, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to switch to a different mirror and then run `pkg upgrade` command to update all packages to the latest available versions if you want, or at least update `termux-tools` package with `pkg install termux-tools` command. ## From b268b6edf742db9d344e5631b1dd134e54a39351 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 1 May 2021 23:31:02 +0500 Subject: [PATCH 05/37] Disable error flashes when clearing TMPDIR directory on termux app exit Rooted users were getting `Clearing $TMPDIR directory at path "/data/data/com.termux/files/usr/tmp" failed` flash errors when they exited Termux if directories existed in TMPDIR that only had `root` user ownership, since they would fail to get cleared since clearing was being run as the termux app user instead of as the root user. Now errors will only be logged to logcat. --- .../src/main/java/com/termux/shared/shell/ShellUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java b/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java index 47ad08bbe7..fe1efc8da5 100644 --- a/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/shell/ShellUtils.java @@ -157,7 +157,7 @@ public static void clearTermuxTMPDIR(Context context, boolean onlyIfExists) { String errmsg; errmsg = FileUtils.clearDirectory(context, "$TMPDIR", FileUtils.getCanonicalPath(TermuxConstants.TERMUX_TMP_PREFIX_DIR_PATH, null, false)); if (errmsg != null) { - Logger.logErrorAndShowToast(context, errmsg); + Logger.logError(errmsg); } } From 42ad3723fd56c1b3db923eb3617e9107f7905813 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 3 May 2021 00:39:42 +0500 Subject: [PATCH 06/37] Fix NullPointerExceptions for cases when TermuxActivity tries to access TermuxService when it doesn't hold a reference Fixes #2026 --- .../terminal/TermuxTerminalSessionClient.java | 48 ++++++++++++++----- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java index c1f1185b03..98003d0d7d 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java @@ -74,7 +74,9 @@ public void onTitleChanged(TerminalSession updatedSession) { @Override public void onSessionFinished(final TerminalSession finishedSession) { - if (mActivity.getTermuxService().wantsToStop()) { + TermuxService service = mActivity.getTermuxService(); + + if (service == null || service.wantsToStop()) { // The service wants to stop as soon as possible. mActivity.finishActivityIfNotFinishing(); return; @@ -82,7 +84,7 @@ public void onSessionFinished(final TerminalSession finishedSession) { if (mActivity.isVisible() && finishedSession != mActivity.getCurrentSession()) { // Show toast for non-current sessions that exit. - int indexOfSession = mActivity.getTermuxService().getIndexOfSession(finishedSession); + int indexOfSession = service.getIndexOfSession(finishedSession); // Verify that session was not removed before we got told about it finishing: if (indexOfSession >= 0) mActivity.showToast(toToastTitle(finishedSession) + " - exited", true); @@ -91,7 +93,7 @@ public void onSessionFinished(final TerminalSession finishedSession) { if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) { // On Android TV devices we need to use older behaviour because we may // not be able to have multiple launcher icons. - if (mActivity.getTermuxService().getTermuxSessionsSize() > 1) { + if (service.getTermuxSessionsSize() > 1) { removeFinishedSession(finishedSession); } } else { @@ -161,6 +163,7 @@ void notifyOfSessionChange() { public void switchToSession(boolean forward) { TermuxService service = mActivity.getTermuxService(); + if (service == null) return; TerminalSession currentTerminalSession = mActivity.getCurrentSession(); int index = service.getIndexOfSession(currentTerminalSession); @@ -177,7 +180,10 @@ public void switchToSession(boolean forward) { } public void switchToSession(int index) { - TermuxSession termuxSession = mActivity.getTermuxService().getTermuxSession(index); + TermuxService service = mActivity.getTermuxService(); + if (service == null) return; + + TermuxSession termuxSession = service.getTermuxSession(index); if (termuxSession != null) setCurrentSession(termuxSession.getTerminalSession()); } @@ -193,7 +199,10 @@ public void renameSession(final TerminalSession sessionToRename) { } public void addNewSession(boolean isFailSafe, String sessionName) { - if (mActivity.getTermuxService().getTermuxSessionsSize() >= MAX_SESSIONS) { + TermuxService service = mActivity.getTermuxService(); + if (service == null) return; + + if (service.getTermuxSessionsSize() >= MAX_SESSIONS) { new AlertDialog.Builder(mActivity).setTitle(R.string.title_max_terminals_reached).setMessage(R.string.msg_max_terminals_reached) .setPositiveButton(android.R.string.ok, null).show(); } else { @@ -206,7 +215,7 @@ public void addNewSession(boolean isFailSafe, String sessionName) { workingDirectory = currentSession.getCwd(); } - TermuxSession newTermuxSession = mActivity.getTermuxService().createTermuxSession(null, null, null, workingDirectory, isFailSafe, sessionName); + TermuxSession newTermuxSession = service.createTermuxSession(null, null, null, workingDirectory, isFailSafe, sessionName); if (newTermuxSession == null) return; TerminalSession newTerminalSession = newTermuxSession.getTerminalSession(); @@ -226,14 +235,17 @@ public void setCurrentStoredSession() { /** The current session as stored or the last one if that does not exist. */ public TerminalSession getCurrentStoredSessionOrLast() { - TerminalSession stored = getCurrentStoredSession(mActivity); + TerminalSession stored = getCurrentStoredSession(); if (stored != null) { // If a stored session is in the list of currently running sessions, then return it return stored; } else { // Else return the last session currently running - TermuxSession termuxSession = mActivity.getTermuxService().getLastTermuxSession(); + TermuxService service = mActivity.getTermuxService(); + if (service == null) return null; + + TermuxSession termuxSession = service.getLastTermuxSession(); if (termuxSession != null) return termuxSession.getTerminalSession(); else @@ -241,7 +253,7 @@ public TerminalSession getCurrentStoredSessionOrLast() { } } - private TerminalSession getCurrentStoredSession(TermuxActivity context) { + private TerminalSession getCurrentStoredSession() { String sessionHandle = mActivity.getPreferences().getCurrentSession(); // If no session is stored in shared preferences @@ -249,16 +261,20 @@ private TerminalSession getCurrentStoredSession(TermuxActivity context) { return null; // Check if the session handle found matches one of the currently running sessions - return context.getTermuxService().getTerminalSessionForHandle(sessionHandle); + TermuxService service = mActivity.getTermuxService(); + if (service == null) return null; + + return service.getTerminalSessionForHandle(sessionHandle); } public void removeFinishedSession(TerminalSession finishedSession) { // Return pressed with finished session - remove it. TermuxService service = mActivity.getTermuxService(); + if (service == null) return; int index = service.removeTermuxSession(finishedSession); - int size = mActivity.getTermuxService().getTermuxSessionsSize(); + int size = service.getTermuxSessionsSize(); if (size == 0) { // There are no sessions to show, so finish the activity. mActivity.finishActivityIfNotFinishing(); @@ -278,7 +294,10 @@ public void termuxSessionListNotifyUpdated() { public void checkAndScrollToSession(TerminalSession session) { if (!mActivity.isVisible()) return; - final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session); + TermuxService service = mActivity.getTermuxService(); + if (service == null) return; + + final int indexOfSession = service.getIndexOfSession(session); if (indexOfSession < 0) return; final ListView termuxSessionsListView = mActivity.findViewById(R.id.terminal_sessions_list); if (termuxSessionsListView == null) return; @@ -290,7 +309,10 @@ public void checkAndScrollToSession(TerminalSession session) { String toToastTitle(TerminalSession session) { - final int indexOfSession = mActivity.getTermuxService().getIndexOfSession(session); + TermuxService service = mActivity.getTermuxService(); + if (service == null) return null; + + final int indexOfSession = service.getIndexOfSession(session); if (indexOfSession < 0) return null; StringBuilder toastTitle = new StringBuilder("[" + (indexOfSession + 1) + "]"); if (!TextUtils.isEmpty(session.mSessionName)) { From 4711094614f88ad62c6421e536dcb023119d16fb Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 3 May 2021 00:40:03 +0500 Subject: [PATCH 07/37] Bump ndk to v22.1.7171670 Will also remove requirement for F-Droid metadata/com.termux.yml ndk version patch https://gitlab.com/fdroid/fdroiddata/-/commit/1fe5c6b905b725bb17179b5bce01fd06d094dd96#54c3cc576ac595d35a41ce9e4a69e1c905fd6ea4 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 99681235a6..bcb6c60ce3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,7 +20,7 @@ termuxVersionCode=111 minSdkVersion=24 targetSdkVersion=28 -ndkVersion=22.0.7026061 +ndkVersion=22.1.7171670 compileSdkVersion=29 markwonVersion=4.6.2 From f3f58c8fc7966cb55ef9297a306577a935b28f51 Mon Sep 17 00:00:00 2001 From: agnostic-apollo <31106828+agnostic-apollo@users.noreply.github.com> Date: Mon, 3 May 2021 16:07:18 +0500 Subject: [PATCH 08/37] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8736b56193..3a07a7c2e8 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Note that this repository is for the app itself (the user interface and the terminal emulation). For the packages installable inside the app, see [termux/termux-packages](https://github.com/termux/termux-packages). +Quick how-to about Termux package management is available at [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management). It also has info on how to fix **`repository is under maintenance or down`** errors when running `apt` or `pkg` commands. + *** **@termux is looking for Termux Application maintainers for implementing new features, fixing bugs and reviewing pull requests since current one (@fornwall) is inactive.** From 3e7b3604a48a1fe312b082d3c19be21ea4b9addb Mon Sep 17 00:00:00 2001 From: agnostic-apollo <31106828+agnostic-apollo@users.noreply.github.com> Date: Mon, 3 May 2021 16:08:04 +0500 Subject: [PATCH 09/37] Update LICENSE.md --- LICENSE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.md b/LICENSE.md index f6dd0a8b5b..b9aa696f7e 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -This repository is released under [GPLv3 only](https://www.gnu.org/licenses/gpl-3.0.html) license. +The `termux/termux-app` repository is released under [GPLv3 only](https://www.gnu.org/licenses/gpl-3.0.html) license. ### Exceptions From bc52a4e90c3a94f56d9aaa0bb775c2cbe787cb13 Mon Sep 17 00:00:00 2001 From: agnostic-apollo <31106828+agnostic-apollo@users.noreply.github.com> Date: Mon, 3 May 2021 16:28:02 +0500 Subject: [PATCH 10/37] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a07a7c2e8..f27125f79e 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ For users who don't want to wait for F-Droid releases and want to try out the la **Termux and its plugins are no longer updated on [Google playstore](https://play.google.com/store/apps/details?id=com.termux) due to [android 10 issues](https://github.com/termux/termux-packages/wiki/Termux-and-Android-10).** The last version released for Android `>= 7` was `v0.101`. There are currently no immediate plans to resume updates on Google playstore. **It is highly recommended to not install Termux from playstore for now.** Any current users **should switch** to a different source like F-Droid. -If for some reason you don't want to switch, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to switch to a different mirror and then run `pkg upgrade` command to update all packages to the latest available versions if you want, or at least update `termux-tools` package with `pkg install termux-tools` command. +If for some reason you don't want to switch, then at least check [Package Management](https://github.com/termux/termux-packages/wiki/Package-Management) to **change your mirror**, otherwise you will get **`repository is under maintenance or down`** errors when running `apt` or `pkg` commands. After that, it is also **highly advisable** to run `pkg upgrade` command to update all packages to the latest available versions, or at least update `termux-tools` package with `pkg install termux-tools` command. ## From 4850678d557c8de654576ca41fe9e46404cb9b8b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 May 2021 03:14:20 +0500 Subject: [PATCH 11/37] Move Build.ID and Build.DISPLAY to Software section of device info markdown --- .../src/main/java/com/termux/shared/termux/TermuxUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java index 374355c74a..3e08b9fe56 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java @@ -223,6 +223,8 @@ public static String getDeviceInfoMarkdownString(@NonNull final Context context) appendPropertyToMarkdown(markdownString, "RELEASE", Build.VERSION.RELEASE); else appendPropertyToMarkdown(markdownString, "CODENAME", Build.VERSION.CODENAME); + appendPropertyToMarkdown(markdownString, "ID", Build.ID); + appendPropertyToMarkdown(markdownString, "DISPLAY", Build.DISPLAY); appendPropertyToMarkdown(markdownString, "INCREMENTAL", Build.VERSION.INCREMENTAL); appendPropertyToMarkdownIfSet(markdownString, "SECURITY_PATCH", systemProperties.getProperty("ro.build.version.security_patch")); appendPropertyToMarkdownIfSet(markdownString, "IS_DEBUGGABLE", systemProperties.getProperty("ro.debuggable")); @@ -236,8 +238,6 @@ public static String getDeviceInfoMarkdownString(@NonNull final Context context) appendPropertyToMarkdown(markdownString, "BRAND", Build.BRAND); appendPropertyToMarkdown(markdownString, "MODEL", Build.MODEL); appendPropertyToMarkdown(markdownString, "PRODUCT", Build.PRODUCT); - appendPropertyToMarkdown(markdownString, "DISPLAY", Build.DISPLAY); - appendPropertyToMarkdown(markdownString, "ID", Build.ID); appendPropertyToMarkdown(markdownString, "BOARD", Build.BOARD); appendPropertyToMarkdown(markdownString, "HARDWARE", Build.HARDWARE); appendPropertyToMarkdown(markdownString, "DEVICE", Build.DEVICE); From 596aa56b38b469f082e363953b9ff71f599642f4 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 May 2021 03:24:09 +0500 Subject: [PATCH 12/37] Update report issue message to ask users to provide details on what they were doing that caused the Termux app crash --- termux-shared/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index d1b4e49456..71bd7bca5e 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -73,7 +73,7 @@ - If you want to report this issue, then copy its text from the options menu (3-dots on top right) and post an issue on one of the following links. If you are posting on Github, then post it in the repository at which the report belongs at. You may optionally remove any device specific info that you consider private or don\'t want to share or that is not relevant to the issue. + If you want to report this issue, then copy its text from the options menu (3-dots on top right) and post an issue on one of the following links.\n\nIf you are posting a Termux app crash report, then please provide details on what you were doing that caused the crash and how to reproduce it, if possible.\n\nIf you are posting an issue on Github, then post it in the repository at which the report belongs at. You may optionally remove any device specific info that you consider private or don\'t want to share or that is not relevant to the issue. From 4d1851e6beb5afb9c8de952797364fb4ff14faf2 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 May 2021 04:16:51 +0500 Subject: [PATCH 13/37] Fix issues where soft keyboard was not shown in some cases when hardware keyboard was attached For Termux app to be able to show a soft keyboard while a hardware keyboard is attached requires either of 2 cases: 1. User has enabled "Show on-screen keyboard while hardware keyboard is attached" toggle in Android "Language and Input" settings. 2. The toggle is disabled, but the soft keyboard app overrides the default implementation of `InputMethodService.onEvaluateInputViewShown()` and returns `true`. Some keyboard apps have a setting for this, like HackerKeyboard, but its not supported by all keyboard apps. https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=1751 Termux previously didn't forcefully show a keyboard when the drawer "KEYBOARD" toggle button was pressed and only did that for the "KEYBOARD" extra keys toggle button. This prevented the keyboard to be shown for case 2 even when the user attempted to show the keyboard with the drawer "KEYBOARD" toggle. Now both buttons will forcefully show the keyboard. Moreover, previously at app startup for case 2, the keyboard wasn't being shown. Now it will automatically be shown without requiring a manual press of a "KEYBOARD" toggle button. This may also solve the issue where the soft keyboard wouldn't show even when the toggle of case 1 was enabled. --- .../java/com/termux/app/TermuxActivity.java | 19 +----- .../terminal/TermuxTerminalViewClient.java | 60 ++++++++++++++++++- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index f323d0fe22..003dec958d 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -237,7 +237,7 @@ public void onStart() { public void onResume() { super.onResume(); - setSoftKeyboardState(); + mTermuxTerminalViewClient.setSoftKeyboardState(); } /** @@ -418,8 +418,7 @@ private void setNewSessionButtonView() { private void setToggleKeyboardView() { findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); + TermuxTerminalViewClient.toggleSoftKeyboard(this); getDrawer().closeDrawers(); }); @@ -429,19 +428,7 @@ private void setToggleKeyboardView() { }); } - private void setSoftKeyboardState() { - // If soft keyboard is to disabled - if (!mPreferences.getSoftKeyboardEnabled()) { - getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } else { - getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } - // If soft keyboard is to be hidden on startup - if (mProperties.shouldSoftKeyboardBeHiddenOnStartup()) { - getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); - } - } @@ -780,7 +767,7 @@ private void reloadTermuxActivityStyling() { setTerminalToolbarHeight(); - setSoftKeyboardState(); + mTermuxTerminalViewClient.setSoftKeyboardState(); // To change the activity and drawer theme, activity needs to be recreated. // But this will destroy the activity, and will call the onCreate() again. diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index e906b01cca..400db111e3 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -7,6 +7,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.net.Uri; import android.text.TextUtils; @@ -14,6 +15,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.ListView; import android.widget.Toast; @@ -36,6 +38,7 @@ import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; +import com.termux.view.TerminalView; import java.util.Arrays; import java.util.Collections; @@ -122,8 +125,7 @@ public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { mActivity.getDrawer().closeDrawers(); } else if (unicodeChar == 'k'/* keyboard */) { - InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + toggleSoftKeyboard(mActivity); } else if (unicodeChar == 'm'/* menu */) { mActivity.getTerminalView().showContextMenu(); } else if (unicodeChar == 'r'/* rename */) { @@ -151,6 +153,8 @@ public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession } + + @Override public boolean onKeyUp(int keyCode, KeyEvent e) { return handleVirtualKeys(keyCode, e, false); @@ -337,6 +341,58 @@ public void changeFontSize(boolean increase) { } + /** + * Toggle the soft keyboard. The {@link InputMethodManager#SHOW_FORCED} is passed as + * {@code showFlags} so that keyboard is forcefully shown if it needs to be enabled. + * + * This is also important for soft keyboard to be shown when a hardware keyboard is attached, and + * user has disabled the {@code Show on-screen keyboard while hardware keyboard is attached} toggle + * in Android "Language and Input" settings but the current soft keyboard app overrides the + * default implementation of {@link InputMethodService#onEvaluateInputViewShown()} and returns + * {@code true}. + */ + public static void toggleSoftKeyboard(Context context) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + + /** + * Show the soft keyboard. The {@code 0} value is passed as {@code flags} so that keyboard is + * forcefully shown. + * + * This is also important for soft keyboard to be shown on app startup when a hardware keyboard + * is attached, and user has disabled the {@code Show on-screen keyboard while hardware keyboard + * is attached} toggle in Android "Language and Input" settings but the current soft keyboard app + * overrides the default implementation of {@link InputMethodService#onEvaluateInputViewShown()} + * and returns {@code true}. + * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=1751 + * + * Also check {@link InputMethodService#onShowInputRequested(int, boolean)} which must return + * {@code true}, which can be done by failing its {@code ((flags&InputMethod.SHOW_EXPLICIT) == 0)} + * check by passing {@code 0} as {@code flags}. + * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=2022 + */ + public static void showSoftKeyboard(Context context, TerminalView terminalView) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(terminalView, 0); + } + + public void setSoftKeyboardState() { + // If soft keyboard is to disabled + if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { + mActivity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } else { + mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + showSoftKeyboard(mActivity, mActivity.getTerminalView()); + } + + // If soft keyboard is to be hidden on startup + if (mActivity.getProperties().shouldSoftKeyboardBeHiddenOnStartup()) { + mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + } + } + + public void shareSessionTranscript() { TerminalSession session = mActivity.getCurrentSession(); From 39c69db820efec991e6bbfef9bd7ea4cc50912be Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 9 May 2021 07:28:12 +0500 Subject: [PATCH 14/37] Fix issues where soft keyboard was not shown in some cases when hardware keyboard was attached v2 This is an update to 4d1851e6 commit. The toggle logic change previously was actually being applied to ctrl+alt+k hardware keyboard shortcut instead of the mentioned extra keys "KEYBOARD" toggle. However, now it applies to the extra keys "KEYBOARD" toggle button as well, in addition to drawer "KEYBOARD" toggle button and ctrl+alt+k hardware keyboard shortcut. They will all behave the same now. Updated onSingleTapUp() to also forcefully show keyboard. Fixed issue where "hide-soft-keyboard-on-startup" property wasn't respected anymore due to forced keyboard showing done in 4d1851e6. Removed "stateAlwaysVisible" flag from AndroidManifest since its ignored in Android 10 by default and not needed due to usage of InputMethodManager.showSoftInput(). https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE Moved "adjustResize" from AndroidManifest into java code (which is also deprecated in API 30) to centralize keyboard logic. --- app/src/main/AndroidManifest.xml | 3 +- .../java/com/termux/app/TermuxActivity.java | 13 +-- .../terminal/TermuxTerminalViewClient.java | 88 ++++++++++++++++--- .../terminal/io/TerminalToolbarViewPager.java | 1 + .../terminal/io/extrakeys/ExtraKeysView.java | 12 ++- 5 files changed, 92 insertions(+), 25 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cef9943d05..ee6eae63cc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,8 +57,7 @@ android:configChanges="orientation|screenSize|smallestScreenSize|density|screenLayout|uiMode|keyboard|keyboardHidden|navigation" android:label="@string/application_name" android:launchMode="singleTask" - android:resizeableActivity="true" - android:windowSoftInputMode="adjustResize|stateAlwaysVisible"> + android:resizeableActivity="true"> diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 003dec958d..1381998e9d 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -25,7 +25,6 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.autofill.AutofillManager; -import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; @@ -237,7 +236,7 @@ public void onStart() { public void onResume() { super.onResume(); - mTermuxTerminalViewClient.setSoftKeyboardState(); + mTermuxTerminalViewClient.setSoftKeyboardState(true); } /** @@ -418,7 +417,7 @@ private void setNewSessionButtonView() { private void setToggleKeyboardView() { findViewById(R.id.toggle_keyboard_button).setOnClickListener(v -> { - TermuxTerminalViewClient.toggleSoftKeyboard(this); + mTermuxTerminalViewClient.onToggleSoftKeyboardRequest(); getDrawer().closeDrawers(); }); @@ -447,8 +446,6 @@ private void setTermuxTerminalViewAndClients() { // Set {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.getTerminalViewKeyLoggingEnabled()); - mTerminalView.requestFocus(); - mTermuxTerminalSessionClient.checkForFontAndColors(); } @@ -677,6 +674,10 @@ public TerminalView getTerminalView() { return mTerminalView; } + public TermuxTerminalViewClient getTermuxTerminalViewClient() { + return mTermuxTerminalViewClient; + } + public TermuxTerminalSessionClient getTermuxTerminalSessionClient() { return mTermuxTerminalSessionClient; } @@ -767,7 +768,7 @@ private void reloadTermuxActivityStyling() { setTerminalToolbarHeight(); - mTermuxTerminalViewClient.setSoftKeyboardState(); + mTermuxTerminalViewClient.setSoftKeyboardState(true); // To change the activity and drawer theme, activity needs to be recreated. // But this will destroy the activity, and will call the onCreate() again. diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 400db111e3..57cb573280 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -1,6 +1,7 @@ package com.termux.app.terminal; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; @@ -15,6 +16,7 @@ import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.View; import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; import android.widget.ListView; @@ -38,7 +40,6 @@ import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; -import com.termux.view.TerminalView; import java.util.Arrays; import java.util.Collections; @@ -75,8 +76,7 @@ public float onScale(float scale) { @Override public void onSingleTapUp(MotionEvent e) { - InputMethodManager mgr = (InputMethodManager) mActivity.getSystemService(Context.INPUT_METHOD_SERVICE); - mgr.showSoftInput(mActivity.getTerminalView(), InputMethodManager.SHOW_IMPLICIT); + showSoftKeyboard(mActivity, mActivity.getTerminalView()); } @Override @@ -125,7 +125,7 @@ public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession currentSession } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) { mActivity.getDrawer().closeDrawers(); } else if (unicodeChar == 'k'/* keyboard */) { - toggleSoftKeyboard(mActivity); + onToggleSoftKeyboardRequest(); } else if (unicodeChar == 'm'/* menu */) { mActivity.getTerminalView().showContextMenu(); } else if (unicodeChar == 'r'/* rename */) { @@ -372,23 +372,83 @@ public static void toggleSoftKeyboard(Context context) { * check by passing {@code 0} as {@code flags}. * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=2022 */ - public static void showSoftKeyboard(Context context, TerminalView terminalView) { + public static void showSoftKeyboard(Context context, View view) { InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(terminalView, 0); + inputMethodManager.showSoftInput(view, 0); } - public void setSoftKeyboardState() { - // If soft keyboard is to disabled + public static void hideSoftKeyboard(Context context, View view) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + public static void disableSoftKeyboard(Activity activity, View view) { + hideSoftKeyboard(activity, view); + setDisableSoftKeyboardFlags(activity); + } + + public static void setDisableSoftKeyboardFlags(Activity activity) { + activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + public static void clearDisableSoftKeyboardFlags(Activity activity) { + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + public void setResizeTerminalViewForSoftKeyboardFlags() { + // TODO: The flag is deprecated for API 30 and WindowInset API should be used + // https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE + // https://medium.com/androiddevelopers/animating-your-keyboard-fb776a8fb66d + // https://stackoverflow.com/a/65194077/14686958 + mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + + /** + * Called when user requests the soft keyboard to be toggled via "KEYBOARD" toggle button in + * drawer or extra keys, or with ctrl+alt+k hardware keyboard shortcut. + */ + public void onToggleSoftKeyboardRequest() { + // If soft keyboard is disabled by user for Termux if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { - mActivity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { - mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - showSoftKeyboard(mActivity, mActivity.getTerminalView()); + clearDisableSoftKeyboardFlags(mActivity); + toggleSoftKeyboard(mActivity); } + } + + public void setSoftKeyboardState(boolean isStartup) { + // If soft keyboard is disabled by user for Termux + if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { + disableSoftKeyboard(mActivity, mActivity.getTerminalView()); + } else { + // Set flag to automatically push up TerminalView when keyboard is opened instead of showing over it + setResizeTerminalViewForSoftKeyboardFlags(); + + // Clear any previous flags to disable soft keyboard in case setting updated + clearDisableSoftKeyboardFlags(mActivity); + + // If soft keyboard is to be hidden on startup + if (isStartup && mActivity.getProperties().shouldSoftKeyboardBeHiddenOnStartup()) { + hideSoftKeyboard(mActivity, mActivity.getTerminalView()); + } else { + // Force show soft keyboard + showSoftKeyboard(mActivity, mActivity.getTerminalView()); + + mActivity.getTerminalView().setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean hasFocus) { + if (hasFocus) { + showSoftKeyboard(mActivity, mActivity.getTerminalView()); + } else { + hideSoftKeyboard(mActivity, mActivity.getTerminalView()); + } + } + }); - // If soft keyboard is to be hidden on startup - if (mActivity.getProperties().shouldSoftKeyboardBeHiddenOnStartup()) { - mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + // Request focus for TerminalView + mActivity.getTerminalView().requestFocus(); + } } } diff --git a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java index 06bdfe19a9..5e7adfb891 100644 --- a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java +++ b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java @@ -44,6 +44,7 @@ public Object instantiateItem(@NonNull ViewGroup collection, int position) { if (position == 0) { layout = inflater.inflate(R.layout.view_terminal_toolbar_extra_keys, collection, false); ExtraKeysView extraKeysView = (ExtraKeysView) layout; + extraKeysView.setTermuxTerminalViewClient(mActivity.getTermuxTerminalViewClient()); mActivity.setExtraKeysView(extraKeysView); extraKeysView.reload(mActivity.getProperties().getExtraKeysInfo()); diff --git a/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysView.java b/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysView.java index da4cbeaf6d..31afa07fed 100644 --- a/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysView.java +++ b/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysView.java @@ -23,12 +23,12 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.GridLayout; import android.widget.PopupWindow; import com.termux.R; +import com.termux.app.terminal.TermuxTerminalViewClient; import com.termux.view.TerminalView; import androidx.drawerlayout.widget.DrawerLayout; @@ -44,6 +44,8 @@ public final class ExtraKeysView extends GridLayout { private static final int INTERESTING_COLOR = 0xFF80DEEA; private static final int BUTTON_PRESSED_COLOR = 0xFF7F7F7F; + TermuxTerminalViewClient mTermuxTerminalViewClient; + public ExtraKeysView(Context context, AttributeSet attrs) { super(context, attrs); } @@ -82,8 +84,8 @@ public ExtraKeysView(Context context, AttributeSet attrs) { private void sendKey(View view, String keyName, boolean forceCtrlDown, boolean forceLeftAltDown) { TerminalView terminalView = view.findViewById(R.id.terminal_view); if ("KEYBOARD".equals(keyName)) { - InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(0, 0); + if(mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onToggleSoftKeyboardRequest(); } else if ("DRAWER".equals(keyName)) { DrawerLayout drawer = view.findViewById(R.id.drawer_layout); drawer.openDrawer(Gravity.LEFT); @@ -379,4 +381,8 @@ public void reload(ExtraKeysInfo infos) { } } + public void setTermuxTerminalViewClient(TermuxTerminalViewClient termuxTerminalViewClient) { + this.mTermuxTerminalViewClient = termuxTerminalViewClient; + } + } From 116b9b42d81e0733edd1d66ec80758c5969db844 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 9 May 2021 21:07:23 +0500 Subject: [PATCH 15/37] Bump compileSdkVersion (NOT targetSdkVersion) to 30 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index bcb6c60ce3..5db9519171 100644 --- a/gradle.properties +++ b/gradle.properties @@ -21,7 +21,7 @@ termuxVersionCode=111 minSdkVersion=24 targetSdkVersion=28 ndkVersion=22.1.7171670 -compileSdkVersion=29 +compileSdkVersion=30 markwonVersion=4.6.2 From 4ecea144bb4d3dd511fa551410f9a481ddd43b92 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 9 May 2021 21:08:54 +0500 Subject: [PATCH 16/37] Create KeyboardUtils --- .../terminal/TermuxTerminalViewClient.java | 94 ++++------------ termux-shared/build.gradle | 1 + .../com/termux/shared/view/KeyboardUtils.java | 101 ++++++++++++++++++ 3 files changed, 121 insertions(+), 75 deletions(-) create mode 100644 termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 57cb573280..2fe87e9207 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -1,14 +1,12 @@ package com.termux.app.terminal; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.inputmethodservice.InputMethodService; import android.media.AudioManager; import android.net.Uri; import android.text.TextUtils; @@ -17,8 +15,6 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; -import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; import android.widget.ListView; import android.widget.Toast; @@ -37,6 +33,7 @@ import com.termux.shared.logger.Logger; import com.termux.shared.markdown.MarkdownUtils; import com.termux.shared.termux.TermuxUtils; +import com.termux.shared.view.KeyboardUtils; import com.termux.terminal.KeyHandler; import com.termux.terminal.TerminalEmulator; import com.termux.terminal.TerminalSession; @@ -57,6 +54,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase { /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ boolean mVirtualControlKeyDown, mVirtualFnKeyDown; + private static final String LOG_TAG = "TermuxTerminalViewClient"; + public TermuxTerminalViewClient(TermuxActivity activity, TermuxTerminalSessionClient termuxTerminalSessionClient) { this.mActivity = activity; this.mTermuxTerminalSessionClient = termuxTerminalSessionClient; @@ -76,7 +75,8 @@ public float onScale(float scale) { @Override public void onSingleTapUp(MotionEvent e) { - showSoftKeyboard(mActivity, mActivity.getTerminalView()); + if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); } @Override @@ -341,67 +341,7 @@ public void changeFontSize(boolean increase) { } - /** - * Toggle the soft keyboard. The {@link InputMethodManager#SHOW_FORCED} is passed as - * {@code showFlags} so that keyboard is forcefully shown if it needs to be enabled. - * - * This is also important for soft keyboard to be shown when a hardware keyboard is attached, and - * user has disabled the {@code Show on-screen keyboard while hardware keyboard is attached} toggle - * in Android "Language and Input" settings but the current soft keyboard app overrides the - * default implementation of {@link InputMethodService#onEvaluateInputViewShown()} and returns - * {@code true}. - */ - public static void toggleSoftKeyboard(Context context) { - InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } - - /** - * Show the soft keyboard. The {@code 0} value is passed as {@code flags} so that keyboard is - * forcefully shown. - * - * This is also important for soft keyboard to be shown on app startup when a hardware keyboard - * is attached, and user has disabled the {@code Show on-screen keyboard while hardware keyboard - * is attached} toggle in Android "Language and Input" settings but the current soft keyboard app - * overrides the default implementation of {@link InputMethodService#onEvaluateInputViewShown()} - * and returns {@code true}. - * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=1751 - * - * Also check {@link InputMethodService#onShowInputRequested(int, boolean)} which must return - * {@code true}, which can be done by failing its {@code ((flags&InputMethod.SHOW_EXPLICIT) == 0)} - * check by passing {@code 0} as {@code flags}. - * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=2022 - */ - public static void showSoftKeyboard(Context context, View view) { - InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(view, 0); - } - - public static void hideSoftKeyboard(Context context, View view) { - InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - public static void disableSoftKeyboard(Activity activity, View view) { - hideSoftKeyboard(activity, view); - setDisableSoftKeyboardFlags(activity); - } - public static void setDisableSoftKeyboardFlags(Activity activity) { - activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } - - public static void clearDisableSoftKeyboardFlags(Activity activity) { - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); - } - - public void setResizeTerminalViewForSoftKeyboardFlags() { - // TODO: The flag is deprecated for API 30 and WindowInset API should be used - // https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE - // https://medium.com/androiddevelopers/animating-your-keyboard-fb776a8fb66d - // https://stackoverflow.com/a/65194077/14686958 - mActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } /** * Called when user requests the soft keyboard to be toggled via "KEYBOARD" toggle button in @@ -410,38 +350,42 @@ public void setResizeTerminalViewForSoftKeyboardFlags() { public void onToggleSoftKeyboardRequest() { // If soft keyboard is disabled by user for Termux if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { - disableSoftKeyboard(mActivity, mActivity.getTerminalView()); + KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { - clearDisableSoftKeyboardFlags(mActivity); - toggleSoftKeyboard(mActivity); + KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity); + KeyboardUtils.toggleSoftKeyboard(mActivity); } + } public void setSoftKeyboardState(boolean isStartup) { // If soft keyboard is disabled by user for Termux if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { - disableSoftKeyboard(mActivity, mActivity.getTerminalView()); + Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard"); + KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { // Set flag to automatically push up TerminalView when keyboard is opened instead of showing over it - setResizeTerminalViewForSoftKeyboardFlags(); + KeyboardUtils.setResizeTerminalViewForSoftKeyboardFlags(mActivity); // Clear any previous flags to disable soft keyboard in case setting updated - clearDisableSoftKeyboardFlags(mActivity); + KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity); // If soft keyboard is to be hidden on startup if (isStartup && mActivity.getProperties().shouldSoftKeyboardBeHiddenOnStartup()) { - hideSoftKeyboard(mActivity, mActivity.getTerminalView()); + Logger.logVerbose(LOG_TAG, "Hiding soft keyboard on startup"); + KeyboardUtils.hideSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { // Force show soft keyboard - showSoftKeyboard(mActivity, mActivity.getTerminalView()); + Logger.logVerbose(LOG_TAG, "Showing soft keyboard"); + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); mActivity.getTerminalView().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { if (hasFocus) { - showSoftKeyboard(mActivity, mActivity.getTerminalView()); + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { - hideSoftKeyboard(mActivity, mActivity.getTerminalView()); + KeyboardUtils.hideSoftKeyboard(mActivity, mActivity.getTerminalView()); } } }); diff --git a/termux-shared/build.gradle b/termux-shared/build.gradle index b34e407bbb..cc49fb91df 100644 --- a/termux-shared/build.gradle +++ b/termux-shared/build.gradle @@ -6,6 +6,7 @@ android { dependencies { implementation "androidx.annotation:annotation:1.2.0" + implementation "androidx.core:core:1.5.0-rc01" implementation "com.google.guava:guava:24.1-jre" implementation "io.noties.markwon:core:$markwonVersion" implementation "io.noties.markwon:ext-strikethrough:$markwonVersion" diff --git a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java new file mode 100644 index 0000000000..a71bb328e1 --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java @@ -0,0 +1,101 @@ +package com.termux.shared.view; + +import android.app.Activity; +import android.content.Context; +import android.inputmethodservice.InputMethodService; +import android.view.View; +import android.view.WindowInsets; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.core.view.WindowInsetsCompat; + +import com.termux.shared.logger.Logger; + +public class KeyboardUtils { + + private static final String LOG_TAG = "KeyboardUtils"; + + /** + * Toggle the soft keyboard. The {@link InputMethodManager#SHOW_FORCED} is passed as + * {@code showFlags} so that keyboard is forcefully shown if it needs to be enabled. + * + * This is also important for soft keyboard to be shown when a hardware keyboard is attached, and + * user has disabled the {@code Show on-screen keyboard while hardware keyboard is attached} toggle + * in Android "Language and Input" settings but the current soft keyboard app overrides the + * default implementation of {@link InputMethodService#onEvaluateInputViewShown()} and returns + * {@code true}. + */ + public static void toggleSoftKeyboard(Context context) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + + /** + * Show the soft keyboard. The {@code 0} value is passed as {@code flags} so that keyboard is + * forcefully shown. + * + * This is also important for soft keyboard to be shown on app startup when a hardware keyboard + * is attached, and user has disabled the {@code Show on-screen keyboard while hardware keyboard + * is attached} toggle in Android "Language and Input" settings but the current soft keyboard app + * overrides the default implementation of {@link InputMethodService#onEvaluateInputViewShown()} + * and returns {@code true}. + * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=1751 + * + * Also check {@link InputMethodService#onShowInputRequested(int, boolean)} which must return + * {@code true}, which can be done by failing its {@code ((flags&InputMethod.SHOW_EXPLICIT) == 0)} + * check by passing {@code 0} as {@code flags}. + * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=2022 + */ + public static void showSoftKeyboard(Context context, View view) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(view, 0); + } + + public static void hideSoftKeyboard(Context context, View view) { + InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + public static void disableSoftKeyboard(Activity activity, View view) { + hideSoftKeyboard(activity, view); + setDisableSoftKeyboardFlags(activity); + } + + public static void setDisableSoftKeyboardFlags(Activity activity) { + activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + public static void clearDisableSoftKeyboardFlags(Activity activity) { + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + } + + public static boolean areDisableSoftKeyboardFlagsSet(Activity activity) { + return (activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0; + } + + public static void setResizeTerminalViewForSoftKeyboardFlags(Activity activity) { + // TODO: The flag is deprecated for API 30 and WindowInset API should be used + // https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE + // https://medium.com/androiddevelopers/animating-your-keyboard-fb776a8fb66d + // https://stackoverflow.com/a/65194077/14686958 + activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + + /** Check if keyboard visible. Does not work on android 7 but does on android 11 avd. */ + public static boolean isSoftKeyboardVisible(Activity activity) { + WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); + + if (insets != null) { + WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets); + if (insetsCompat != null && insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { + Logger.logVerbose(LOG_TAG, "Keyboard visible"); + return true; + } + } + + Logger.logVerbose(LOG_TAG, "Keyboard not visible"); + return false; + } + +} From fe8c3ba216d2fc145eb33707accb4abfad87125d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 10 May 2021 05:21:54 +0500 Subject: [PATCH 17/37] Update KeyboardUtils will null checks and add setSoftKeyboardVisibility() --- .../com/termux/shared/view/KeyboardUtils.java | 71 +++++++++++++------ 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java index a71bb328e1..e6b713a5a9 100644 --- a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java @@ -8,6 +8,7 @@ import android.view.WindowManager; import android.view.inputmethod.InputMethodManager; +import androidx.annotation.NonNull; import androidx.core.view.WindowInsetsCompat; import com.termux.shared.logger.Logger; @@ -16,6 +17,15 @@ public class KeyboardUtils { private static final String LOG_TAG = "KeyboardUtils"; + public static void setSoftKeyboardVisibility(@NonNull final Runnable showSoftKeyboardRunnable, final Activity activity, final View view, final boolean visible) { + if (visible) { + view.postDelayed(showSoftKeyboardRunnable, 1000); + } else { + view.removeCallbacks(showSoftKeyboardRunnable); + hideSoftKeyboard(activity, view); + } + } + /** * Toggle the soft keyboard. The {@link InputMethodManager#SHOW_FORCED} is passed as * {@code showFlags} so that keyboard is forcefully shown if it needs to be enabled. @@ -26,9 +36,11 @@ public class KeyboardUtils { * default implementation of {@link InputMethodService#onEvaluateInputViewShown()} and returns * {@code true}. */ - public static void toggleSoftKeyboard(Context context) { + public static void toggleSoftKeyboard(final Context context) { + if (context == null) return; InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + if (inputMethodManager != null) + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } /** @@ -47,50 +59,65 @@ public static void toggleSoftKeyboard(Context context) { * check by passing {@code 0} as {@code flags}. * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=2022 */ - public static void showSoftKeyboard(Context context, View view) { + public static void showSoftKeyboard(final Context context, final View view) { + if (context == null || view == null) return; InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.showSoftInput(view, 0); + if (inputMethodManager != null) + inputMethodManager.showSoftInput(view, 0); } - public static void hideSoftKeyboard(Context context, View view) { + public static void hideSoftKeyboard(final Context context, final View view) { + if (context == null || view == null) return; InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + if (inputMethodManager != null) + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } - public static void disableSoftKeyboard(Activity activity, View view) { + public static void disableSoftKeyboard(final Activity activity, final View view) { + if (activity == null || view == null) return; hideSoftKeyboard(activity, view); setDisableSoftKeyboardFlags(activity); } - public static void setDisableSoftKeyboardFlags(Activity activity) { - activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + public static void setDisableSoftKeyboardFlags(final Activity activity) { + if (activity != null && activity.getWindow() != null) + activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); } - public static void clearDisableSoftKeyboardFlags(Activity activity) { - activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + public static void clearDisableSoftKeyboardFlags(final Activity activity) { + if (activity != null && activity.getWindow() != null) + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); } - public static boolean areDisableSoftKeyboardFlagsSet(Activity activity) { + public static boolean areDisableSoftKeyboardFlagsSet(final Activity activity) { + if (activity == null || activity.getWindow() == null) return false; return (activity.getWindow().getAttributes().flags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0; } - public static void setResizeTerminalViewForSoftKeyboardFlags(Activity activity) { + public static void setSoftKeyboardAlwaysHiddenFlags(final Activity activity) { + if (activity != null && activity.getWindow() != null) + activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + } + + public static void setResizeTerminalViewForSoftKeyboardFlags(final Activity activity) { // TODO: The flag is deprecated for API 30 and WindowInset API should be used // https://developer.android.com/reference/android/view/WindowManager.LayoutParams#SOFT_INPUT_ADJUST_RESIZE // https://medium.com/androiddevelopers/animating-your-keyboard-fb776a8fb66d // https://stackoverflow.com/a/65194077/14686958 - activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + if (activity != null && activity.getWindow() != null) + activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } /** Check if keyboard visible. Does not work on android 7 but does on android 11 avd. */ - public static boolean isSoftKeyboardVisible(Activity activity) { - WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); - - if (insets != null) { - WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets); - if (insetsCompat != null && insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { - Logger.logVerbose(LOG_TAG, "Keyboard visible"); - return true; + public static boolean isSoftKeyboardVisible(final Activity activity) { + if (activity != null && activity.getWindow() != null) { + WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); + if (insets != null) { + WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets); + if (insetsCompat != null && insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { + Logger.logVerbose(LOG_TAG, "Keyboard visible"); + return true; + } } } From e4e638bd318baf6ad344c898c3baa9093975e53f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 10 May 2021 06:03:29 +0500 Subject: [PATCH 18/37] Allow users to enable/disable keyboard instead of just show/hide with keyboard toggle buttons This `soft-keyboard-toggle-behaviour` key can be used to change the behaviour. The default behaviour is `show/hide`. The user can set the value to `enable/disable` in `termux.properties` file to change default behaviour of keyboard toggle buttons to enable/disable. In this mode, tapping the keyboard toggle button will disable (and hide) the keyboard and tapping on the terminal view will not open the keybaord automatically, until the keyboard toggle button is pressed again manually. This applies to split screen and floating keyboard as well. The keyboard can also be enabled from Settings -> Keyboard I/O -> Soft Keyboard toggle. Running `termux-reload-settings` command will also update the behaviour instantaneously if changed. Fixed issue where "hide-soft-keyboard-on-startup" property wouldn't work if Termux app was switched back from another app. Fixes #1098 Fixed issue where soft keyboard may not show on startup on some devices but it still may fail sometimes. The `TermuxPropertyConstants` class has been updated to `v0.7.0`. Check its Changelog sections for info on changes. --- .../java/com/termux/app/TermuxActivity.java | 4 +- .../terminal/TermuxTerminalViewClient.java | 61 +++++++++++++------ .../properties/TermuxPropertyConstants.java | 17 +++++- .../properties/TermuxSharedProperties.java | 16 +++++ .../com/termux/shared/view/KeyboardUtils.java | 4 +- 5 files changed, 80 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 1381998e9d..3942ea39da 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -236,7 +236,7 @@ public void onStart() { public void onResume() { super.onResume(); - mTermuxTerminalViewClient.setSoftKeyboardState(true); + mTermuxTerminalViewClient.setSoftKeyboardState(true, false); } /** @@ -768,7 +768,7 @@ private void reloadTermuxActivityStyling() { setTerminalToolbarHeight(); - mTermuxTerminalViewClient.setSoftKeyboardState(true); + mTermuxTerminalViewClient.setSoftKeyboardState(false, true); // To change the activity and drawer theme, activity needs to be recreated. // But this will destroy the activity, and will call the onCreate() again. diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 2fe87e9207..93e387d16f 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -54,6 +54,8 @@ public class TermuxTerminalViewClient extends TermuxTerminalViewClientBase { /** Keeping track of the special keys acting as Ctrl and Fn for the soft keyboard and other hardware keys. */ boolean mVirtualControlKeyDown, mVirtualFnKeyDown; + private Runnable mShowSoftKeyboardRunnable; + private static final String LOG_TAG = "TermuxTerminalViewClient"; public TermuxTerminalViewClient(TermuxActivity activity, TermuxTerminalSessionClient termuxTerminalSessionClient) { @@ -77,6 +79,8 @@ public float onScale(float scale) { public void onSingleTapUp(MotionEvent e) { if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); + else + Logger.logVerbose(LOG_TAG, "Not showing keyboard onSingleTapUp since its disabled"); } @Override @@ -342,23 +346,40 @@ public void changeFontSize(boolean increase) { - /** * Called when user requests the soft keyboard to be toggled via "KEYBOARD" toggle button in * drawer or extra keys, or with ctrl+alt+k hardware keyboard shortcut. */ public void onToggleSoftKeyboardRequest() { - // If soft keyboard is disabled by user for Termux - if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { - KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); - } else { - KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity); - KeyboardUtils.toggleSoftKeyboard(mActivity); + // If soft keyboard toggle behaviour is enable/disabled + if (mActivity.getProperties().shouldEnableDisableSoftKeyboardOnToggle()) { + // If soft keyboard is visible + if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) { + Logger.logVerbose(LOG_TAG, "Disabling soft keyboard on toggle"); + mActivity.getPreferences().setSoftKeyboardEnabled(false); + KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); + } else { + Logger.logVerbose(LOG_TAG, "Enabling soft keyboard on toggle"); + mActivity.getPreferences().setSoftKeyboardEnabled(true); + KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity); + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); + } + } + // If soft keyboard toggle behaviour is show/hide + else { + // If soft keyboard is disabled by user for Termux + if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { + Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard on toggle"); + KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); + } else { + Logger.logVerbose(LOG_TAG, "Showing/Hiding soft keyboard on toggle"); + KeyboardUtils.clearDisableSoftKeyboardFlags(mActivity); + KeyboardUtils.toggleSoftKeyboard(mActivity); + } } - } - public void setSoftKeyboardState(boolean isStartup) { + public void setSoftKeyboardState(boolean isStartup, boolean isReloadTermuxProperties) { // If soft keyboard is disabled by user for Termux if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard"); @@ -374,19 +395,25 @@ public void setSoftKeyboardState(boolean isStartup) { if (isStartup && mActivity.getProperties().shouldSoftKeyboardBeHiddenOnStartup()) { Logger.logVerbose(LOG_TAG, "Hiding soft keyboard on startup"); KeyboardUtils.hideSoftKeyboard(mActivity, mActivity.getTerminalView()); + // Required to keep keyboard hidden when Termux app is switched back from another app + KeyboardUtils.setSoftKeyboardAlwaysHiddenFlags(mActivity); } else { - // Force show soft keyboard - Logger.logVerbose(LOG_TAG, "Showing soft keyboard"); - KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); + // Do not force show soft keyboard if termux-reload-settings command was run with hardware keyboard + if (isReloadTermuxProperties) + return; + + if (mShowSoftKeyboardRunnable == null) { + mShowSoftKeyboardRunnable = () -> { + Logger.logVerbose(LOG_TAG, "Showing soft keyboard on focus change"); + KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); + }; + } mActivity.getTerminalView().setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View view, boolean hasFocus) { - if (hasFocus) { - KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); - } else { - KeyboardUtils.hideSoftKeyboard(mActivity, mActivity.getTerminalView()); - } + // Force show soft keyboard if TerminalView has focus and close it if it doesn't + KeyboardUtils.setSoftKeyboardVisibility(mShowSoftKeyboardRunnable, mActivity, mActivity.getTerminalView(), hasFocus); } }); diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java index 588db5d24e..3e1c62182b 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java @@ -10,7 +10,7 @@ import java.util.Set; /* - * Version: v0.6.0 + * Version: v0.7.0 * * Changelog * @@ -33,6 +33,9 @@ * * - 0.6.0 (2021-04-07) * - Updated javadocs. + * + * - 0.7.0 (2021-05-09) + * - Add `*SOFT_KEYBOARD_TOGGLE_BEHAVIOUR*`. */ /** @@ -166,6 +169,15 @@ public final class TermuxPropertyConstants { + /** Defines the key for whether toggle soft keyboard request will show/hide or enable/disable keyboard */ + public static final String SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = "soft-keyboard-toggle-behaviour"; // Default: "soft-keyboard-toggle-behaviour" + + public static final String IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE = "show/hide"; + public static final String IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE = "enable/disable"; + public static final String DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE; + + + /** Defines the set for keys loaded by termux @@ -198,7 +210,8 @@ public final class TermuxPropertyConstants { // String KEY_DEFAULT_WORKING_DIRECTORY, KEY_EXTRA_KEYS, - KEY_EXTRA_KEYS_STYLE + KEY_EXTRA_KEYS_STYLE, + SOFT_KEYBOARD_TOGGLE_BEHAVIOUR )); /** Defines the set for keys loaded by termux that have default boolean behaviour diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java index 124fe6ae09..617e8208e0 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java @@ -211,6 +211,8 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St return (String) getExtraKeysInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE: return (String) getExtraKeysStyleInternalPropertyValueFromValue(value); + case TermuxPropertyConstants.SOFT_KEYBOARD_TOGGLE_BEHAVIOUR: + return (String) getSoftKeyboardToggleBehaviourInternalPropertyValueFromValue(value); default: // default boolean behaviour if (TermuxPropertyConstants.TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key)) @@ -373,6 +375,16 @@ public static String getExtraKeysStyleInternalPropertyValueFromValue(String valu return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE); } + /** + * Returns the value itself if it is not {@code null}, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR}. + * + * @param value {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static String getSoftKeyboardToggleBehaviourInternalPropertyValueFromValue(String value) { + return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR); + } + @@ -421,6 +433,10 @@ public String getDefaultWorkingDirectory() { return (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY, true); } + public boolean shouldEnableDisableSoftKeyboardOnToggle() { + return (boolean) TermuxPropertyConstants.IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE.equals(getInternalPropertyValue(TermuxPropertyConstants.SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, true)); + } + diff --git a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java index e6b713a5a9..3891086dec 100644 --- a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java @@ -19,7 +19,9 @@ public class KeyboardUtils { public static void setSoftKeyboardVisibility(@NonNull final Runnable showSoftKeyboardRunnable, final Activity activity, final View view, final boolean visible) { if (visible) { - view.postDelayed(showSoftKeyboardRunnable, 1000); + // A Runnable with a delay is used, otherwise soft keyboard may not automatically open + // on some devices, but still may fail + view.postDelayed(showSoftKeyboardRunnable, 500); } else { view.removeCallbacks(showSoftKeyboardRunnable); hideSoftKeyboard(activity, view); From e6dac9335236f58d567c6e1aa93021b4fb6174de Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 10 May 2021 07:10:38 +0500 Subject: [PATCH 19/37] Preserve the termux.properties literal string values internally that were being converted to boolean on load time The `TermuxPropertyConstants` class has been updated to `v0.8.0`. Check its Changelog sections for info on changes. --- .../properties/TermuxPropertyConstants.java | 76 ++++++++++++------ .../properties/TermuxSharedProperties.java | 78 +++++++++---------- 2 files changed, 90 insertions(+), 64 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java index 3e1c62182b..bedc27f3c9 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java @@ -10,7 +10,7 @@ import java.util.Set; /* - * Version: v0.7.0 + * Version: v0.8.0 * * Changelog * @@ -36,6 +36,11 @@ * * - 0.7.0 (2021-05-09) * - Add `*SOFT_KEYBOARD_TOGGLE_BEHAVIOUR*`. + * + * - 0.8.0 (2021-05-10) + * - Change the `KEY_USE_BACK_KEY_AS_ESCAPE_KEY` and `KEY_VIRTUAL_VOLUME_KEYS_DISABLED` booleans + * to `KEY_BACK_KEY_BEHAVIOUR` and `KEY_VOLUME_KEYS_BEHAVIOUR` String internal values. + * - Renamed `SOFT_KEYBOARD_TOGGLE_BEHAVIOUR` to `KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR`. */ /** @@ -51,13 +56,7 @@ */ public final class TermuxPropertyConstants { - /** Defines the key for whether to use back key as the escape key */ - public static final String KEY_USE_BACK_KEY_AS_ESCAPE_KEY = "back-key"; // Default: "back-key" - - public static final String VALUE_BACK_KEY_BEHAVIOUR_BACK = "back"; - public static final String VALUE_BACK_KEY_BEHAVIOUR_ESCAPE = "escape"; - - + /* boolean */ /** Defines the key for whether to enforce character based input to fix the issue where for some devices like Samsung, the letters might not appear until enter is pressed */ public static final String KEY_ENFORCE_CHAR_BASED_INPUT = "enforce-char-based-input"; // Default: "enforce-char-based-input" @@ -89,13 +88,9 @@ public final class TermuxPropertyConstants { - /** Defines the key for whether virtual volume keys are disabled */ - public static final String KEY_VIRTUAL_VOLUME_KEYS_DISABLED = "volume-keys"; // Default: "volume-keys" - - public static final String VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME = "volume"; - public static final String VALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL = "virtual"; + /* int */ /** Defines the key for the bell behaviour */ public static final String KEY_BELL_BEHAVIOUR = "bell-character"; // Default: "bell-character" @@ -120,7 +115,11 @@ public final class TermuxPropertyConstants { - /** Defines the key for the bell behaviour */ + + + /* float */ + + /** Defines the key for the terminal toolbar height */ public static final String KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR = "terminal-toolbar-height"; // Default: "terminal-toolbar-height" public static final float IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN = 0.4f; public static final float IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX = 3; @@ -128,6 +127,10 @@ public final class TermuxPropertyConstants { + + + /* Integer */ + /** Defines the key for create session shortcut */ public static final String KEY_SHORTCUT_CREATE_SESSION = "shortcut.create-session"; // Default: "shortcut.create-session" /** Defines the key for next session shortcut */ @@ -153,6 +156,19 @@ public final class TermuxPropertyConstants { + + + /* String */ + + /** Defines the key for whether back key will behave as escape key or literal back key */ + public static final String KEY_BACK_KEY_BEHAVIOUR = "back-key"; // Default: "back-key" + + public static final String IVALUE_BACK_KEY_BEHAVIOUR_BACK = "back"; + public static final String IVALUE_BACK_KEY_BEHAVIOUR_ESCAPE = "escape"; + public static final String DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR = IVALUE_BACK_KEY_BEHAVIOUR_BACK; + + + /** Defines the key for the default working directory */ public static final String KEY_DEFAULT_WORKING_DIRECTORY = "default-working-directory"; // Default: "default-working-directory" /** Defines the default working directory */ @@ -162,15 +178,16 @@ public final class TermuxPropertyConstants { /** Defines the key for extra keys */ public static final String KEY_EXTRA_KEYS = "extra-keys"; // Default: "extra-keys" + public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]"; + /** Defines the key for extra keys style */ public static final String KEY_EXTRA_KEYS_STYLE = "extra-keys-style"; // Default: "extra-keys-style" - public static final String DEFAULT_IVALUE_EXTRA_KEYS = "[[ESC, TAB, CTRL, ALT, {key: '-', popup: '|'}, DOWN, UP]]"; public static final String DEFAULT_IVALUE_EXTRA_KEYS_STYLE = "default"; /** Defines the key for whether toggle soft keyboard request will show/hide or enable/disable keyboard */ - public static final String SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = "soft-keyboard-toggle-behaviour"; // Default: "soft-keyboard-toggle-behaviour" + public static final String KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = "soft-keyboard-toggle-behaviour"; // Default: "soft-keyboard-toggle-behaviour" public static final String IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE = "show/hide"; public static final String IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE = "enable/disable"; @@ -178,41 +195,50 @@ public final class TermuxPropertyConstants { + /** Defines the key for whether volume keys will behave as virtual or literal volume keys */ + public static final String KEY_VOLUME_KEYS_BEHAVIOUR = "volume-keys"; // Default: "volume-keys" + + public static final String IVALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL = "virtual"; + public static final String IVALUE_VOLUME_KEY_BEHAVIOUR_VOLUME = "volume"; + public static final String DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR = IVALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL; + + + /** Defines the set for keys loaded by termux * Setting this to {@code null} will make {@link SharedProperties} throw an exception. * */ public static final Set TERMUX_PROPERTIES_LIST = new HashSet<>(Arrays.asList( - // boolean + /* boolean */ KEY_ENFORCE_CHAR_BASED_INPUT, KEY_HIDE_SOFT_KEYBOARD_ON_STARTUP, - KEY_USE_BACK_KEY_AS_ESCAPE_KEY, KEY_USE_BLACK_UI, KEY_USE_CTRL_SPACE_WORKAROUND, KEY_USE_FULLSCREEN, KEY_USE_FULLSCREEN_WORKAROUND, - KEY_VIRTUAL_VOLUME_KEYS_DISABLED, TermuxConstants.PROP_ALLOW_EXTERNAL_APPS, - // int + /* int */ KEY_BELL_BEHAVIOUR, - // float + /* float */ KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, - // Integer + /* Integer */ KEY_SHORTCUT_CREATE_SESSION, KEY_SHORTCUT_NEXT_SESSION, KEY_SHORTCUT_PREVIOUS_SESSION, KEY_SHORTCUT_RENAME_SESSION, - // String + /* String */ + KEY_BACK_KEY_BEHAVIOUR, KEY_DEFAULT_WORKING_DIRECTORY, KEY_EXTRA_KEYS, KEY_EXTRA_KEYS_STYLE, - SOFT_KEYBOARD_TOGGLE_BEHAVIOUR - )); + KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, + KEY_VOLUME_KEYS_BEHAVIOUR + )); /** Defines the set for keys loaded by termux that have default boolean behaviour * "true" -> true diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java index 617e8208e0..4d294d108d 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java @@ -181,38 +181,39 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St - If the value is not null and does exist in MAP_*, then internal value returned by map will be used. */ switch (key) { - // boolean - case TermuxPropertyConstants.KEY_USE_BACK_KEY_AS_ESCAPE_KEY: - return (boolean) getUseBackKeyAsEscapeKeyInternalPropertyValueFromValue(value); + /* boolean */ case TermuxPropertyConstants.KEY_USE_BLACK_UI: return (boolean) getUseBlackUIInternalPropertyValueFromValue(context, value); - case TermuxPropertyConstants.KEY_VIRTUAL_VOLUME_KEYS_DISABLED: - return (boolean) getVolumeKeysDisabledInternalPropertyValueFromValue(value); - // int + /* int */ case TermuxPropertyConstants.KEY_BELL_BEHAVIOUR: return (int) getBellBehaviourInternalPropertyValueFromValue(value); - // float + /* float */ case TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR: return (float) getTerminalToolbarHeightScaleFactorInternalPropertyValueFromValue(value); - // Integer (may be null) + /* Integer (may be null) */ case TermuxPropertyConstants.KEY_SHORTCUT_CREATE_SESSION: case TermuxPropertyConstants.KEY_SHORTCUT_NEXT_SESSION: case TermuxPropertyConstants.KEY_SHORTCUT_PREVIOUS_SESSION: case TermuxPropertyConstants.KEY_SHORTCUT_RENAME_SESSION: return (Integer) getCodePointForSessionShortcuts(key, value); - // String (may be null) + /* String (may be null) */ + case TermuxPropertyConstants.KEY_BACK_KEY_BEHAVIOUR: + return (String) getBackKeyBehaviourInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY: return (String) getDefaultWorkingDirectoryInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_EXTRA_KEYS: return (String) getExtraKeysInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE: return (String) getExtraKeysStyleInternalPropertyValueFromValue(value); - case TermuxPropertyConstants.SOFT_KEYBOARD_TOGGLE_BEHAVIOUR: + case TermuxPropertyConstants.KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR: return (String) getSoftKeyboardToggleBehaviourInternalPropertyValueFromValue(value); + case TermuxPropertyConstants.KEY_VOLUME_KEYS_BEHAVIOUR: + return (String) getVolumeKeysBehaviourInternalPropertyValueFromValue(value); + default: // default boolean behaviour if (TermuxPropertyConstants.TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key)) @@ -230,15 +231,7 @@ else if (TermuxPropertyConstants.TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROP - /** - * Returns {@code true} if value is not {@code null} and equals {@link TermuxPropertyConstants#VALUE_BACK_KEY_BEHAVIOUR_ESCAPE}, otherwise false. - * - * @param value The {@link String} value to convert. - * @return Returns the internal value for value. - */ - public static boolean getUseBackKeyAsEscapeKeyInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.VALUE_BACK_KEY_BEHAVIOUR_BACK).equals(TermuxPropertyConstants.VALUE_BACK_KEY_BEHAVIOUR_ESCAPE); - } + /** * Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively regardless of case. @@ -252,17 +245,6 @@ public static boolean getUseBlackUIInternalPropertyValueFromValue(Context contex return SharedProperties.getBooleanValueForStringValue(value, nightMode == Configuration.UI_MODE_NIGHT_YES); } - /** - * Returns {@code true} if value is not {@code null} and equals - * {@link TermuxPropertyConstants#VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME}, otherwise {@code false}. - * - * @param value The {@link String} value to convert. - * @return Returns the internal value for value. - */ - public static boolean getVolumeKeysDisabledInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.VALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL).equals(TermuxPropertyConstants.VALUE_VOLUME_KEY_BEHAVIOUR_VOLUME); - } - /** * Returns the internal value after mapping it based on * {@code TermuxPropertyConstants#MAP_BELL_BEHAVIOUR} if the value is not {@code null} @@ -336,6 +318,16 @@ public static Integer getCodePointForSessionShortcuts(String key, String value) return codePoint; } + /** + * Returns the value itself if it is not {@code null}, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR}. + * + * @param value {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static String getBackKeyBehaviourInternalPropertyValueFromValue(String value) { + return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR); + } + /** * Returns the path itself if a directory exists at it and is readable, otherwise returns * {@link TermuxPropertyConstants#DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY}. @@ -385,6 +377,15 @@ public static String getSoftKeyboardToggleBehaviourInternalPropertyValueFromValu return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR); } + /** + * Returns the value itself if it is not {@code null}, otherwise returns {@link TermuxPropertyConstants#DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR}. + * + * @param value {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static String getVolumeKeysBehaviourInternalPropertyValueFromValue(String value) { + return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR); + } @@ -397,10 +398,6 @@ public boolean shouldSoftKeyboardBeHiddenOnStartup() { return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_HIDE_SOFT_KEYBOARD_ON_STARTUP, true); } - public boolean isBackKeyTheEscapeKey() { - return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_BACK_KEY_AS_ESCAPE_KEY, true); - } - public boolean isUsingBlackUI() { return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_BLACK_UI, true); } @@ -417,10 +414,6 @@ public boolean isUsingFullScreenWorkAround() { return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_USE_FULLSCREEN_WORKAROUND, true); } - public boolean areVirtualVolumeKeysDisabled() { - return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_VIRTUAL_VOLUME_KEYS_DISABLED, true); - } - public int getBellBehaviour() { return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_BELL_BEHAVIOUR, true); } @@ -429,14 +422,21 @@ public float getTerminalToolbarHeightScaleFactor() { return rangeTerminalToolbarHeightScaleFactorValue((float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true)); } + public boolean isBackKeyTheEscapeKey() { + return (boolean) TermuxPropertyConstants.IVALUE_BACK_KEY_BEHAVIOUR_ESCAPE.equals(getInternalPropertyValue(TermuxPropertyConstants.KEY_BACK_KEY_BEHAVIOUR, true)); + } + public String getDefaultWorkingDirectory() { return (String) getInternalPropertyValue(TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY, true); } public boolean shouldEnableDisableSoftKeyboardOnToggle() { - return (boolean) TermuxPropertyConstants.IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE.equals(getInternalPropertyValue(TermuxPropertyConstants.SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, true)); + return (boolean) TermuxPropertyConstants.IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE.equals(getInternalPropertyValue(TermuxPropertyConstants.KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, true)); } + public boolean areVirtualVolumeKeysDisabled() { + return (boolean) TermuxPropertyConstants.IVALUE_VOLUME_KEY_BEHAVIOUR_VOLUME.equals(getInternalPropertyValue(TermuxPropertyConstants.KEY_VOLUME_KEYS_BEHAVIOUR, true)); + } From 90c9a7b3bc79972cb3765f7bad2681d7dd58bf1b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 12 May 2021 21:56:23 +0500 Subject: [PATCH 20/37] Allow users to disable soft keyboard automatically if hardware keyboard is connected Users can enable this behaviour by enabling the `Termux Settings` -> `Keyboard I/O` -> `Soft Keyboard Only If No Hardware` toggle. Currently, for this case, soft keyboard will be disabled on Termux app startup and when switching back from another app. Soft keyboard can be temporarily enabled in show/hide soft keyboard toggle behaviour with keyboard toggle buttons and will continue to work when tapping on terminal view for opening and back button for closing, until Termux app is switched to another app. After returning back, keyboard will be disabled until toggle is pressed again. This also may help for the Lineage OS bug where blank space is shown where soft keyboard should be if "Show soft keyboard" toggle in "Language and Input" is disabled. Check KeyboardUtils.shouldSoftKeyboardBeDisabled() and https://github.com/termux/termux-app/issues/1995#issuecomment-837080079 for details. The `TermuxPreferenceConstants` class has been updated to `v0.10.0`. Check its Changelog sections for info on changes. --- .../TerminalIOPreferencesFragment.java | 5 ++ .../terminal/TermuxTerminalViewClient.java | 8 +- app/src/main/res/values/strings.xml | 7 +- .../main/res/xml/terminal_io_preferences.xml | 6 ++ .../TermuxAppSharedPreferences.java | 8 ++ .../TermuxPreferenceConstants.java | 13 ++- .../com/termux/shared/view/KeyboardUtils.java | 81 +++++++++++++++++-- 7 files changed, 115 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java index ef0a56c2c8..d9500f841d 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java @@ -53,6 +53,9 @@ public void putBoolean(String key, boolean value) { case "soft_keyboard_enabled": mPreferences.setSoftKeyboardEnabled(value); break; + case "soft_keyboard_enabled_only_if_no_hardware": + mPreferences.setSoftKeyboardEnabledOnlyIfNoHardware(value); + break; default: break; } @@ -63,6 +66,8 @@ public boolean getBoolean(String key, boolean defValue) { switch (key) { case "soft_keyboard_enabled": return mPreferences.getSoftKeyboardEnabled(); + case "soft_keyboard_enabled_only_if_no_hardware": + return mPreferences.getSoftKeyboardEnabledOnlyIfNoHardware(); default: return false; } diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 93e387d16f..18a0d39b03 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -80,7 +80,7 @@ public void onSingleTapUp(MotionEvent e) { if (!KeyboardUtils.areDisableSoftKeyboardFlagsSet(mActivity)) KeyboardUtils.showSoftKeyboard(mActivity, mActivity.getTerminalView()); else - Logger.logVerbose(LOG_TAG, "Not showing keyboard onSingleTapUp since its disabled"); + Logger.logVerbose(LOG_TAG, "Not showing soft keyboard onSingleTapUp since its disabled"); } @Override @@ -380,8 +380,10 @@ public void onToggleSoftKeyboardRequest() { } public void setSoftKeyboardState(boolean isStartup, boolean isReloadTermuxProperties) { - // If soft keyboard is disabled by user for Termux - if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { + // If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info) + if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity, + mActivity.getPreferences().getSoftKeyboardEnabled(), + mActivity.getPreferences().getSoftKeyboardEnabledOnlyIfNoHardware())) { Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard"); KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0cebd0b560..b12521886c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -151,8 +151,13 @@ Keyboard - Soft Keyboard + Soft Keyboard Enabled Soft keyboard will be disabled. Soft keyboard will be enabled. (Default) + + Soft Keyboard If No Hardware + Soft keyboard will be enabled even if hardware keyboard is connected. (Default) + Soft keyboard will be enabled only if no hardware keyboard is connected. + diff --git a/app/src/main/res/xml/terminal_io_preferences.xml b/app/src/main/res/xml/terminal_io_preferences.xml index d20e7c58bf..6aecbf482c 100644 --- a/app/src/main/res/xml/terminal_io_preferences.xml +++ b/app/src/main/res/xml/terminal_io_preferences.xml @@ -10,6 +10,12 @@ app:summaryOn="@string/soft_keyboard_on" app:title="@string/soft_keyboard_title" /> + + diff --git a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java index 086e50d75e..7c6656cdb3 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java @@ -62,6 +62,14 @@ public void setSoftKeyboardEnabled(boolean value) { SharedPreferenceUtils.setBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED, value, false); } + public boolean getSoftKeyboardEnabledOnlyIfNoHardware() { + return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE, TERMUX_APP.DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE); + } + + public void setSoftKeyboardEnabledOnlyIfNoHardware(boolean value) { + SharedPreferenceUtils.setBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE, value, false); + } + public boolean getKeepScreenOn() { diff --git a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxPreferenceConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxPreferenceConstants.java index 633b7c3441..efec9d9863 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxPreferenceConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxPreferenceConstants.java @@ -1,7 +1,7 @@ package com.termux.shared.settings.preferences; /* - * Version: v0.9.0 + * Version: v0.10.0 * * Changelog * @@ -40,6 +40,10 @@ * * - 0.9.0 (2021-04-07) * - Updated javadocs. + * + * - 0.10.0 (2021-05-12) + * - Added following to `TERMUX_APP`: + * `KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE` and `DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE`. */ /** @@ -70,6 +74,13 @@ public static final class TERMUX_APP { public static final String KEY_SOFT_KEYBOARD_ENABLED = "soft_keyboard_enabled"; public static final boolean DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED = true; + /** + * Defines the key for whether the soft keyboard will be enabled only if no hardware keyboard + * attached, for cases where users want to use a hardware keyboard instead. + */ + public static final String KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE = "soft_keyboard_enabled_only_if_no_hardware"; + public static final boolean DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE = false; + /** * Defines the key for whether to always keep screen on. diff --git a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java index 3891086dec..a54b4fa9f9 100644 --- a/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/view/KeyboardUtils.java @@ -2,6 +2,7 @@ import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.inputmethodservice.InputMethodService; import android.view.View; import android.view.WindowInsets; @@ -32,8 +33,8 @@ public static void setSoftKeyboardVisibility(@NonNull final Runnable showSoftKey * Toggle the soft keyboard. The {@link InputMethodManager#SHOW_FORCED} is passed as * {@code showFlags} so that keyboard is forcefully shown if it needs to be enabled. * - * This is also important for soft keyboard to be shown when a hardware keyboard is attached, and - * user has disabled the {@code Show on-screen keyboard while hardware keyboard is attached} toggle + * This is also important for soft keyboard to be shown when a hardware keyboard is connected, and + * user has disabled the {@code Show on-screen keyboard while hardware keyboard is connected} toggle * in Android "Language and Input" settings but the current soft keyboard app overrides the * default implementation of {@link InputMethodService#onEvaluateInputViewShown()} and returns * {@code true}. @@ -50,8 +51,8 @@ public static void toggleSoftKeyboard(final Context context) { * forcefully shown. * * This is also important for soft keyboard to be shown on app startup when a hardware keyboard - * is attached, and user has disabled the {@code Show on-screen keyboard while hardware keyboard - * is attached} toggle in Android "Language and Input" settings but the current soft keyboard app + * is connected, and user has disabled the {@code Show on-screen keyboard while hardware keyboard + * is connected} toggle in Android "Language and Input" settings but the current soft keyboard app * overrides the default implementation of {@link InputMethodService#onEvaluateInputViewShown()} * and returns {@code true}. * https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/core/java/android/inputmethodservice/InputMethodService.java;l=1751 @@ -110,21 +111,85 @@ public static void setResizeTerminalViewForSoftKeyboardFlags(final Activity acti activity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } - /** Check if keyboard visible. Does not work on android 7 but does on android 11 avd. */ + /** + * Check if soft keyboard is visible. + * Does not work on android 7 but does on android 11 avd. + * + * @param activity The Activity of the root view for which the visibility should be checked. + * @return Returns {@code true} if soft keyboard is visible, otherwise {@code false}. + */ public static boolean isSoftKeyboardVisible(final Activity activity) { if (activity != null && activity.getWindow() != null) { WindowInsets insets = activity.getWindow().getDecorView().getRootWindowInsets(); if (insets != null) { WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets); - if (insetsCompat != null && insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { - Logger.logVerbose(LOG_TAG, "Keyboard visible"); + if (insetsCompat.isVisible(WindowInsetsCompat.Type.ime())) { + Logger.logVerbose(LOG_TAG, "Soft keyboard visible"); return true; } } } - Logger.logVerbose(LOG_TAG, "Keyboard not visible"); + Logger.logVerbose(LOG_TAG, "Soft keyboard not visible"); return false; } + /** + * Check if hardware keyboard is connected. + * Based on default implementation of {@link InputMethodService#onEvaluateInputViewShown()}. + * + * https://developer.android.com/guide/topics/resources/providing-resources#ImeQualifier + * + * @param context The Context for operations. + * @return Returns {@code true} if device has hardware keys for text input or an external hardware + * keyboard is connected, otherwise {@code false}. + */ + public static boolean isHardKeyboardConnected(final Context context) { + if (context == null) return false; + + Configuration config = context.getResources().getConfiguration(); + return config.keyboard != Configuration.KEYBOARD_NOKEYS + || config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO; + } + + /** + * Check if soft keyboard should be disabled based on user configuration. + * + * @param context The Context for operations. + * @return Returns {@code true} if device has soft keyboard should be disabled, otherwise {@code false}. + */ + public static boolean shouldSoftKeyboardBeDisabled(final Context context, final boolean isSoftKeyboardEnabled, final boolean isSoftKeyboardEnabledOnlyIfNoHardware) { + // If soft keyboard is disabled by user regardless of hardware keyboard + if (!isSoftKeyboardEnabled) { + return true; + } else { + /* + * Currently, for this case, soft keyboard will be disabled on Termux app startup and + * when switching back from another app. Soft keyboard can be temporarily enabled in + * show/hide soft keyboard toggle behaviour with keyboard toggle buttons and will continue + * to work when tapping on terminal view for opening and back button for closing, until + * Termux app is switched to another app. After returning back, keyboard will be disabled + * until toggle is pressed again. + * This may also be helpful for the Lineage OS bug where if "Show soft keyboard" toggle + * in "Language and Input" is disabled and Termux is started without a hardware keyboard + * in landscape mode, and then the keyboard is connected and phone is rotated to portrait + * mode and then keyboard is toggled with Termux keyboard toggle buttons, then a blank + * space is shown in-place of the soft keyboard. Its likely related to + * WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE which pushes up the view when + * keyboard is opened instead of the keyboard opening on top of the view (hiding stuff). + * If the "Show soft keyboard" toggle was disabled, then this resizing shouldn't happen. + * But it seems resizing does happen, but keyboard is never opened since its not supposed to. + * https://github.com/termux/termux-app/issues/1995#issuecomment-837080079 + */ + // If soft keyboard is disabled by user only if hardware keyboard is connected + if(isSoftKeyboardEnabledOnlyIfNoHardware) { + boolean isHardKeyboardConnected = KeyboardUtils.isHardKeyboardConnected(context); + Logger.logVerbose(LOG_TAG, "Hardware keyboard connected=" + isHardKeyboardConnected); + return isHardKeyboardConnected; + } else { + return false; + } + } + } + } From d42514d8c958eadb5eb6c59c47b3bf62ea2ab472 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 13 May 2021 05:07:45 +0500 Subject: [PATCH 21/37] Moved Termux app settings into dedicated "directory" in Termux Settings and added About page The `TermuxConstants` class has been updated to `v0.20.0`. Check its Changelog sections for info on changes. --- .../termux/app/activities/ReportActivity.java | 4 +- .../app/activities/SettingsActivity.java | 32 +++++++++ .../settings/TermuxPreferencesFragment.java | 46 ++++++++++++ .../DebuggingPreferencesFragment.java | 10 +-- .../TerminalIOPreferencesFragment.java | 8 +-- .../com/termux/app/models/UserAction.java | 3 +- ...l => markdown_adapter_node_code_block.xml} | 0 ....xml => markdown_adapter_node_default.xml} | 0 .../res/layout/preference_markdown_text.xml | 20 ++++++ app/src/main/res/values/strings.xml | 69 ++++++++++-------- .../main/res/xml/debugging_preferences.xml | 33 --------- app/src/main/res/xml/root_preferences.xml | 14 ++-- .../main/res/xml/terminal_io_preferences.xml | 21 ------ .../res/xml/termux_debugging_preferences.xml | 33 +++++++++ app/src/main/res/xml/termux_preferences.xml | 13 ++++ .../xml/termux_terminal_io_preferences.xml | 21 ++++++ .../termux/shared/markdown/MarkdownUtils.java | 2 +- .../termux/shared/termux/TermuxConstants.java | 46 +++++++++++- .../com/termux/shared/termux/TermuxUtils.java | 71 +++++++++++++++++++ 19 files changed, 344 insertions(+), 102 deletions(-) create mode 100644 app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java rename app/src/main/java/com/termux/app/fragments/settings/{ => termux}/DebuggingPreferencesFragment.java (92%) rename app/src/main/java/com/termux/app/fragments/settings/{ => termux}/TerminalIOPreferencesFragment.java (88%) rename app/src/main/res/layout/{activity_report_adapter_node_code_block.xml => markdown_adapter_node_code_block.xml} (100%) rename app/src/main/res/layout/{activity_report_adapter_node_default.xml => markdown_adapter_node_default.xml} (100%) create mode 100644 app/src/main/res/layout/preference_markdown_text.xml delete mode 100644 app/src/main/res/xml/debugging_preferences.xml delete mode 100644 app/src/main/res/xml/terminal_io_preferences.xml create mode 100644 app/src/main/res/xml/termux_debugging_preferences.xml create mode 100644 app/src/main/res/xml/termux_preferences.xml create mode 100644 app/src/main/res/xml/termux_terminal_io_preferences.xml diff --git a/app/src/main/java/com/termux/app/activities/ReportActivity.java b/app/src/main/java/com/termux/app/activities/ReportActivity.java index 2491005a02..5f6b0b3928 100644 --- a/app/src/main/java/com/termux/app/activities/ReportActivity.java +++ b/app/src/main/java/com/termux/app/activities/ReportActivity.java @@ -92,8 +92,8 @@ private void updateUI(Bundle bundle) { final Markwon markwon = MarkdownUtils.getRecyclerMarkwonBuilder(this); - final MarkwonAdapter adapter = MarkwonAdapter.builderTextViewIsRoot(R.layout.activity_report_adapter_node_default) - .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.activity_report_adapter_node_code_block, R.id.code_text_view)) + final MarkwonAdapter adapter = MarkwonAdapter.builderTextViewIsRoot(R.layout.markdown_adapter_node_default) + .include(FencedCodeBlock.class, SimpleEntry.create(R.layout.markdown_adapter_node_code_block, R.id.code_text_view)) .build(); recyclerView.setLayoutManager(new LinearLayoutManager(this)); diff --git a/app/src/main/java/com/termux/app/activities/SettingsActivity.java b/app/src/main/java/com/termux/app/activities/SettingsActivity.java index b30b1a5799..afb7373e97 100644 --- a/app/src/main/java/com/termux/app/activities/SettingsActivity.java +++ b/app/src/main/java/com/termux/app/activities/SettingsActivity.java @@ -1,12 +1,18 @@ package com.termux.app.activities; +import android.content.Context; import android.os.Bundle; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import com.termux.R; +import com.termux.app.models.ReportInfo; +import com.termux.app.models.UserAction; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.TermuxUtils; public class SettingsActivity extends AppCompatActivity { @@ -37,6 +43,32 @@ public static class RootPreferencesFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); + + setAboutOnPreferenceClickListener(); + } + + private void setAboutOnPreferenceClickListener() { + Context context = getContext(); + Preference about = findPreference("about"); + if (context != null && about != null) { + about.setOnPreferenceClickListener(preference -> { + String title = "About"; + + StringBuilder aboutString = new StringBuilder(); + aboutString.append(TermuxUtils.getAppInfoMarkdownString(context, false)); + + String termuxPluginAppsInfo = TermuxUtils.getTermuxPluginAppsInfoMarkdownString(context); + if (termuxPluginAppsInfo != null) + aboutString.append("\n\n").append(termuxPluginAppsInfo); + + aboutString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); + aboutString.append("\n\n").append(TermuxUtils.getImportantLinksMarkdownString(context)); + + ReportActivity.startReportActivity(context, new ReportInfo(UserAction.ABOUT, TermuxConstants.TERMUX_APP.TERMUX_SETTINGS_ACTIVITY_NAME, title, null, aboutString.toString(), null, false)); + + return true; + }); + } } } diff --git a/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java new file mode 100644 index 0000000000..67543f63c0 --- /dev/null +++ b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java @@ -0,0 +1,46 @@ +package com.termux.app.fragments.settings; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Keep; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.termux.R; +import com.termux.shared.settings.preferences.TermuxAppSharedPreferences; + +@Keep +public class TermuxPreferencesFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(TermuxPreferencesDataStore.getInstance(getContext())); + + setPreferencesFromResource(R.xml.termux_preferences, rootKey); + } + +} + +class TermuxPreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + private final TermuxAppSharedPreferences mPreferences; + + private static TermuxPreferencesDataStore mInstance; + + private TermuxPreferencesDataStore(Context context) { + mContext = context; + mPreferences = new TermuxAppSharedPreferences(context); + } + + public static synchronized TermuxPreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new TermuxPreferencesDataStore(context.getApplicationContext()); + } + return mInstance; + } + +} diff --git a/app/src/main/java/com/termux/app/fragments/settings/DebuggingPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java similarity index 92% rename from app/src/main/java/com/termux/app/fragments/settings/DebuggingPreferencesFragment.java rename to app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java index 814426b66e..279cdc6526 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/DebuggingPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.termux.app.fragments.settings; +package com.termux.app.fragments.settings.termux; import android.content.Context; import android.os.Bundle; @@ -23,7 +23,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { PreferenceManager preferenceManager = getPreferenceManager(); preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(getContext())); - setPreferencesFromResource(R.xml.debugging_preferences, rootKey); + setPreferencesFromResource(R.xml.termux_debugging_preferences, rootKey); PreferenceCategory loggingCategory = findPreference("logging"); @@ -125,11 +125,11 @@ public void putBoolean(String key, boolean value) { public boolean getBoolean(String key, boolean defValue) { switch (key) { case "terminal_view_key_logging_enabled": - return mPreferences.getTerminalViewKeyLoggingEnabled(); + return mPreferences.isTerminalViewKeyLoggingEnabled(); case "plugin_error_notifications_enabled": - return mPreferences.getPluginErrorNotificationsEnabled(); + return mPreferences.arePluginErrorNotificationsEnabled(); case "crash_report_notifications_enabled": - return mPreferences.getCrashReportNotificationsEnabled(); + return mPreferences.areCrashReportNotificationsEnabled(); default: return false; } diff --git a/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java similarity index 88% rename from app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java rename to app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java index d9500f841d..2e750c85da 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/TerminalIOPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java @@ -1,4 +1,4 @@ -package com.termux.app.fragments.settings; +package com.termux.app.fragments.settings.termux; import android.content.Context; import android.os.Bundle; @@ -19,7 +19,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { PreferenceManager preferenceManager = getPreferenceManager(); preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(getContext())); - setPreferencesFromResource(R.xml.terminal_io_preferences, rootKey); + setPreferencesFromResource(R.xml.termux_terminal_io_preferences, rootKey); } } @@ -65,9 +65,9 @@ public void putBoolean(String key, boolean value) { public boolean getBoolean(String key, boolean defValue) { switch (key) { case "soft_keyboard_enabled": - return mPreferences.getSoftKeyboardEnabled(); + return mPreferences.isSoftKeyboardEnabled(); case "soft_keyboard_enabled_only_if_no_hardware": - return mPreferences.getSoftKeyboardEnabledOnlyIfNoHardware(); + return mPreferences.isSoftKeyboardEnabledOnlyIfNoHardware(); default: return false; } diff --git a/app/src/main/java/com/termux/app/models/UserAction.java b/app/src/main/java/com/termux/app/models/UserAction.java index ad56fbefaf..ee47605a07 100644 --- a/app/src/main/java/com/termux/app/models/UserAction.java +++ b/app/src/main/java/com/termux/app/models/UserAction.java @@ -2,8 +2,9 @@ public enum UserAction { - PLUGIN_EXECUTION_COMMAND("plugin execution command"), + ABOUT("about"), CRASH_REPORT("crash report"), + PLUGIN_EXECUTION_COMMAND("plugin execution command"), REPORT_ISSUE_FROM_TRANSCRIPT("report issue from transcript"); private final String name; diff --git a/app/src/main/res/layout/activity_report_adapter_node_code_block.xml b/app/src/main/res/layout/markdown_adapter_node_code_block.xml similarity index 100% rename from app/src/main/res/layout/activity_report_adapter_node_code_block.xml rename to app/src/main/res/layout/markdown_adapter_node_code_block.xml diff --git a/app/src/main/res/layout/activity_report_adapter_node_default.xml b/app/src/main/res/layout/markdown_adapter_node_default.xml similarity index 100% rename from app/src/main/res/layout/activity_report_adapter_node_default.xml rename to app/src/main/res/layout/markdown_adapter_node_default.xml diff --git a/app/src/main/res/layout/preference_markdown_text.xml b/app/src/main/res/layout/preference_markdown_text.xml new file mode 100644 index 0000000000..f77049e350 --- /dev/null +++ b/app/src/main/res/layout/preference_markdown_text.xml @@ -0,0 +1,20 @@ + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b12521886c..0c00943eff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -122,42 +122,55 @@ &TERMUX_APP_NAME; Settings - - Debugging + + &TERMUX_APP_NAME; + Preferences for &TERMUX_APP_NAME; app - - Logging + + Debugging + Preferences for debugging - - Terminal View Key Logging - Logs will not have entries for terminal view keys. (Default) - Logcat logs will have entries for terminal view keys. These are very verbose and should be disabled under normal circumstances or will cause performance issues. + + Logging - - Plugin Error Notifications - Disable flashes and notifications for plugin errors. - Show flashes and notifications for plugin errors. (Default) + + Log Level - - Crash Report Notifications - Disable notifications for crash reports. - Show notifications for crash reports. (Default) + + Terminal View Key Logging + Logs will not have entries for terminal view keys. (Default) + Logcat logs will have entries for terminal view keys. These are very verbose and should be disabled under normal circumstances or will cause performance issues. + + Plugin Error Notifications + Disable flashes and notifications for plugin errors. + Show flashes and notifications for plugin errors. (Default) - - Terminal I/O + + Crash Report Notifications + Disable notifications for crash reports. + Show notifications for crash reports. (Default) - - Keyboard - - Soft Keyboard Enabled - Soft keyboard will be disabled. - Soft keyboard will be enabled. (Default) + + Terminal I/O + Preferences for terminal I/O - - Soft Keyboard If No Hardware - Soft keyboard will be enabled even if hardware keyboard is connected. (Default) - Soft keyboard will be enabled only if no hardware keyboard is connected. + + Keyboard + + + Soft Keyboard Enabled + Soft keyboard will be disabled. + Soft keyboard will be enabled. (Default) + + + Soft Keyboard Only If No Hardware + Soft keyboard will be enabled even if hardware keyboard is connected. (Default) + Soft keyboard will be enabled only if no hardware keyboard is connected. + + + + About diff --git a/app/src/main/res/xml/debugging_preferences.xml b/app/src/main/res/xml/debugging_preferences.xml deleted file mode 100644 index 48e44f98d1..0000000000 --- a/app/src/main/res/xml/debugging_preferences.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 75dd8826ac..297b490366 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -1,13 +1,15 @@ + app:key="termux" + app:title="@string/termux_preferences_title" + app:summary="@string/termux_preferences_summary" + app:fragment="com.termux.app.fragments.settings.TermuxPreferencesFragment"/> + app:key="about" + app:title="@string/about_preferences_title" + app:persistent="false"/> + diff --git a/app/src/main/res/xml/terminal_io_preferences.xml b/app/src/main/res/xml/terminal_io_preferences.xml deleted file mode 100644 index 6aecbf482c..0000000000 --- a/app/src/main/res/xml/terminal_io_preferences.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/xml/termux_debugging_preferences.xml b/app/src/main/res/xml/termux_debugging_preferences.xml new file mode 100644 index 0000000000..b59d931de7 --- /dev/null +++ b/app/src/main/res/xml/termux_debugging_preferences.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/termux_preferences.xml b/app/src/main/res/xml/termux_preferences.xml new file mode 100644 index 0000000000..f28c775dc5 --- /dev/null +++ b/app/src/main/res/xml/termux_preferences.xml @@ -0,0 +1,13 @@ + + + + + + + diff --git a/app/src/main/res/xml/termux_terminal_io_preferences.xml b/app/src/main/res/xml/termux_terminal_io_preferences.xml new file mode 100644 index 0000000000..ea9a0eb509 --- /dev/null +++ b/app/src/main/res/xml/termux_terminal_io_preferences.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + diff --git a/termux-shared/src/main/java/com/termux/shared/markdown/MarkdownUtils.java b/termux-shared/src/main/java/com/termux/shared/markdown/MarkdownUtils.java index 86ddd03fff..470624de29 100644 --- a/termux-shared/src/main/java/com/termux/shared/markdown/MarkdownUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/markdown/MarkdownUtils.java @@ -179,7 +179,7 @@ public void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder) .setFactory(Code.class, (configuration, props) -> new Object[]{ new BackgroundColorSpan(ContextCompat.getColor(context, R.color.background_markdown_code_inline)), new TypefaceSpan("monospace"), - new AbsoluteSizeSpan(8) + new AbsoluteSizeSpan(48) }) // NB! both ordered and bullet list items .setFactory(ListItem.class, (configuration, props) -> new BulletSpan()); diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index d0bcab1ddb..fe8c526dbe 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -3,9 +3,11 @@ import android.annotation.SuppressLint; import java.io.File; +import java.util.Arrays; +import java.util.List; /* - * Version: v0.19.0 + * Version: v0.20.0 * * Changelog * @@ -135,6 +137,11 @@ * - Added `TERMUX_SERVICE.EXTRA_STDIN`. * - Added `RUN_COMMAND_SERVICE.EXTRA_STDIN`. * - Deprecated `TERMUX_ACTIVITY.EXTRA_RELOAD_STYLE`. + * + * - 0.20.0 (2021-05-13) + * - Added `TERMUX_WIKI`, `TERMUX_WIKI_URL`, `TERMUX_PLUGIN_APP_NAMES_LIST`, `TERMUX_PLUGIN_APP_PACKAGE_NAMES_LIST`. + * - Added `TERMUX_SETTINGS_ACTIVITY_NAME`. + * */ /** @@ -194,6 +201,12 @@ public final class TermuxConstants { /** Termux support email mailto url */ public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:termuxreports@groups.io" + /** Termux Wiki */ + public static final String TERMUX_WIKI = "Termux Wiki"; // Default: "Termux Wiki" + + /** Termux Wiki url */ + public static final String TERMUX_WIKI_URL = "https://wiki.termux.com"; // Default: "https://wiki.termux.com" + /** Termux Reddit subreddit */ public static final String TERMUX_REDDIT_SUBREDDIT = "r/termux"; // Default: "r/termux" @@ -314,6 +327,30 @@ public final class TermuxConstants { + /* + * Termux plugin apps lists. + */ + + public static final List TERMUX_PLUGIN_APP_NAMES_LIST = Arrays.asList( + TERMUX_API_APP_NAME, + TERMUX_BOOT_APP_NAME, + TERMUX_FLOAT_APP_NAME, + TERMUX_STYLING_APP_NAME, + TERMUX_TASKER_APP_NAME, + TERMUX_WIDGET_APP_NAME); + + public static final List TERMUX_PLUGIN_APP_PACKAGE_NAMES_LIST = Arrays.asList( + TERMUX_API_PACKAGE_NAME, + TERMUX_BOOT_PACKAGE_NAME, + TERMUX_FLOAT_PACKAGE_NAME, + TERMUX_STYLING_PACKAGE_NAME, + TERMUX_TASKER_PACKAGE_NAME, + TERMUX_WIDGET_PACKAGE_NAME); + + + + + /* * Termux packages urls. */ @@ -654,6 +691,13 @@ public static final class TERMUX_ACTIVITY { + /** Termux app settings activity name. */ + public static final String TERMUX_SETTINGS_ACTIVITY_NAME = TERMUX_PACKAGE_NAME + ".app.activities.SettingsActivity"; // Default: "com.termux.app.activities.SettingsActivity" + + + + + /** Termux app core service name. */ public static final String TERMUX_SERVICE_NAME = TERMUX_PACKAGE_NAME + ".app.TermuxService"; // Default: "com.termux.app.TermuxService" diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java index 3e08b9fe56..545e1c245b 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java @@ -132,6 +132,38 @@ public static void sendTermuxOpenedBroadcast(@NonNull Context context) { } } + /** + * Get a markdown {@link String} for the apps info of all/any termux plugin apps installed. + * + * @param currentPackageContext The context of current package. + * @return Returns the markdown {@link String}. + */ + public static String getTermuxPluginAppsInfoMarkdownString(@NonNull final Context currentPackageContext) { + if (currentPackageContext == null) return "null"; + + StringBuilder markdownString = new StringBuilder(); + + List termuxPluginAppPackageNamesList = TermuxConstants.TERMUX_PLUGIN_APP_PACKAGE_NAMES_LIST; + + if (termuxPluginAppPackageNamesList != null) { + for (int i = 0; i < termuxPluginAppPackageNamesList.size(); i++) { + String termuxPluginAppPackageName = termuxPluginAppPackageNamesList.get(i); + Context termuxPluginAppContext = PackageUtils.getContextForPackage(currentPackageContext, termuxPluginAppPackageName); + // If the package context for the plugin app is not null, then assume its installed and get its info + if (termuxPluginAppContext != null) { + if (i != 0) + markdownString.append("\n\n"); + markdownString.append(TermuxUtils.getAppInfoMarkdownString(termuxPluginAppContext, false)); + } + } + } + + if (markdownString.toString().isEmpty()) + return null; + + return markdownString.toString(); + } + /** * Get a markdown {@link String} for the app info. If the {@code context} passed is different * from the {@link TermuxConstants#TERMUX_PACKAGE_NAME} package context, then this function @@ -291,6 +323,45 @@ public static String getReportIssueMarkdownString(@NonNull final Context context return markdownString.toString(); } + /** + * Get a markdown {@link String} for important links. + * + * @param context The context for operations. + * @return Returns the markdown {@link String}. + */ + public static String getImportantLinksMarkdownString(@NonNull final Context context) { + if (context == null) return "null"; + + StringBuilder markdownString = new StringBuilder(); + + markdownString.append("## Important Links"); + + markdownString.append("\n\n### Github\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_APP_NAME, TermuxConstants.TERMUX_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_API_APP_NAME, TermuxConstants.TERMUX_API_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_BOOT_APP_NAME, TermuxConstants.TERMUX_BOOT_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_FLOAT_APP_NAME, TermuxConstants.TERMUX_FLOAT_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_STYLING_APP_NAME, TermuxConstants.TERMUX_STYLING_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_TASKER_APP_NAME, TermuxConstants.TERMUX_TASKER_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_WIDGET_APP_NAME, TermuxConstants.TERMUX_WIDGET_GITHUB_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_PACKAGES_GITHUB_REPO_URL)).append(" "); + + markdownString.append("\n\n### Email\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_SUPPORT_EMAIL_URL, TermuxConstants.TERMUX_SUPPORT_EMAIL_MAILTO_URL)).append(" "); + + markdownString.append("\n\n### Reddit\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_REDDIT_SUBREDDIT, TermuxConstants.TERMUX_REDDIT_SUBREDDIT_URL)).append(" "); + + markdownString.append("\n\n### Wiki\n"); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_WIKI, TermuxConstants.TERMUX_WIKI_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_APP_NAME, TermuxConstants.TERMUX_GITHUB_WIKI_REPO_URL)).append(" "); + markdownString.append("\n").append(MarkdownUtils.getLinkMarkdownString(TermuxConstants.TERMUX_PACKAGES_GITHUB_REPO_NAME, TermuxConstants.TERMUX_PACKAGES_GITHUB_WIKI_REPO_URL)).append(" "); + + markdownString.append("\n##\n"); + + return markdownString.toString(); + } + /** From 4629276500a889344ff74d485ded15f337c4b86c Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 13 May 2021 05:08:25 +0500 Subject: [PATCH 22/37] Changed TermuxAppSharedPreferences function naming convention --- .../main/java/com/termux/app/TermuxActivity.java | 10 +++++----- .../app/terminal/TermuxTerminalViewClient.java | 6 +++--- .../java/com/termux/app/utils/CrashUtils.java | 2 +- .../java/com/termux/app/utils/PluginUtils.java | 2 +- .../termux/shared/packages/PermissionUtils.java | 2 +- .../preferences/TermuxAppSharedPreferences.java | 16 ++++++++-------- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 3942ea39da..9cf396a815 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -225,7 +225,7 @@ public void onStart() { // If user changed the preference from {@link TermuxSettings} activity and returns, then // update the {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value. - mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.getTerminalViewKeyLoggingEnabled()); + mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.isTerminalViewKeyLoggingEnabled()); // The current terminal session may have changed while being away, force // a refresh of the displayed terminal. @@ -353,7 +353,7 @@ private void setDrawerTheme() { private void setTerminalToolbarView(Bundle savedInstanceState) { final ViewPager terminalToolbarViewPager = findViewById(R.id.terminal_toolbar_view_pager); - if (mPreferences.getShowTerminalToolbar()) terminalToolbarViewPager.setVisibility(View.VISIBLE); + if (mPreferences.shouldShowTerminalToolbar()) terminalToolbarViewPager.setVisibility(View.VISIBLE); ViewGroup.LayoutParams layoutParams = terminalToolbarViewPager.getLayoutParams(); mTerminalToolbarDefaultHeight = layoutParams.height; @@ -441,10 +441,10 @@ private void setTermuxTerminalViewAndClients() { mTerminalView.setTerminalViewClient(mTermuxTerminalViewClient); mTerminalView.setTextSize(mPreferences.getFontSize()); - mTerminalView.setKeepScreenOn(mPreferences.getKeepScreenOn()); + mTerminalView.setKeepScreenOn(mPreferences.shouldKeepScreenOn()); // Set {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value - mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.getTerminalViewKeyLoggingEnabled()); + mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.isTerminalViewKeyLoggingEnabled()); mTermuxTerminalSessionClient.checkForFontAndColors(); } @@ -508,7 +508,7 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal); menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning()); menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); - menu.add(Menu.NONE, CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.action_toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.getKeepScreenOn()); + menu.add(Menu.NONE, CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.action_toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.shouldKeepScreenOn()); menu.add(Menu.NONE, CONTEXT_MENU_HELP_ID, Menu.NONE, R.string.action_open_help); menu.add(Menu.NONE, CONTEXT_MENU_SETTINGS_ID, Menu.NONE, R.string.action_open_settings); menu.add(Menu.NONE, CONTEXT_MENU_REPORT_ID, Menu.NONE, R.string.action_report_issue); diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 18a0d39b03..8b2a3f9451 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -368,7 +368,7 @@ public void onToggleSoftKeyboardRequest() { // If soft keyboard toggle behaviour is show/hide else { // If soft keyboard is disabled by user for Termux - if (!mActivity.getPreferences().getSoftKeyboardEnabled()) { + if (!mActivity.getPreferences().isSoftKeyboardEnabled()) { Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard on toggle"); KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { @@ -382,8 +382,8 @@ public void onToggleSoftKeyboardRequest() { public void setSoftKeyboardState(boolean isStartup, boolean isReloadTermuxProperties) { // If soft keyboard is disabled by user for Termux (check function docs for Termux behaviour info) if (KeyboardUtils.shouldSoftKeyboardBeDisabled(mActivity, - mActivity.getPreferences().getSoftKeyboardEnabled(), - mActivity.getPreferences().getSoftKeyboardEnabledOnlyIfNoHardware())) { + mActivity.getPreferences().isSoftKeyboardEnabled(), + mActivity.getPreferences().isSoftKeyboardEnabledOnlyIfNoHardware())) { Logger.logVerbose(LOG_TAG, "Maintaining disabled soft keyboard"); KeyboardUtils.disableSoftKeyboard(mActivity, mActivity.getTerminalView()); } else { diff --git a/app/src/main/java/com/termux/app/utils/CrashUtils.java b/app/src/main/java/com/termux/app/utils/CrashUtils.java index a192e5ab2e..f2d6011bc0 100644 --- a/app/src/main/java/com/termux/app/utils/CrashUtils.java +++ b/app/src/main/java/com/termux/app/utils/CrashUtils.java @@ -49,7 +49,7 @@ public static void notifyCrash(final Context context, final String logTagParam) TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); // If user has disabled notifications for crashes - if (!preferences.getCrashReportNotificationsEnabled()) + if (!preferences.areCrashReportNotificationsEnabled()) return; new Thread() { diff --git a/app/src/main/java/com/termux/app/utils/PluginUtils.java b/app/src/main/java/com/termux/app/utils/PluginUtils.java index 98d469194a..72d4af5337 100644 --- a/app/src/main/java/com/termux/app/utils/PluginUtils.java +++ b/app/src/main/java/com/termux/app/utils/PluginUtils.java @@ -141,7 +141,7 @@ public static void processPluginExecutionCommandError(final Context context, Str TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); // If user has disabled notifications for plugin, then just return - if (!preferences.getPluginErrorNotificationsEnabled() && !forceNotification) + if (!preferences.arePluginErrorNotificationsEnabled() && !forceNotification) return; // Flash the errmsg diff --git a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java index da522b066e..65c0f73db0 100644 --- a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java @@ -76,7 +76,7 @@ public static boolean validateDisplayOverOtherAppsPermissionForPostAndroid10(Con if (!PermissionUtils.checkDisplayOverOtherAppsPermission(context)) { TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); - if (preferences.getPluginErrorNotificationsEnabled()) + if (preferences.arePluginErrorNotificationsEnabled()) Logger.showToast(context, context.getString(R.string.error_display_over_other_apps_permission_not_granted), true); return false; } else { diff --git a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java index 7c6656cdb3..4e66c8f599 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java @@ -38,7 +38,7 @@ private static SharedPreferences getPrivateSharedPreferences(Context context) { - public boolean getShowTerminalToolbar() { + public boolean shouldShowTerminalToolbar() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_SHOW_TERMINAL_TOOLBAR, TERMUX_APP.DEFAULT_VALUE_SHOW_TERMINAL_TOOLBAR); } @@ -47,14 +47,14 @@ public void setShowTerminalToolbar(boolean value) { } public boolean toogleShowTerminalToolbar() { - boolean currentValue = getShowTerminalToolbar(); + boolean currentValue = shouldShowTerminalToolbar(); setShowTerminalToolbar(!currentValue); return !currentValue; } - public boolean getSoftKeyboardEnabled() { + public boolean isSoftKeyboardEnabled() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED, TERMUX_APP.DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED); } @@ -62,7 +62,7 @@ public void setSoftKeyboardEnabled(boolean value) { SharedPreferenceUtils.setBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED, value, false); } - public boolean getSoftKeyboardEnabledOnlyIfNoHardware() { + public boolean isSoftKeyboardEnabledOnlyIfNoHardware() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE, TERMUX_APP.DEFAULT_VALUE_KEY_SOFT_KEYBOARD_ENABLED_ONLY_IF_NO_HARDWARE); } @@ -72,7 +72,7 @@ public void setSoftKeyboardEnabledOnlyIfNoHardware(boolean value) { - public boolean getKeepScreenOn() { + public boolean shouldKeepScreenOn() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_KEEP_SCREEN_ON, TERMUX_APP.DEFAULT_VALUE_KEEP_SCREEN_ON); } @@ -151,7 +151,7 @@ public void setLastNotificationId(int notificationId) { - public boolean getTerminalViewKeyLoggingEnabled() { + public boolean isTerminalViewKeyLoggingEnabled() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_TERMINAL_VIEW_KEY_LOGGING_ENABLED, TERMUX_APP.DEFAULT_VALUE_TERMINAL_VIEW_KEY_LOGGING_ENABLED); } @@ -161,7 +161,7 @@ public void setTerminalViewKeyLoggingEnabled(boolean value) { - public boolean getPluginErrorNotificationsEnabled() { + public boolean arePluginErrorNotificationsEnabled() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED, TERMUX_APP.DEFAULT_VALUE_PLUGIN_ERROR_NOTIFICATIONS_ENABLED); } @@ -171,7 +171,7 @@ public void setPluginErrorNotificationsEnabled(boolean value) { - public boolean getCrashReportNotificationsEnabled() { + public boolean areCrashReportNotificationsEnabled() { return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED, TERMUX_APP.DEFAULT_VALUE_CRASH_REPORT_NOTIFICATIONS_ENABLED); } From 1e30022ce7d79c26632aba675fe0da999271225a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 13 May 2021 15:40:09 +0500 Subject: [PATCH 23/37] Add support for APK signing certificate SHA-256 digest and detecting APK release type and add them to App Info reports The `TermuxConstants` class has been updated to `v0.21.0`. Check its Changelog sections for info on changes. --- .../com/termux/shared/data/DataUtils.java | 22 ++++++++++-- .../termux/shared/packages/PackageUtils.java | 34 ++++++++++++++++++- .../termux/shared/termux/TermuxConstants.java | 33 +++++++++++++++++- .../com/termux/shared/termux/TermuxUtils.java | 22 ++++++++++++ 4 files changed, 107 insertions(+), 4 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java index 61b7fff2a9..9a6af5a086 100644 --- a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java @@ -10,6 +10,8 @@ public class DataUtils { public static final int TRANSACTION_SIZE_LIMIT_IN_BYTES = 100 * 1024; // 100KB + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + public static String getTruncatedCommandOutput(String text, int maxLength, boolean fromEnd, boolean onNewline, boolean addPrefix) { if (text == null) return null; @@ -43,7 +45,7 @@ public static String getTruncatedCommandOutput(String text, int maxLength, boole /** * Get the {@code float} from a {@link String}. * - * @param value The {@link String value. + * @param value The {@link String} value. * @param def The default value if failed to read a valid value. * @return Returns the {@code float} value after parsing the {@link String} value, otherwise * returns default if failed to read a valid value, like in case of an exception. @@ -62,7 +64,7 @@ public static float getFloatFromString(String value, float def) { /** * Get the {@code int} from a {@link String}. * - * @param value The {@link String value. + * @param value The {@link String} value. * @param def The default value if failed to read a valid value. * @return Returns the {@code int} value after parsing the {@link String} value, otherwise * returns default if failed to read a valid value, like in case of an exception. @@ -78,6 +80,22 @@ public static int getIntFromString(String value, int def) { } } + /** + * Get the {@code hex string} from a {@link byte[]}. + * + * @param bytes The {@link byte[]} value. + * @return Returns the {@code hex string} value. + */ + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } + /** * Get an {@code int} from {@link Bundle} that is stored as a {@link String}. * diff --git a/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java b/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java index 75b8b8c4a6..b1192e7e95 100644 --- a/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java @@ -3,11 +3,15 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import androidx.annotation.NonNull; +import com.termux.shared.data.DataUtils; import com.termux.shared.logger.Logger; +import java.security.MessageDigest; + public class PackageUtils { /** @@ -32,8 +36,19 @@ public static Context getContextForPackage(@NonNull final Context context, Strin * @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised. */ public static PackageInfo getPackageInfoForPackage(@NonNull final Context context) { + return getPackageInfoForPackage(context, 0); + } + + /** + * Get the {@link PackageInfo} for the package associated with the {@code context}. + * + * @param context The {@link Context} for the package. + * @param flags The flags to pass to {@link PackageManager#getPackageInfo(String, int)}. + * @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised. + */ + public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, final int flags) { try { - return context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return context.getPackageManager().getPackageInfo(context.getPackageName(), flags); } catch (final Exception e) { return null; } @@ -107,4 +122,21 @@ public static String getVersionNameForPackage(@NonNull final Context context) { } } + + /** + * Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}. + * + * @param context The {@link Context} for the package. + * @return Returns the{@code SHA-256 digest}. This will be {@code null} if an exception is raised. + */ + public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context) { + try { + PackageInfo packageInfo = getPackageInfoForPackage(context, PackageManager.GET_SIGNATURES); + if (packageInfo == null) return null; + return DataUtils.bytesToHex(MessageDigest.getInstance("SHA-256").digest(packageInfo.signatures[0].toByteArray())); + } catch (final Exception e) { + return null; + } + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index fe8c526dbe..2f4284d9ac 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -7,7 +7,7 @@ import java.util.List; /* - * Version: v0.20.0 + * Version: v0.21.0 * * Changelog * @@ -142,6 +142,11 @@ * - Added `TERMUX_WIKI`, `TERMUX_WIKI_URL`, `TERMUX_PLUGIN_APP_NAMES_LIST`, `TERMUX_PLUGIN_APP_PACKAGE_NAMES_LIST`. * - Added `TERMUX_SETTINGS_ACTIVITY_NAME`. * + * - 0.21.0 (2021-05-13) + * - Added `APK_RELEASE_FDROID`, `APK_RELEASE_FDROID_SIGNING_CERTIFICATE_SHA256_DIGEST`, + * - Added `APK_RELEASE_GITHUB_DEBUG_BUILD`, `APK_RELEASE_GITHUB_DEBUG_BUILD_SIGNING_CERTIFICATE_SHA256_DIGEST`, + * - Added `APK_RELEASE_GOOGLE_PLAYSTORE`, `APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST`. + * */ /** @@ -351,6 +356,32 @@ public final class TermuxConstants { + /* + * Termux APK releases. + */ + + /** F-Droid APK release */ + public static final String APK_RELEASE_FDROID = "F-Droid"; // Default: "F-Droid" + + /** F-Droid APK release signing certificate SHA-256 digest */ + public static final String APK_RELEASE_FDROID_SIGNING_CERTIFICATE_SHA256_DIGEST = "228FB2CFE90831C1499EC3CCAF61E96E8E1CE70766B9474672CE427334D41C42"; // Default: "228FB2CFE90831C1499EC3CCAF61E96E8E1CE70766B9474672CE427334D41C42" + + /** Github Debug Build APK release */ + public static final String APK_RELEASE_GITHUB_DEBUG_BUILD = "Github Debug Build"; // Default: "Github Debug Build" + + /** Github Debug Build APK release signing certificate SHA-256 digest */ + public static final String APK_RELEASE_GITHUB_DEBUG_BUILD_SIGNING_CERTIFICATE_SHA256_DIGEST = "B6DA01480EEFD5FBF2CD3771B8D1021EC791304BDD6C4BF41D3FAABAD48EE5E1"; // Default: "B6DA01480EEFD5FBF2CD3771B8D1021EC791304BDD6C4BF41D3FAABAD48EE5E1" + + /** Google Play Store APK release */ + public static final String APK_RELEASE_GOOGLE_PLAYSTORE = "Google Play Store"; // Default: "Google Play Store" + + /** Google Play Store APK release signing certificate SHA-256 digest */ + public static final String APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST = "738F0A30A04D3C8A1BE304AF18D0779BCF3EA88FB60808F657A3521861C2EBF9"; // Default: "738F0A30A04D3C8A1BE304AF18D0779BCF3EA88FB60808F657A3521861C2EBF9" + + + + + /* * Termux packages urls. */ diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java index 545e1c245b..706f556ba6 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java @@ -227,6 +227,13 @@ public static String getAppInfoMarkdownStringInner(@NonNull final Context contex appendPropertyToMarkdown(markdownString,"TARGET_SDK", PackageUtils.getTargetSDKForPackage(context)); appendPropertyToMarkdown(markdownString,"IS_DEBUG_BUILD", PackageUtils.isAppForPackageADebugBuild(context)); + String signingCertificateSHA256Digest = PackageUtils.getSigningCertificateSHA256DigestForPackage(context); + Logger.logError("'" + signingCertificateSHA256Digest + "'"); + if (signingCertificateSHA256Digest != null) { + appendPropertyToMarkdown(markdownString,"APK_RELEASE", getAPKRelease(signingCertificateSHA256Digest)); + appendPropertyToMarkdown(markdownString,"SIGNING_CERTIFICATE_SHA256_DIGEST", signingCertificateSHA256Digest); + } + return markdownString.toString(); } @@ -490,4 +497,19 @@ public static String getCurrentTimeStamp() { return df.format(new Date()); } + public static String getAPKRelease(String signingCertificateSHA256Digest) { + if (signingCertificateSHA256Digest == null) return "null"; + + switch (signingCertificateSHA256Digest.toUpperCase()) { + case TermuxConstants.APK_RELEASE_FDROID_SIGNING_CERTIFICATE_SHA256_DIGEST: + return TermuxConstants.APK_RELEASE_FDROID; + case TermuxConstants.APK_RELEASE_GITHUB_DEBUG_BUILD_SIGNING_CERTIFICATE_SHA256_DIGEST: + return TermuxConstants.APK_RELEASE_GITHUB_DEBUG_BUILD; + case TermuxConstants.APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST: + return TermuxConstants.APK_RELEASE_GOOGLE_PLAYSTORE; + default: + return "Unknown"; + } + } + } From af115c996627f7f5000f8036be4e0769c7ed0436 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 13 May 2021 17:37:01 +0500 Subject: [PATCH 24/37] Add generic functions to show a message in dialog and exit app with an error message --- .../java/com/termux/app/TermuxInstaller.java | 6 ++- termux-shared/build.gradle | 1 + .../termux/shared/interact/DialogUtils.java | 38 ++++++++++++++- .../main/res/layout/dialog_show_message.xml | 48 +++++++++++++++++++ termux-shared/src/main/res/values/colors.xml | 1 + 5 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 termux-shared/src/main/res/layout/dialog_show_message.xml diff --git a/app/src/main/java/com/termux/app/TermuxInstaller.java b/app/src/main/java/com/termux/app/TermuxInstaller.java index a600e534d5..0bcff56f12 100644 --- a/app/src/main/java/com/termux/app/TermuxInstaller.java +++ b/app/src/main/java/com/termux/app/TermuxInstaller.java @@ -12,6 +12,7 @@ import com.termux.R; import com.termux.shared.file.FileUtils; +import com.termux.shared.interact.DialogUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; @@ -57,8 +58,9 @@ static void setupBootstrapIfNeeded(final Activity activity, final Runnable whenD if (!isPrimaryUser) { String bootstrapErrorMessage = activity.getString(R.string.bootstrap_error_not_primary_user_message, TermuxConstants.TERMUX_PREFIX_DIR_PATH); Logger.logError(LOG_TAG, bootstrapErrorMessage); - new AlertDialog.Builder(activity).setTitle(R.string.bootstrap_error_title).setMessage(bootstrapErrorMessage) - .setOnDismissListener(dialog -> System.exit(0)).setPositiveButton(android.R.string.ok, null).show(); + DialogUtils.exitAppWithErrorMessage(activity, + activity.getString(R.string.bootstrap_error_title), + bootstrapErrorMessage); return; } diff --git a/termux-shared/build.gradle b/termux-shared/build.gradle index cc49fb91df..06238e984d 100644 --- a/termux-shared/build.gradle +++ b/termux-shared/build.gradle @@ -5,6 +5,7 @@ android { compileSdkVersion project.properties.compileSdkVersion.toInteger() dependencies { + implementation 'androidx.appcompat:appcompat:1.2.0' implementation "androidx.annotation:annotation:1.2.0" implementation "androidx.core:core:1.5.0-rc01" implementation "com.google.guava:guava:24.1-jre" diff --git a/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java b/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java index 1a5fa8344d..d6e8174186 100644 --- a/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java @@ -2,13 +2,19 @@ import android.app.Activity; import android.app.AlertDialog; +import android.content.Context; import android.content.DialogInterface; import android.text.Selection; import android.util.TypedValue; import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup.LayoutParams; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.TextView; + +import com.termux.shared.R; public final class DialogUtils { @@ -61,11 +67,41 @@ public static void textInput(Activity activity, int titleText, String initialTex builder.setNegativeButton(negativeButtonText, (dialog, which) -> onNegative.onTextSet(input.getText().toString())); } - if (onDismiss != null) builder.setOnDismissListener(onDismiss); + if (onDismiss != null) + builder.setOnDismissListener(onDismiss); dialogHolder[0] = builder.create(); dialogHolder[0].setCanceledOnTouchOutside(false); dialogHolder[0].show(); } + public static void showMessage(Context context, String titleText, String messageText, final DialogInterface.OnDismissListener onDismiss) { + + AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Light_Dialog) + .setPositiveButton(android.R.string.ok, null); + + LayoutInflater inflater = (LayoutInflater) context.getSystemService( Context.LAYOUT_INFLATER_SERVICE ); + View view = inflater.inflate(R.layout.dialog_show_message, null); + if (view != null) { + builder.setView(view); + + TextView titleView = view.findViewById(R.id.dialog_title); + if (titleView != null) + titleView.setText(titleText); + + TextView messageView = view.findViewById(R.id.dialog_message); + if (messageView != null) + messageView.setText(messageText); + } + + if (onDismiss != null) + builder.setOnDismissListener(onDismiss); + + builder.show(); + } + + public static void exitAppWithErrorMessage(Context context, String titleText, String messageText) { + showMessage(context, titleText, messageText, dialog -> System.exit(0)); + } + } diff --git a/termux-shared/src/main/res/layout/dialog_show_message.xml b/termux-shared/src/main/res/layout/dialog_show_message.xml new file mode 100644 index 0000000000..f074d08c10 --- /dev/null +++ b/termux-shared/src/main/res/layout/dialog_show_message.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + diff --git a/termux-shared/src/main/res/values/colors.xml b/termux-shared/src/main/res/values/colors.xml index cc83b56d07..1b54c29535 100644 --- a/termux-shared/src/main/res/values/colors.xml +++ b/termux-shared/src/main/res/values/colors.xml @@ -2,4 +2,5 @@ #1F000000 #0F000000 + #FF0000 From 79df863b75d1a9c75b65a16714fda7f9318ba038 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 03:54:13 +0500 Subject: [PATCH 25/37] Ensure we read/write to/from current SharedPreferences When getting SharedPreferences of other termux sharedUserId app packages, we get its Context first and if its null, it would mean that the package is not installed or likely has a different signature. For this case, we force exit the app in some places, since that shouldn't occur. Previously, if it was null, we were defaulting to getting SharedPreferences of current package context instead, which would mix keys of other packages with current one. SharedPreferences of other app packages aren't being used currently, so this isn't an issue, this commit just fixes the issue for future. Force exit will also be triggered if Termux is forked and TermuxConstants.TERMUX_PACKAGE_NAME is not updated to the same value as applicationId since TermuxActivity.onCreate() will fail to get SharedPreferences of TermuxConstants.TERMUX_PACKAGE_NAME. Moreover, its normally not allowed to install apps with different signatures, but if its done, we "may" need AndroidManifest `queries` entries in andorid 11, check PackageUtils.getSigningCertificateSHA256DigestForPackage() for details. --- .../java/com/termux/app/TermuxActivity.java | 35 +++++++++++-- .../com/termux/app/TermuxApplication.java | 3 +- .../java/com/termux/app/TermuxService.java | 5 +- .../settings/TermuxPreferencesFragment.java | 4 +- .../termux/DebuggingPreferencesFragment.java | 10 ++-- .../termux/TerminalIOPreferencesFragment.java | 7 ++- .../java/com/termux/app/utils/CrashUtils.java | 4 +- .../com/termux/app/utils/PluginUtils.java | 4 +- .../termux/shared/interact/DialogUtils.java | 9 ++++ .../notification/NotificationUtils.java | 4 +- .../termux/shared/packages/PackageUtils.java | 49 ++++++++++++++++++- .../shared/packages/PermissionUtils.java | 4 +- .../TermuxAppSharedPreferences.java | 45 +++++++++++++++-- .../TermuxTaskerAppSharedPreferences.java | 47 ++++++++++++++++-- .../com/termux/shared/termux/TermuxUtils.java | 3 +- .../main/res/layout/dialog_show_message.xml | 4 +- termux-shared/src/main/res/values/strings.xml | 17 +++++-- 17 files changed, 218 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 9cf396a815..582d141588 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -129,10 +129,16 @@ public final class TermuxActivity extends Activity implements ServiceConnection */ private boolean mIsVisible; + /** + * The {@link TermuxActivity} is in an invalid state and must not be run. + */ + private boolean mIsInvalidState; + private int mNavBarHeight; private int mTerminalToolbarDefaultHeight; + private static final int CONTEXT_MENU_SELECT_URL_ID = 0; private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1; private static final int CONTEXT_MENU_AUTOFILL_ID = 2; @@ -159,8 +165,7 @@ public void onCreate(Bundle savedInstanceState) { // notification with the crash details if it did CrashUtils.notifyCrash(this, LOG_TAG); - // Load termux shared preferences and properties - mPreferences = new TermuxAppSharedPreferences(this); + // Load termux shared properties mProperties = new TermuxAppSharedProperties(this); setActivityTheme(); @@ -169,6 +174,15 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_termux); + // Load termux shared preferences + // This will also fail if TermuxConstants.TERMUX_PACKAGE_NAME does not equal applicationId + mPreferences = TermuxAppSharedPreferences.build(this, true); + if (mPreferences == null) { + // An AlertDialog should have shown to kill the app, so we don't continue running activity code + mIsInvalidState = true; + return; + } + View content = findViewById(android.R.id.content); content.setOnApplyWindowInsetsListener((v, insets) -> { mNavBarHeight = insets.getSystemWindowInsetBottom(); @@ -211,6 +225,8 @@ public void onStart() { Logger.logDebug(LOG_TAG, "onStart"); + if (mIsInvalidState) return; + mIsVisible = true; if (mTermuxService != null) { @@ -236,6 +252,10 @@ public void onStart() { public void onResume() { super.onResume(); + Logger.logVerbose(LOG_TAG, "onResume"); + + if (mIsInvalidState) return; + mTermuxTerminalViewClient.setSoftKeyboardState(true, false); } @@ -302,6 +322,8 @@ protected void onStop() { Logger.logDebug(LOG_TAG, "onStop"); + if (mIsInvalidState) return; + mIsVisible = false; // Store current session in shared preferences so that it can be restored later in @@ -318,12 +340,19 @@ public void onDestroy() { Logger.logDebug(LOG_TAG, "onDestroy"); + if (mIsInvalidState) return; + if (mTermuxService != null) { // Do not leave service and session clients with references to activity. mTermuxService.unsetTermuxTerminalSessionClient(); mTermuxService = null; } - unbindService(this); + + try { + unbindService(this); + } catch (Exception e) { + // ignore. + } } @Override diff --git a/app/src/main/java/com/termux/app/TermuxApplication.java b/app/src/main/java/com/termux/app/TermuxApplication.java index 3bc6689ca4..beb666343c 100644 --- a/app/src/main/java/com/termux/app/TermuxApplication.java +++ b/app/src/main/java/com/termux/app/TermuxApplication.java @@ -20,7 +20,8 @@ public void onCreate() { private void setLogLevel() { // Load the log level from shared preferences and set it to the {@link Logger.CURRENT_LOG_LEVEL} - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(getApplicationContext()); + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(getApplicationContext()); + if (preferences == null) return; preferences.setLogLevel(null, preferences.getLogLevel()); Logger.logDebug("Starting Application"); } diff --git a/app/src/main/java/com/termux/app/TermuxService.java b/app/src/main/java/com/termux/app/TermuxService.java index 39c92023be..3457b7d8e3 100644 --- a/app/src/main/java/com/termux/app/TermuxService.java +++ b/app/src/main/java/com/termux/app/TermuxService.java @@ -743,8 +743,9 @@ private synchronized void updateNotification() { private void setCurrentStoredTerminalSession(TerminalSession session) { if (session == null) return; - // Make the newly created session the current one to be displayed: - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(this); + // Make the newly created session the current one to be displayed + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(this); + if (preferences == null) return; preferences.setCurrentSession(session.mHandle); } diff --git a/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java index 67543f63c0..b92472c672 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java @@ -33,12 +33,12 @@ class TermuxPreferencesDataStore extends PreferenceDataStore { private TermuxPreferencesDataStore(Context context) { mContext = context; - mPreferences = new TermuxAppSharedPreferences(context); + mPreferences = TermuxAppSharedPreferences.build(context, true); } public static synchronized TermuxPreferencesDataStore getInstance(Context context) { if (mInstance == null) { - mInstance = new TermuxPreferencesDataStore(context.getApplicationContext()); + mInstance = new TermuxPreferencesDataStore(context); } return mInstance; } diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java index 279cdc6526..dd5795eb01 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java @@ -28,7 +28,7 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { PreferenceCategory loggingCategory = findPreference("logging"); if (loggingCategory != null) { - final ListPreference logLevelListPreference = setLogLevelListPreferenceData(findPreference("log_level"), getActivity()); + final ListPreference logLevelListPreference = setLogLevelListPreferenceData(findPreference("log_level"), getContext()); loggingCategory.addPreference(logLevelListPreference); } } @@ -60,12 +60,12 @@ class DebuggingPreferencesDataStore extends PreferenceDataStore { private DebuggingPreferencesDataStore(Context context) { mContext = context; - mPreferences = new TermuxAppSharedPreferences(context); + mPreferences = TermuxAppSharedPreferences.build(context, true); } public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { if (mInstance == null) { - mInstance = new DebuggingPreferencesDataStore(context.getApplicationContext()); + mInstance = new DebuggingPreferencesDataStore(context); } return mInstance; } @@ -75,6 +75,7 @@ public static synchronized DebuggingPreferencesDataStore getInstance(Context con @Override @Nullable public String getString(String key, @Nullable String defValue) { + if (mPreferences == null) return null; if (key == null) return null; switch (key) { @@ -87,6 +88,7 @@ public String getString(String key, @Nullable String defValue) { @Override public void putString(String key, @Nullable String value) { + if (mPreferences == null) return; if (key == null) return; switch (key) { @@ -104,6 +106,7 @@ public void putString(String key, @Nullable String value) { @Override public void putBoolean(String key, boolean value) { + if (mPreferences == null) return; if (key == null) return; switch (key) { @@ -123,6 +126,7 @@ public void putBoolean(String key, boolean value) { @Override public boolean getBoolean(String key, boolean defValue) { + if (mPreferences == null) return false; switch (key) { case "terminal_view_key_logging_enabled": return mPreferences.isTerminalViewKeyLoggingEnabled(); diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java index 2e750c85da..109daf9569 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java @@ -33,12 +33,12 @@ class TerminalIOPreferencesDataStore extends PreferenceDataStore { private TerminalIOPreferencesDataStore(Context context) { mContext = context; - mPreferences = new TermuxAppSharedPreferences(context); + mPreferences = TermuxAppSharedPreferences.build(context, true); } public static synchronized TerminalIOPreferencesDataStore getInstance(Context context) { if (mInstance == null) { - mInstance = new TerminalIOPreferencesDataStore(context.getApplicationContext()); + mInstance = new TerminalIOPreferencesDataStore(context); } return mInstance; } @@ -47,6 +47,7 @@ public static synchronized TerminalIOPreferencesDataStore getInstance(Context co @Override public void putBoolean(String key, boolean value) { + if (mPreferences == null) return; if (key == null) return; switch (key) { @@ -63,6 +64,8 @@ public void putBoolean(String key, boolean value) { @Override public boolean getBoolean(String key, boolean defValue) { + if (mPreferences == null) return false; + switch (key) { case "soft_keyboard_enabled": return mPreferences.isSoftKeyboardEnabled(); diff --git a/app/src/main/java/com/termux/app/utils/CrashUtils.java b/app/src/main/java/com/termux/app/utils/CrashUtils.java index f2d6011bc0..670277b75d 100644 --- a/app/src/main/java/com/termux/app/utils/CrashUtils.java +++ b/app/src/main/java/com/termux/app/utils/CrashUtils.java @@ -47,7 +47,9 @@ public static void notifyCrash(final Context context, final String logTagParam) if (context == null) return; - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); + if (preferences == null) return; + // If user has disabled notifications for crashes if (!preferences.areCrashReportNotificationsEnabled()) return; diff --git a/app/src/main/java/com/termux/app/utils/PluginUtils.java b/app/src/main/java/com/termux/app/utils/PluginUtils.java index 72d4af5337..d1b7dba9c4 100644 --- a/app/src/main/java/com/termux/app/utils/PluginUtils.java +++ b/app/src/main/java/com/termux/app/utils/PluginUtils.java @@ -139,7 +139,9 @@ public static void processPluginExecutionCommandError(final Context context, Str } - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); + if (preferences == null) return; + // If user has disabled notifications for plugin, then just return if (!preferences.arePluginErrorNotificationsEnabled() && !forceNotification) return; diff --git a/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java b/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java index d6e8174186..e70c4dcb49 100644 --- a/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/interact/DialogUtils.java @@ -75,6 +75,15 @@ public static void textInput(Activity activity, int titleText, String initialTex dialogHolder[0].show(); } + /** + * Show a message in a dialog + * + * @param context The {@link Context} to use to start the dialog. An {@link Activity} {@link Context} + * must be passed, otherwise exceptions will be thrown. + * @param titleText The title text of the dialog. + * @param messageText The message text of the dialog. + * @param onDismiss The {@link DialogInterface.OnDismissListener} to run when dialog is dismissed. + */ public static void showMessage(Context context, String titleText, String messageText, final DialogInterface.OnDismissListener onDismiss) { AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.Theme_AppCompat_Light_Dialog) diff --git a/termux-shared/src/main/java/com/termux/shared/notification/NotificationUtils.java b/termux-shared/src/main/java/com/termux/shared/notification/NotificationUtils.java index fe9d87e6d1..b31072e87a 100644 --- a/termux-shared/src/main/java/com/termux/shared/notification/NotificationUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/notification/NotificationUtils.java @@ -61,7 +61,9 @@ public static NotificationManager getNotificationManager(final Context context) public synchronized static int getNextNotificationId(final Context context) { if (context == null) return TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); + if (preferences == null) return TermuxPreferenceConstants.TERMUX_APP.DEFAULT_VALUE_KEY_LAST_NOTIFICATION_ID; + int lastNotificationId = preferences.getLastNotificationId(); int nextNotificationId = lastNotificationId + 1; diff --git a/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java b/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java index b1192e7e95..054aa5c82d 100644 --- a/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/packages/PackageUtils.java @@ -7,28 +7,62 @@ import androidx.annotation.NonNull; +import com.termux.shared.R; import com.termux.shared.data.DataUtils; +import com.termux.shared.interact.DialogUtils; import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; import java.security.MessageDigest; +import javax.annotation.Nullable; + public class PackageUtils { + private static final String LOG_TAG = "PackageUtils"; + /** * Get the {@link Context} for the package name. * * @param context The {@link Context} to use to get the {@link Context} of the {@code packageName}. + * @param packageName The package name whose {@link Context} to get. * @return Returns the {@link Context}. This will {@code null} if an exception is raised. */ + @Nullable public static Context getContextForPackage(@NonNull final Context context, String packageName) { try { return context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); } catch (Exception e) { - Logger.logStackTraceWithMessage("Failed to get \"" + packageName + "\" package context.", e); + Logger.logVerbose(LOG_TAG, "Failed to get \"" + packageName + "\" package context: " + e.getMessage()); return null; } } + /** + * Get the {@link Context} for a package name. + * + * @param context The {@link Context} to use to get the {@link Context} of the {@code packageName}. + * @param packageName The package name whose {@link Context} to get. + * @param exitAppOnError If {@code true} and failed to get package context, then a dialog will + * be shown which when dismissed will exit the app. + * @return Returns the {@link Context}. This will {@code null} if an exception is raised. + */ + @Nullable + public static Context getContextForPackageOrExitApp(@NonNull Context context, String packageName, final boolean exitAppOnError) { + Context packageContext = getContextForPackage(context, packageName); + + if (packageContext == null && exitAppOnError) { + String errorMessage = context.getString(R.string.error_get_package_context_failed_message, + packageName, TermuxConstants.TERMUX_GITHUB_REPO_URL); + Logger.logError(LOG_TAG, errorMessage); + DialogUtils.exitAppWithErrorMessage(context, + context.getString(R.string.error_get_package_context_failed_title), + errorMessage); + } + + return packageContext; + } + /** * Get the {@link PackageInfo} for the package associated with the {@code context}. * @@ -46,6 +80,7 @@ public static PackageInfo getPackageInfoForPackage(@NonNull final Context contex * @param flags The flags to pass to {@link PackageManager#getPackageInfo(String, int)}. * @return Returns the {@link PackageInfo}. This will be {@code null} if an exception is raised. */ + @Nullable public static PackageInfo getPackageInfoForPackage(@NonNull final Context context, final int flags) { try { return context.getPackageManager().getPackageInfo(context.getPackageName(), flags); @@ -100,6 +135,7 @@ public static Boolean isAppForPackageADebugBuild(@NonNull final Context context) * @param context The {@link Context} for the package. * @return Returns the {@code versionCode}. This will be {@code null} if an exception is raised. */ + @Nullable public static Integer getVersionCodeForPackage(@NonNull final Context context) { try { return getPackageInfoForPackage(context).versionCode; @@ -114,6 +150,7 @@ public static Integer getVersionCodeForPackage(@NonNull final Context context) { * @param context The {@link Context} for the package. * @return Returns the {@code versionName}. This will be {@code null} if an exception is raised. */ + @Nullable public static String getVersionNameForPackage(@NonNull final Context context) { try { return getPackageInfoForPackage(context).versionName; @@ -122,15 +159,23 @@ public static String getVersionNameForPackage(@NonNull final Context context) { } } - /** * Get the {@code SHA-256 digest} of signing certificate for the package associated with the {@code context}. * * @param context The {@link Context} for the package. * @return Returns the{@code SHA-256 digest}. This will be {@code null} if an exception is raised. */ + @Nullable public static String getSigningCertificateSHA256DigestForPackage(@NonNull final Context context) { try { + /* + * Todo: We may need AndroidManifest queries entries if package is installed but with a different signature on android 11 + * https://developer.android.com/training/package-visibility + * Need a device that allows (manual) installation of apk with mismatched signature of + * sharedUserId apps to test. Currently, if its done, PackageManager just doesn't load + * the package and removes its apk automatically if its installed as a user app instead of system app + * W/PackageManager: Failed to parse /path/to/com.termux.tasker.apk: Signature mismatch for shared user: SharedUserSetting{xxxxxxx com.termux/10xxx} + */ PackageInfo packageInfo = getPackageInfoForPackage(context, PackageManager.GET_SIGNATURES); if (packageInfo == null) return null; return DataUtils.bytesToHex(MessageDigest.getInstance("SHA-256").digest(packageInfo.signatures[0].toByteArray())); diff --git a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java index 65c0f73db0..e6a8569c03 100644 --- a/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/packages/PermissionUtils.java @@ -75,7 +75,9 @@ public static boolean validateDisplayOverOtherAppsPermissionForPostAndroid10(Con if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return true; if (!PermissionUtils.checkDisplayOverOtherAppsPermission(context)) { - TermuxAppSharedPreferences preferences = new TermuxAppSharedPreferences(context); + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context); + if (preferences == null) return false; + if (preferences.arePluginErrorNotificationsEnabled()) Logger.showToast(context, context.getString(R.string.error_display_over_other_apps_permission_not_granted), true); return false; diff --git a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java index 4e66c8f599..c2979ee08c 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxAppSharedPreferences.java @@ -1,16 +1,20 @@ package com.termux.shared.settings.preferences; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.util.TypedValue; +import androidx.annotation.NonNull; + +import com.termux.shared.packages.PackageUtils; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.logger.Logger; -import com.termux.shared.termux.TermuxUtils; import com.termux.shared.data.DataUtils; import com.termux.shared.settings.preferences.TermuxPreferenceConstants.TERMUX_APP; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class TermuxAppSharedPreferences { @@ -24,15 +28,48 @@ public class TermuxAppSharedPreferences { private static final String LOG_TAG = "TermuxAppSharedPreferences"; - public TermuxAppSharedPreferences(@Nonnull Context context) { - // We use the default context if failed to get termux package context - mContext = DataUtils.getDefaultIfNull(TermuxUtils.getTermuxPackageContext(context), context); + private TermuxAppSharedPreferences(@Nonnull Context context) { + mContext = context; mSharedPreferences = getPrivateSharedPreferences(mContext); setFontVariables(context); } + /** + * Get the {@link Context} for a package name. + * + * @param context The {@link Context} to use to get the {@link Context} of the + * {@link TermuxConstants#TERMUX_PACKAGE_NAME}. + * @return Returns the {@link TermuxAppSharedPreferences}. This will {@code null} if an exception is raised. + */ + @Nullable + public static TermuxAppSharedPreferences build(@NonNull final Context context) { + Context termuxPackageContext = PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_PACKAGE_NAME); + if (termuxPackageContext == null) + return null; + else + return new TermuxAppSharedPreferences(termuxPackageContext); + } + + /** + * Get the {@link Context} for a package name. + * + * @param context The {@link Activity} to use to get the {@link Context} of the + * {@link TermuxConstants#TERMUX_PACKAGE_NAME}. + * @param exitAppOnError If {@code true} and failed to get package context, then a dialog will + * be shown which when dismissed will exit the app. + * @return Returns the {@link TermuxAppSharedPreferences}. This will {@code null} if an exception is raised. + */ + public static TermuxAppSharedPreferences build(@NonNull final Context context, final boolean exitAppOnError) { + Context termuxPackageContext = PackageUtils.getContextForPackageOrExitApp(context, TermuxConstants.TERMUX_PACKAGE_NAME, exitAppOnError); + if (termuxPackageContext == null) + return null; + else + return new TermuxAppSharedPreferences(termuxPackageContext); + } + private static SharedPreferences getPrivateSharedPreferences(Context context) { + if (context == null) return null; return SharedPreferenceUtils.getPrivateSharedPreferences(context, TermuxConstants.TERMUX_DEFAULT_PREFERENCES_FILE_BASENAME_WITHOUT_EXTENSION); } diff --git a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxTaskerAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxTaskerAppSharedPreferences.java index b2f0ba7846..412c9b2299 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxTaskerAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/preferences/TermuxTaskerAppSharedPreferences.java @@ -1,15 +1,18 @@ package com.termux.shared.settings.preferences; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; +import androidx.annotation.NonNull; + +import com.termux.shared.packages.PackageUtils; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.settings.preferences.TermuxPreferenceConstants.TERMUX_TASKER_APP; -import com.termux.shared.data.DataUtils; import com.termux.shared.logger.Logger; -import com.termux.shared.termux.TermuxUtils; import javax.annotation.Nonnull; +import javax.annotation.Nullable; public class TermuxTaskerAppSharedPreferences { @@ -20,18 +23,52 @@ public class TermuxTaskerAppSharedPreferences { private static final String LOG_TAG = "TermuxTaskerAppSharedPreferences"; - public TermuxTaskerAppSharedPreferences(@Nonnull Context context) { - // We use the default context if failed to get termux-tasker package context - mContext = DataUtils.getDefaultIfNull(TermuxUtils.getTermuxTaskerPackageContext(context), context); + private TermuxTaskerAppSharedPreferences(@Nonnull Context context) { + mContext = context; mSharedPreferences = getPrivateSharedPreferences(mContext); mMultiProcessSharedPreferences = getPrivateAndMultiProcessSharedPreferences(mContext); } + /** + * Get the {@link Context} for a package name. + * + * @param context The {@link Context} to use to get the {@link Context} of the + * {@link TermuxConstants#TERMUX_TASKER_PACKAGE_NAME}. + * @return Returns the {@link TermuxTaskerAppSharedPreferences}. This will {@code null} if an exception is raised. + */ + @Nullable + public static TermuxTaskerAppSharedPreferences build(@NonNull final Context context) { + Context termuxTaskerPackageContext = PackageUtils.getContextForPackage(context, TermuxConstants.TERMUX_TASKER_PACKAGE_NAME); + if (termuxTaskerPackageContext == null) + return null; + else + return new TermuxTaskerAppSharedPreferences(termuxTaskerPackageContext); + } + + /** + * Get the {@link Context} for a package name. + * + * @param context The {@link Activity} to use to get the {@link Context} of the + * {@link TermuxConstants#TERMUX_TASKER_PACKAGE_NAME}. + * @param exitAppOnError If {@code true} and failed to get package context, then a dialog will + * be shown which when dismissed will exit the app. + * @return Returns the {@link TermuxAppSharedPreferences}. This will {@code null} if an exception is raised. + */ + public static TermuxTaskerAppSharedPreferences build(@NonNull final Context context, final boolean exitAppOnError) { + Context termuxTaskerPackageContext = PackageUtils.getContextForPackageOrExitApp(context, TermuxConstants.TERMUX_TASKER_PACKAGE_NAME, exitAppOnError); + if (termuxTaskerPackageContext == null) + return null; + else + return new TermuxTaskerAppSharedPreferences(termuxTaskerPackageContext); + } + private static SharedPreferences getPrivateSharedPreferences(Context context) { + if (context == null) return null; return SharedPreferenceUtils.getPrivateSharedPreferences(context, TermuxConstants.TERMUX_TASKER_DEFAULT_PREFERENCES_FILE_BASENAME_WITHOUT_EXTENSION); } private static SharedPreferences getPrivateAndMultiProcessSharedPreferences(Context context) { + if (context == null) return null; return SharedPreferenceUtils.getPrivateAndMultiProcessSharedPreferences(context, TermuxConstants.TERMUX_TASKER_DEFAULT_PREFERENCES_FILE_BASENAME_WITHOUT_EXTENSION); } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java index 706f556ba6..6ad9a18e03 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxUtils.java @@ -228,7 +228,6 @@ public static String getAppInfoMarkdownStringInner(@NonNull final Context contex appendPropertyToMarkdown(markdownString,"IS_DEBUG_BUILD", PackageUtils.isAppForPackageADebugBuild(context)); String signingCertificateSHA256Digest = PackageUtils.getSigningCertificateSHA256DigestForPackage(context); - Logger.logError("'" + signingCertificateSHA256Digest + "'"); if (signingCertificateSHA256Digest != null) { appendPropertyToMarkdown(markdownString,"APK_RELEASE", getAPKRelease(signingCertificateSHA256Digest)); appendPropertyToMarkdown(markdownString,"SIGNING_CERTIFICATE_SHA256_DIGEST", signingCertificateSHA256Digest); @@ -381,7 +380,7 @@ public static String getImportantLinksMarkdownString(@NonNull final Context cont */ public static String geAPTInfoMarkdownString(@NonNull final Context context) { - String aptInfoScript = null; + String aptInfoScript; InputStream inputStream = context.getResources().openRawResource(com.termux.shared.R.raw.apt_info_script); try { aptInfoScript = IOUtils.toString(inputStream, Charset.defaultCharset()); diff --git a/termux-shared/src/main/res/layout/dialog_show_message.xml b/termux-shared/src/main/res/layout/dialog_show_message.xml index f074d08c10..f5dd82f13c 100644 --- a/termux-shared/src/main/res/layout/dialog_show_message.xml +++ b/termux-shared/src/main/res/layout/dialog_show_message.xml @@ -35,8 +35,10 @@ android:id="@+id/dialog_message" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:autoLink="web|email" android:textSize="14sp" - android:textColor="@android:color/tab_indicator_text"/> + android:textColor="@android:color/tab_indicator_text" + android:textColorLink="@android:color/black"/> diff --git a/termux-shared/src/main/res/values/strings.xml b/termux-shared/src/main/res/values/strings.xml index 71bd7bca5e..d9eedf3316 100644 --- a/termux-shared/src/main/res/values/strings.xml +++ b/termux-shared/src/main/res/values/strings.xml @@ -61,6 +61,13 @@ The %1$s at path is not executable. Permission Denied. + + + Failed To Get Package Context + Failed to get package context for the \"%1$s\" package. This may be because the app package is not installed or it has different APK signature from the current app. Check install instruction at %2$s for more details. + + + Please grant permissions on next screen &TERMUX_APP_NAME; requires \"Display over other apps\" permission to start terminal sessions from background on Android >= 10. Grants it from Settings -> Apps -> &TERMUX_APP_NAME; -> Advanced @@ -72,11 +79,6 @@ - - If you want to report this issue, then copy its text from the options menu (3-dots on top right) and post an issue on one of the following links.\n\nIf you are posting a Termux app crash report, then please provide details on what you were doing that caused the crash and how to reproduce it, if possible.\n\nIf you are posting an issue on Github, then post it in the repository at which the report belongs at. You may optionally remove any device specific info that you consider private or don\'t want to share or that is not relevant to the issue. - - - Sending SIGKILL to process on user request or because android is killing the execution service Execution has been cancelled since execution service is being killed @@ -87,6 +89,11 @@ + + If you want to report this issue, then copy its text from the options menu (3-dots on top right) and post an issue on one of the following links.\n\nIf you are posting a Termux app crash report, then please provide details on what you were doing that caused the crash and how to reproduce it, if possible.\n\nIf you are posting an issue on Github, then post it in the repository at which the report belongs at. You may optionally remove any device specific info that you consider private or don\'t want to share or that is not relevant to the issue. + + + Log Level "Off" From 6de3713049e61a838fe094192bb27d0f691b318e Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 05:19:18 +0500 Subject: [PATCH 26/37] Add in-app Donation link in Termux Settings for non google playstore releases The `TermuxConstants` class has been updated to `v0.22.0`. Check its Changelog sections for info on changes. --- .../app/activities/SettingsActivity.java | 32 ++++++++- app/src/main/res/values/strings.xml | 7 +- app/src/main/res/xml/root_preferences.xml | 8 ++- .../termux/shared/interact/ShareUtils.java | 20 ++++++ .../termux/shared/termux/TermuxConstants.java | 67 ++++++++++++------- 5 files changed, 105 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/termux/app/activities/SettingsActivity.java b/app/src/main/java/com/termux/app/activities/SettingsActivity.java index afb7373e97..08ac7fef59 100644 --- a/app/src/main/java/com/termux/app/activities/SettingsActivity.java +++ b/app/src/main/java/com/termux/app/activities/SettingsActivity.java @@ -11,6 +11,8 @@ import com.termux.R; import com.termux.app.models.ReportInfo; import com.termux.app.models.UserAction; +import com.termux.shared.interact.ShareUtils; +import com.termux.shared.packages.PackageUtils; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxUtils; @@ -44,10 +46,11 @@ public static class RootPreferencesFragment extends PreferenceFragmentCompat { public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.root_preferences, rootKey); - setAboutOnPreferenceClickListener(); + configureAboutPreference(); + configureDonatePreference(); } - private void setAboutOnPreferenceClickListener() { + private void configureAboutPreference() { Context context = getContext(); Preference about = findPreference("about"); if (context != null && about != null) { @@ -70,6 +73,31 @@ private void setAboutOnPreferenceClickListener() { }); } } + + private void configureDonatePreference() { + Context context = getContext(); + Preference donate = findPreference("donate"); + if (context != null && donate != null) { + String signingCertificateSHA256Digest = PackageUtils.getSigningCertificateSHA256DigestForPackage(context); + if (signingCertificateSHA256Digest != null) { + // If APK is a Google Playstore release, then do not show the donation link + // since Termux isn't exempted from the playstore policy donation links restriction + // Check Fund solicitations: https://pay.google.com/intl/en_in/about/policy/ + String apkRelease = TermuxUtils.getAPKRelease(signingCertificateSHA256Digest); + if (apkRelease == null || apkRelease.equals(TermuxConstants.APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST)) { + donate.setVisible(false); + return; + } else { + donate.setVisible(true); + } + } + + donate.setOnPreferenceClickListener(preference -> { + ShareUtils.openURL(context, TermuxConstants.TERMUX_DONATE_URL); + return true; + }); + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0c00943eff..c149c4cba3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,7 +170,10 @@ Soft keyboard will be enabled only if no hardware keyboard is connected. - - About + + About + + + Donate diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 297b490366..f8efef7b70 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -8,8 +8,14 @@ + + diff --git a/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java b/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java index c4efab2352..2f38a3a929 100644 --- a/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/interact/ShareUtils.java @@ -4,6 +4,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; +import android.net.Uri; import androidx.core.content.ContextCompat; @@ -13,6 +14,8 @@ public class ShareUtils { + private static final String LOG_TAG = "ShareUtils"; + /** * Open the system app chooser that allows the user to select which app to send the intent. * @@ -68,4 +71,21 @@ public static void copyTextToClipboard(final Context context, final String text, } } + /** + * Open a url. + * + * @param context The context for operations. + * @param url The url to open. + */ + public static void openURL(final Context context, final String url) { + if (context == null || url == null || url.isEmpty()) return; + try { + Uri uri = Uri.parse(url); + Intent intent = new Intent(Intent.ACTION_VIEW, uri); + context.startActivity(intent); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to open the url \"" + url + "\"", e); + } + } + } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 2f4284d9ac..3d79dee7d7 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -144,8 +144,11 @@ * * - 0.21.0 (2021-05-13) * - Added `APK_RELEASE_FDROID`, `APK_RELEASE_FDROID_SIGNING_CERTIFICATE_SHA256_DIGEST`, - * - Added `APK_RELEASE_GITHUB_DEBUG_BUILD`, `APK_RELEASE_GITHUB_DEBUG_BUILD_SIGNING_CERTIFICATE_SHA256_DIGEST`, - * - Added `APK_RELEASE_GOOGLE_PLAYSTORE`, `APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST`. + * `APK_RELEASE_GITHUB_DEBUG_BUILD`, `APK_RELEASE_GITHUB_DEBUG_BUILD_SIGNING_CERTIFICATE_SHA256_DIGEST`, + * `APK_RELEASE_GOOGLE_PLAYSTORE`, `APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST`. + * + * - 0.22.0 (2021-05-13) + * - Added `TERMUX_DONATE_URL`. * */ @@ -200,24 +203,6 @@ public final class TermuxConstants { /** Termux Github organization url */ public static final String TERMUX_GITHUB_ORGANIZATION_URL = "https://github.com" + "/" + TERMUX_GITHUB_ORGANIZATION_NAME; // Default: "https://github.com/termux" - /** Termux support email url */ - public static final String TERMUX_SUPPORT_EMAIL_URL = "termuxreports@groups.io"; // Default: "termuxreports@groups.io" - - /** Termux support email mailto url */ - public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:termuxreports@groups.io" - - /** Termux Wiki */ - public static final String TERMUX_WIKI = "Termux Wiki"; // Default: "Termux Wiki" - - /** Termux Wiki url */ - public static final String TERMUX_WIKI_URL = "https://wiki.termux.com"; // Default: "https://wiki.termux.com" - - /** Termux Reddit subreddit */ - public static final String TERMUX_REDDIT_SUBREDDIT = "r/termux"; // Default: "r/termux" - - /** Termux Reddit subreddit url */ - public static final String TERMUX_REDDIT_SUBREDDIT_URL = "https://www.reddit.com/r/termux"; // Default: "https://www.reddit.com/r/termux" - /** F-Droid packages base url */ public static final String FDROID_PACKAGES_BASE_URL = "https://f-droid.org/en/packages"; // Default: "https://f-droid.org/en/packages" @@ -239,8 +224,6 @@ public final class TermuxConstants { public static final String TERMUX_GITHUB_REPO_URL = TERMUX_GITHUB_ORGANIZATION_URL + "/" + TERMUX_GITHUB_REPO_NAME; // Default: "https://github.com/termux/termux-app" /** Termux Github issues repo url */ public static final String TERMUX_GITHUB_ISSUES_REPO_URL = TERMUX_GITHUB_REPO_URL + "/issues"; // Default: "https://github.com/termux/termux-app/issues" - /** Termux Github wiki repo url */ - public static final String TERMUX_GITHUB_WIKI_REPO_URL = TERMUX_GITHUB_REPO_URL + "/wiki"; // Default: "https://github.com/termux/termux-app/wiki" /** Termux F-Droid package url */ public static final String TERMUX_FDROID_PACKAGE_URL = FDROID_PACKAGES_BASE_URL + "/" + TERMUX_PACKAGE_NAME; // Default: "https://f-droid.org/en/packages/com.termux" @@ -392,8 +375,6 @@ public final class TermuxConstants { public static final String TERMUX_PACKAGES_GITHUB_REPO_URL = TERMUX_GITHUB_ORGANIZATION_URL + "/" + TERMUX_PACKAGES_GITHUB_REPO_NAME; // Default: "https://github.com/termux/termux-packages" /** Termux Packages Github issues repo url */ public static final String TERMUX_PACKAGES_GITHUB_ISSUES_REPO_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/issues"; // Default: "https://github.com/termux/termux-packages/issues" - /** Termux Packages wiki repo url */ - public static final String TERMUX_PACKAGES_GITHUB_WIKI_REPO_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/wiki"; // Default: "https://github.com/termux/termux-packages/wiki" /** Termux Game Packages Github repo name */ @@ -439,6 +420,44 @@ public final class TermuxConstants { + /* + * Termux miscellaneous urls. + */ + + /** Termux Wiki */ + public static final String TERMUX_WIKI = TERMUX_APP_NAME + " Wiki"; // Default: "Termux Wiki" + + /** Termux Wiki url */ + public static final String TERMUX_WIKI_URL = "https://wiki.termux.com"; // Default: "https://wiki.termux.com" + + /** Termux Github wiki repo url */ + public static final String TERMUX_GITHUB_WIKI_REPO_URL = TERMUX_GITHUB_REPO_URL + "/wiki"; // Default: "https://github.com/termux/termux-app/wiki" + + /** Termux Packages wiki repo url */ + public static final String TERMUX_PACKAGES_GITHUB_WIKI_REPO_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/wiki"; // Default: "https://github.com/termux/termux-packages/wiki" + + + /** Termux support email url */ + public static final String TERMUX_SUPPORT_EMAIL_URL = "termuxreports@groups.io"; // Default: "termuxreports@groups.io" + + /** Termux support email mailto url */ + public static final String TERMUX_SUPPORT_EMAIL_MAILTO_URL = "mailto:" + TERMUX_SUPPORT_EMAIL_URL; // Default: "mailto:termuxreports@groups.io" + + + /** Termux Reddit subreddit */ + public static final String TERMUX_REDDIT_SUBREDDIT = "r/termux"; // Default: "r/termux" + + /** Termux Reddit subreddit url */ + public static final String TERMUX_REDDIT_SUBREDDIT_URL = "https://www.reddit.com/r/termux"; // Default: "https://www.reddit.com/r/termux" + + + /** Termux donate url */ + public static final String TERMUX_DONATE_URL = TERMUX_PACKAGES_GITHUB_REPO_URL + "/wiki/Donate"; // Default: "https://github.com/termux/termux-packages/wiki/Donate" + + + + + /* * Termux app core directory paths. */ From 339b2a24a2a05416cd3b313d94551cc7ba5d59fa Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 06:47:42 +0500 Subject: [PATCH 27/37] Add support for setting Termux:Tasker log level from TermuxSettings --- .../app/activities/SettingsActivity.java | 41 ++++--- .../settings/TermuxPreferencesFragment.java | 5 +- .../TermuxTaskerPreferencesFragment.java | 49 +++++++++ .../termux/DebuggingPreferencesFragment.java | 25 +++-- .../termux/TerminalIOPreferencesFragment.java | 5 +- .../DebuggingPreferencesFragment.java | 101 ++++++++++++++++++ app/src/main/res/values/strings.xml | 5 + app/src/main/res/xml/root_preferences.xml | 7 ++ .../termux_tasker_debugging_preferences.xml | 15 +++ .../res/xml/termux_tasker_preferences.xml | 8 ++ 10 files changed, 239 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/termux/app/fragments/settings/TermuxTaskerPreferencesFragment.java create mode 100644 app/src/main/java/com/termux/app/fragments/settings/termux_tasker/DebuggingPreferencesFragment.java create mode 100644 app/src/main/res/xml/termux_tasker_debugging_preferences.xml create mode 100644 app/src/main/res/xml/termux_tasker_preferences.xml diff --git a/app/src/main/java/com/termux/app/activities/SettingsActivity.java b/app/src/main/java/com/termux/app/activities/SettingsActivity.java index 08ac7fef59..5ef65a3d00 100644 --- a/app/src/main/java/com/termux/app/activities/SettingsActivity.java +++ b/app/src/main/java/com/termux/app/activities/SettingsActivity.java @@ -3,6 +3,7 @@ import android.content.Context; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.preference.Preference; @@ -13,6 +14,7 @@ import com.termux.app.models.UserAction; import com.termux.shared.interact.ShareUtils; import com.termux.shared.packages.PackageUtils; +import com.termux.shared.settings.preferences.TermuxTaskerAppSharedPreferences; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.termux.TermuxUtils; @@ -44,17 +46,29 @@ public boolean onSupportNavigateUp() { public static class RootPreferencesFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + setPreferencesFromResource(R.xml.root_preferences, rootKey); - configureAboutPreference(); - configureDonatePreference(); + configureTermuxTaskerPreference(context); + configureAboutPreference(context); + configureDonatePreference(context); } - private void configureAboutPreference() { - Context context = getContext(); - Preference about = findPreference("about"); - if (context != null && about != null) { - about.setOnPreferenceClickListener(preference -> { + private void configureTermuxTaskerPreference(@NonNull Context context) { + Preference termuxTaskerPrefernce = findPreference("termux_tasker"); + if (termuxTaskerPrefernce != null) { + TermuxTaskerAppSharedPreferences preferences = TermuxTaskerAppSharedPreferences.build(context, false); + // If failed to get app preferences, then likely app is not installed, so do not show its preference + termuxTaskerPrefernce.setVisible(preferences != null); + } + } + + private void configureAboutPreference(@NonNull Context context) { + Preference aboutPreference = findPreference("about"); + if (aboutPreference != null) { + aboutPreference.setOnPreferenceClickListener(preference -> { String title = "About"; StringBuilder aboutString = new StringBuilder(); @@ -74,10 +88,9 @@ private void configureAboutPreference() { } } - private void configureDonatePreference() { - Context context = getContext(); - Preference donate = findPreference("donate"); - if (context != null && donate != null) { + private void configureDonatePreference(@NonNull Context context) { + Preference donatePreference = findPreference("donate"); + if (donatePreference != null) { String signingCertificateSHA256Digest = PackageUtils.getSigningCertificateSHA256DigestForPackage(context); if (signingCertificateSHA256Digest != null) { // If APK is a Google Playstore release, then do not show the donation link @@ -85,14 +98,14 @@ private void configureDonatePreference() { // Check Fund solicitations: https://pay.google.com/intl/en_in/about/policy/ String apkRelease = TermuxUtils.getAPKRelease(signingCertificateSHA256Digest); if (apkRelease == null || apkRelease.equals(TermuxConstants.APK_RELEASE_GOOGLE_PLAYSTORE_SIGNING_CERTIFICATE_SHA256_DIGEST)) { - donate.setVisible(false); + donatePreference.setVisible(false); return; } else { - donate.setVisible(true); + donatePreference.setVisible(true); } } - donate.setOnPreferenceClickListener(preference -> { + donatePreference.setOnPreferenceClickListener(preference -> { ShareUtils.openURL(context, TermuxConstants.TERMUX_DONATE_URL); return true; }); diff --git a/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java index b92472c672..273fa0f552 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/TermuxPreferencesFragment.java @@ -16,8 +16,11 @@ public class TermuxPreferencesFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setPreferenceDataStore(TermuxPreferencesDataStore.getInstance(getContext())); + preferenceManager.setPreferenceDataStore(TermuxPreferencesDataStore.getInstance(context)); setPreferencesFromResource(R.xml.termux_preferences, rootKey); } diff --git a/app/src/main/java/com/termux/app/fragments/settings/TermuxTaskerPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/TermuxTaskerPreferencesFragment.java new file mode 100644 index 0000000000..b86685b60b --- /dev/null +++ b/app/src/main/java/com/termux/app/fragments/settings/TermuxTaskerPreferencesFragment.java @@ -0,0 +1,49 @@ +package com.termux.app.fragments.settings; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Keep; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.termux.R; +import com.termux.shared.settings.preferences.TermuxTaskerAppSharedPreferences; + +@Keep +public class TermuxTaskerPreferencesFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(TermuxTaskerPreferencesDataStore.getInstance(context)); + + setPreferencesFromResource(R.xml.termux_tasker_preferences, rootKey); + } + +} + +class TermuxTaskerPreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + private final TermuxTaskerAppSharedPreferences mPreferences; + + private static TermuxTaskerPreferencesDataStore mInstance; + + private TermuxTaskerPreferencesDataStore(Context context) { + mContext = context; + mPreferences = TermuxTaskerAppSharedPreferences.build(context, true); + } + + public static synchronized TermuxTaskerPreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new TermuxTaskerPreferencesDataStore(context); + } + return mInstance; + } + +} diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java index dd5795eb01..72ced348a6 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/DebuggingPreferencesFragment.java @@ -4,6 +4,7 @@ import android.os.Bundle; import androidx.annotation.Keep; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.preference.ListPreference; import androidx.preference.PreferenceCategory; @@ -20,20 +21,32 @@ public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(getContext())); + preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(context)); setPreferencesFromResource(R.xml.termux_debugging_preferences, rootKey); + configureLoggingPreferences(context); + } + + private void configureLoggingPreferences(@NonNull Context context) { PreferenceCategory loggingCategory = findPreference("logging"); + if (loggingCategory == null) return; + + ListPreference logLevelListPreference = findPreference("log_level"); + if (logLevelListPreference != null) { + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context, true); + if (preferences == null) return; - if (loggingCategory != null) { - final ListPreference logLevelListPreference = setLogLevelListPreferenceData(findPreference("log_level"), getContext()); + setLogLevelListPreferenceData(logLevelListPreference, context, preferences.getLogLevel()); loggingCategory.addPreference(logLevelListPreference); } } - protected ListPreference setLogLevelListPreferenceData(ListPreference logLevelListPreference, Context context) { + public static ListPreference setLogLevelListPreferenceData(ListPreference logLevelListPreference, Context context, int logLevel) { if (logLevelListPreference == null) logLevelListPreference = new ListPreference(context); @@ -43,8 +56,8 @@ protected ListPreference setLogLevelListPreferenceData(ListPreference logLevelLi logLevelListPreference.setEntryValues(logLevels); logLevelListPreference.setEntries(logLevelLabels); - logLevelListPreference.setValue(String.valueOf(Logger.getLogLevel())); - logLevelListPreference.setDefaultValue(Logger.getLogLevel()); + logLevelListPreference.setValue(String.valueOf(logLevel)); + logLevelListPreference.setDefaultValue(Logger.DEFAULT_LOG_LEVEL); return logLevelListPreference; } diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java index 109daf9569..46e7504940 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TerminalIOPreferencesFragment.java @@ -16,8 +16,11 @@ public class TerminalIOPreferencesFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(getContext())); + preferenceManager.setPreferenceDataStore(TerminalIOPreferencesDataStore.getInstance(context)); setPreferencesFromResource(R.xml.termux_terminal_io_preferences, rootKey); } diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux_tasker/DebuggingPreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux_tasker/DebuggingPreferencesFragment.java new file mode 100644 index 0000000000..a66708140f --- /dev/null +++ b/app/src/main/java/com/termux/app/fragments/settings/termux_tasker/DebuggingPreferencesFragment.java @@ -0,0 +1,101 @@ +package com.termux.app.fragments.settings.termux_tasker; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.preference.ListPreference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; + +import com.termux.R; +import com.termux.shared.settings.preferences.TermuxTaskerAppSharedPreferences; + +@Keep +public class DebuggingPreferencesFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(DebuggingPreferencesDataStore.getInstance(context)); + + setPreferencesFromResource(R.xml.termux_tasker_debugging_preferences, rootKey); + + configureLoggingPreferences(context); + } + + private void configureLoggingPreferences(@NonNull Context context) { + PreferenceCategory loggingCategory = findPreference("logging"); + if (loggingCategory == null) return; + + ListPreference logLevelListPreference = findPreference("log_level"); + if (logLevelListPreference != null) { + TermuxTaskerAppSharedPreferences preferences = TermuxTaskerAppSharedPreferences.build(context, true); + if (preferences == null) return; + + com.termux.app.fragments.settings.termux.DebuggingPreferencesFragment. + setLogLevelListPreferenceData(logLevelListPreference, context, preferences.getLogLevel(true)); + loggingCategory.addPreference(logLevelListPreference); + } + } +} + +class DebuggingPreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + private final TermuxTaskerAppSharedPreferences mPreferences; + + private static DebuggingPreferencesDataStore mInstance; + + private DebuggingPreferencesDataStore(Context context) { + mContext = context; + mPreferences = TermuxTaskerAppSharedPreferences.build(context, true); + } + + public static synchronized DebuggingPreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new DebuggingPreferencesDataStore(context); + } + return mInstance; + } + + + + @Override + @Nullable + public String getString(String key, @Nullable String defValue) { + if (mPreferences == null) return null; + if (key == null) return null; + + switch (key) { + case "log_level": + return String.valueOf(mPreferences.getLogLevel(true)); + default: + return null; + } + } + + @Override + public void putString(String key, @Nullable String value) { + if (mPreferences == null) return; + if (key == null) return; + + switch (key) { + case "log_level": + if (value != null) { + mPreferences.setLogLevel(mContext, Integer.parseInt(value), true); + } + break; + default: + break; + } + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c149c4cba3..fe0851c995 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,6 +170,11 @@ Soft keyboard will be enabled only if no hardware keyboard is connected. + + &TERMUX_TASKER_APP_NAME; + Preferences for &TERMUX_TASKER_APP_NAME; app + + About diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index f8efef7b70..39c6093ed0 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -6,6 +6,13 @@ app:summary="@string/termux_preferences_summary" app:fragment="com.termux.app.fragments.settings.TermuxPreferencesFragment"/> + + + + + + + + + + diff --git a/app/src/main/res/xml/termux_tasker_preferences.xml b/app/src/main/res/xml/termux_tasker_preferences.xml new file mode 100644 index 0000000000..9aa72485bc --- /dev/null +++ b/app/src/main/res/xml/termux_tasker_preferences.xml @@ -0,0 +1,8 @@ + + + + + From 2f828255eeca61e1e92442c1a48b7c409830549d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 07:05:14 +0500 Subject: [PATCH 28/37] Generate potentially long running reports in background threads instead of main UI thread --- .../app/activities/SettingsActivity.java | 23 +++++++------ .../terminal/TermuxTerminalViewClient.java | 32 ++++++++++++------- app/src/main/res/values/strings.xml | 4 ++- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/termux/app/activities/SettingsActivity.java b/app/src/main/java/com/termux/app/activities/SettingsActivity.java index 5ef65a3d00..3e3c23dea4 100644 --- a/app/src/main/java/com/termux/app/activities/SettingsActivity.java +++ b/app/src/main/java/com/termux/app/activities/SettingsActivity.java @@ -69,19 +69,24 @@ private void configureAboutPreference(@NonNull Context context) { Preference aboutPreference = findPreference("about"); if (aboutPreference != null) { aboutPreference.setOnPreferenceClickListener(preference -> { - String title = "About"; + new Thread() { + @Override + public void run() { + String title = "About"; - StringBuilder aboutString = new StringBuilder(); - aboutString.append(TermuxUtils.getAppInfoMarkdownString(context, false)); + StringBuilder aboutString = new StringBuilder(); + aboutString.append(TermuxUtils.getAppInfoMarkdownString(context, false)); - String termuxPluginAppsInfo = TermuxUtils.getTermuxPluginAppsInfoMarkdownString(context); - if (termuxPluginAppsInfo != null) - aboutString.append("\n\n").append(termuxPluginAppsInfo); + String termuxPluginAppsInfo = TermuxUtils.getTermuxPluginAppsInfoMarkdownString(context); + if (termuxPluginAppsInfo != null) + aboutString.append("\n\n").append(termuxPluginAppsInfo); - aboutString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); - aboutString.append("\n\n").append(TermuxUtils.getImportantLinksMarkdownString(context)); + aboutString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(context)); + aboutString.append("\n\n").append(TermuxUtils.getImportantLinksMarkdownString(context)); - ReportActivity.startReportActivity(context, new ReportInfo(UserAction.ABOUT, TermuxConstants.TERMUX_APP.TERMUX_SETTINGS_ACTIVITY_NAME, title, null, aboutString.toString(), null, false)); + ReportActivity.startReportActivity(context, new ReportInfo(UserAction.ABOUT, TermuxConstants.TERMUX_APP.TERMUX_SETTINGS_ACTIVITY_NAME, title, null, aboutString.toString(), null, false)); + } + }.start(); return true; }); diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 8b2a3f9451..7700cc6252 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -494,26 +494,34 @@ public void reportIssueFromTranscript() { TerminalSession session = mActivity.getCurrentSession(); if (session == null) return; - String transcriptText = ShellUtils.getTerminalSessionTranscriptText(session, false, true); + final String transcriptText = ShellUtils.getTerminalSessionTranscriptText(session, false, true); if (transcriptText == null) return; - transcriptText = DataUtils.getTruncatedCommandOutput(transcriptText, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, true, false).trim(); + Logger.showToast(mActivity, mActivity.getString(R.string.msg_generating_report), true); + + new Thread() { + @Override + public void run() { - StringBuilder reportString = new StringBuilder(); + String transcriptTextTruncated = DataUtils.getTruncatedCommandOutput(transcriptText, DataUtils.TRANSACTION_SIZE_LIMIT_IN_BYTES, false, true, false).trim(); - String title = TermuxConstants.TERMUX_APP_NAME + " Report Issue"; + StringBuilder reportString = new StringBuilder(); - reportString.append("## Transcript\n"); - reportString.append("\n").append(MarkdownUtils.getMarkdownCodeForString(transcriptText, true)); + String title = TermuxConstants.TERMUX_APP_NAME + " Report Issue"; - reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(mActivity, true)); - reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(mActivity)); + reportString.append("## Transcript\n"); + reportString.append("\n").append(MarkdownUtils.getMarkdownCodeForString(transcriptTextTruncated, true)); - String termuxAptInfo = TermuxUtils.geAPTInfoMarkdownString(mActivity); - if (termuxAptInfo != null) - reportString.append("\n\n").append(termuxAptInfo); + reportString.append("\n\n").append(TermuxUtils.getAppInfoMarkdownString(mActivity, true)); + reportString.append("\n\n").append(TermuxUtils.getDeviceInfoMarkdownString(mActivity)); - ReportActivity.startReportActivity(mActivity, new ReportInfo(UserAction.REPORT_ISSUE_FROM_TRANSCRIPT, TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY_NAME, title, null, reportString.toString(), "\n\n" + TermuxUtils.getReportIssueMarkdownString(mActivity), false)); + String termuxAptInfo = TermuxUtils.geAPTInfoMarkdownString(mActivity); + if (termuxAptInfo != null) + reportString.append("\n\n").append(termuxAptInfo); + + ReportActivity.startReportActivity(mActivity, new ReportInfo(UserAction.REPORT_ISSUE_FROM_TRANSCRIPT, TermuxConstants.TERMUX_APP.TERMUX_ACTIVITY_NAME, title, null, reportString.toString(), "\n\n" + TermuxUtils.getReportIssueMarkdownString(mActivity), false)); + } + }.start(); } public void doPaste() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fe0851c995..ca4cb76663 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -66,7 +66,7 @@ Autofill password Reset - Terminal reset. + Terminal reset Kill process (%d) Really kill this session? @@ -75,7 +75,9 @@ Keep screen on Help Settings + Report Issue + Generating Report The &TERMUX_STYLING_APP_NAME; Plugin App is not installed. Install From 27dc211e2d7735bb3857d524b3a59e85dd84033f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 08:05:08 +0500 Subject: [PATCH 29/37] Update .gitattributes --- .gitattributes | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitattributes b/.gitattributes index 21c2769ab9..253738449b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,5 @@ * text=auto -*.bat eol=crlf -*.gradle eol=lf -*.mk eol=lf -*.sh eol=lf +*.bat text eol=crlf +*.gradle text eol=lf +*.mk text eol=lf +*.sh text eol=lf From 11f5c0afd105b0647c610e13e39a5f72d9917e4d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 14 May 2021 08:41:15 +0500 Subject: [PATCH 30/37] Normalize gradlew.bat --- gradlew.bat | 178 ++++++++++++++++++++++++++-------------------------- 1 file changed, 89 insertions(+), 89 deletions(-) diff --git a/gradlew.bat b/gradlew.bat index ac1b06f938..107acd32c4 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,89 +1,89 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 31298b88575e12ea73e484cfd760ea3f030dcbd5 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 15 May 2021 16:35:54 +0500 Subject: [PATCH 31/37] Allow users to enable terminal cursor blinking with termux.properties This `terminal-cursor-blink-rate` key can be used to enable terminal cursor blinking. The user can set an int value between `100` and `2000` which will be used as blink rate in millisecond. The default value is `0`, which disables cursor blinking. So adding an entry like `terminal-cursor-blink-rate=600` to `~/termux.properties` file will make the cursor attempt to blink every 600ms. Running `termux-reload-settings` command will also update the cursor blinking rate instantaneously if changed. A background thread is used to control the blinking by toggling the cursor visibility and then invalidating the view every x milliseconds set. This will have a performance impact, so use wisely and at your own risk. If the cursor itself is disabled, which is controlled by whether DECSET_BIT_CURSOR_ENABLED (DECSET 25, DECTCEM), then blinking will be automatically disabled. You can enable the cursor with `tput cnorm` or `echo -e '\e[?25h'` and disable it with `tput civis` or `echo -e '\e[?25l'`. Note that you can also change the cursor color by adding `cursor` property to `~/colors.properties` file, like `cursor=#FFFFFF` for a white cursor. The `TermuxPropertyConstants` class has been updated to `v0.9.0`. Check its Changelog sections for info on changes. Closes #153 --- .../java/com/termux/app/TermuxActivity.java | 9 ++ .../terminal/TermuxTerminalSessionClient.java | 13 ++ .../terminal/TermuxTerminalViewClient.java | 12 ++ .../com/termux/terminal/TerminalEmulator.java | 47 ++++++-- .../terminal/TerminalSessionClient.java | 2 + .../java/com/termux/terminal/DecSetTest.java | 24 ++-- .../com/termux/view/TerminalRenderer.java | 2 +- .../java/com/termux/view/TerminalView.java | 113 +++++++++++++++++- .../properties/TermuxPropertyConstants.java | 15 ++- .../properties/TermuxSharedProperties.java | 35 ++++++ .../TermuxTerminalSessionClientBase.java | 6 + 11 files changed, 254 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 582d141588..766f941f45 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -257,6 +257,9 @@ public void onResume() { if (mIsInvalidState) return; mTermuxTerminalViewClient.setSoftKeyboardState(true, false); + + // Start terminal cursor blinking if enabled + mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true); } /** @@ -330,6 +333,9 @@ protected void onStop() { // {@link #onStart} if needed. mTermuxTerminalSessionClient.setCurrentStoredSession(); + // Stop terminal cursor blinking if enabled + mTermuxTerminalViewClient.setTerminalCursorBlinkerState(false); + unregisterTermuxActivityBroadcastReceiever(); getDrawer().closeDrawers(); } @@ -799,6 +805,9 @@ private void reloadTermuxActivityStyling() { mTermuxTerminalViewClient.setSoftKeyboardState(false, true); + mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true); + + // To change the activity and drawer theme, activity needs to be recreated. // But this will destroy the activity, and will call the onCreate() again. // We need to investigate if enabling this is wise, since all stored variables and diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java index 98003d0d7d..7d11689048 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java @@ -137,6 +137,19 @@ public void onColorsChanged(TerminalSession changedSession) { updateBackgroundColor(); } + @Override + public void onTerminalCursorStateChange(boolean enabled) { + // Do not start cursor blinking thread if activity is not visible + if (enabled && !mActivity.isVisible()) { + Logger.logVerbose(LOG_TAG, "Ignoring call to start cursor blinking since activity is not visible"); + return; + } + + // If cursor is to enabled now, then start cursor blinking if blinking is enabled + // otherwise stop cursor blinking + mActivity.getTerminalView().setTerminalCursorBlinkerState(enabled, false); + } + /** Try switching to session. */ diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 7700cc6252..6c4be3855f 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -427,6 +427,18 @@ public void onFocusChange(View view, boolean hasFocus) { + public void setTerminalCursorBlinkerState(boolean start) { + if (start) { + // Set/Update the cursor blinking rate + mActivity.getTerminalView().setTerminalCursorBlinkerRate(mActivity.getProperties().getTerminalCursorBlinkRate()); + } + + // Set the new state of cursor blinker + mActivity.getTerminalView().setTerminalCursorBlinkerState(start, true); + } + + + public void shareSessionTranscript() { TerminalSession session = mActivity.getCurrentSession(); if (session == null) return; diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java index 8f449dab2d..f5fb10e855 100644 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java +++ b/terminal-emulator/src/main/java/com/termux/terminal/TerminalEmulator.java @@ -108,8 +108,8 @@ public final class TerminalEmulator { * characters received when the cursor is at the right border of the page replace characters already on the page." */ private static final int DECSET_BIT_AUTOWRAP = 1 << 3; - /** DECSET 25 - if the cursor should be visible, {@link #isShowingCursor()}. */ - private static final int DECSET_BIT_SHOWING_CURSOR = 1 << 4; + /** DECSET 25 - if the cursor should be enabled, {@link #isCursorEnabled()}. */ + private static final int DECSET_BIT_CURSOR_ENABLED = 1 << 4; private static final int DECSET_BIT_APPLICATION_KEYPAD = 1 << 5; /** DECSET 1000 - if to report mouse press&release events. */ private static final int DECSET_BIT_MOUSE_TRACKING_PRESS_RELEASE = 1 << 6; @@ -205,6 +205,18 @@ public final class TerminalEmulator { */ private boolean mAboutToAutoWrap; + /** + * If the cursor blinking is enabled. It requires cursor itself to be enabled, which is controlled + * byt whether {@link #DECSET_BIT_CURSOR_ENABLED} bit is set or not. + */ + private boolean mCursorBlinkingEnabled; + + /** + * If currently cursor should be in a visible state or not if {@link #mCursorBlinkingEnabled} + * is {@code true}. + */ + private boolean mCursorBlinkState; + /** * Current foreground and background colors. Can either be a color index in [0,259] or a truecolor (24-bit) value. * For a 24-bit value the top byte (0xff000000) is set. @@ -261,7 +273,7 @@ static int mapDecSetBitToInternalBit(int decsetBit) { case 7: return DECSET_BIT_AUTOWRAP; case 25: - return DECSET_BIT_SHOWING_CURSOR; + return DECSET_BIT_CURSOR_ENABLED; case 66: return DECSET_BIT_APPLICATION_KEYPAD; case 69: @@ -381,10 +393,28 @@ public boolean isReverseVideo() { return isDecsetInternalBitSet(DECSET_BIT_REVERSE_VIDEO); } - public boolean isShowingCursor() { - return isDecsetInternalBitSet(DECSET_BIT_SHOWING_CURSOR); + + + public boolean isCursorEnabled() { + return isDecsetInternalBitSet(DECSET_BIT_CURSOR_ENABLED); + } + public boolean shouldCursorBeVisible() { + if (!isCursorEnabled()) + return false; + else + return mCursorBlinkingEnabled ? mCursorBlinkState : true; + } + + public void setCursorBlinkingEnabled(boolean cursorBlinkingEnabled) { + this.mCursorBlinkingEnabled = cursorBlinkingEnabled; + } + + public void setCursorBlinkState(boolean cursorBlinkState) { + this.mCursorBlinkState = cursorBlinkState; } + + public boolean isKeypadApplicationMode() { return isDecsetInternalBitSet(DECSET_BIT_APPLICATION_KEYPAD); } @@ -1054,7 +1084,10 @@ public void doDecSetOrReset(boolean setting, int externalBit) { case 8: // Auto-repeat Keys (DECARM). Do not implement. case 9: // X10 mouse reporting - outdated. Do not implement. case 12: // Control cursor blinking - ignore. - case 25: // Hide/show cursor - no action needed, renderer will check with isShowingCursor(). + case 25: // Hide/show cursor - no action needed, renderer will check with shouldCursorBeVisible(). + if (mClient != null) + mClient.onTerminalCursorStateChange(setting); + break; case 40: // Allow 80 => 132 Mode, ignore. case 45: // TODO: Reverse wrap-around. Implement??? case 66: // Application keypad (DECNKM). @@ -2318,7 +2351,7 @@ public void reset() { mCurrentDecSetFlags = 0; // Initial wrap-around is not accurate but makes terminal more useful, especially on a small screen: setDecsetinternalBit(DECSET_BIT_AUTOWRAP, true); - setDecsetinternalBit(DECSET_BIT_SHOWING_CURSOR, true); + setDecsetinternalBit(DECSET_BIT_CURSOR_ENABLED, true); mSavedDecSetFlags = mSavedStateMain.mSavedDecFlags = mSavedStateAlt.mSavedDecFlags = mCurrentDecSetFlags; // XXX: Should we set terminal driver back to IUTF8 with termios? diff --git a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java b/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java index c275ca82d3..9c99803023 100644 --- a/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java +++ b/terminal-emulator/src/main/java/com/termux/terminal/TerminalSessionClient.java @@ -19,6 +19,8 @@ public interface TerminalSessionClient { void onColorsChanged(TerminalSession session); + void onTerminalCursorStateChange(boolean state); + void logError(String tag, String message); diff --git a/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java b/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java index 9d4b5e26bf..f31f1fb833 100644 --- a/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java +++ b/terminal-emulator/src/test/java/com/termux/terminal/DecSetTest.java @@ -16,23 +16,23 @@ public class DecSetTest extends TerminalTestCase { /** DECSET 25, DECTCEM, controls visibility of the cursor. */ - public void testShowHideCursor() { + public void testEnableDisableCursor() { withTerminalSized(3, 3); - assertTrue("Initially the cursor should be visible", mTerminal.isShowingCursor()); - enterString("\033[?25l"); // Hide Cursor (DECTCEM). - assertFalse(mTerminal.isShowingCursor()); - enterString("\033[?25h"); // Show Cursor (DECTCEM). - assertTrue(mTerminal.isShowingCursor()); + assertTrue("Initially the cursor should be enabled", mTerminal.isCursorEnabled()); + enterString("\033[?25l"); // Disable Cursor (DECTCEM). + assertFalse(mTerminal.isCursorEnabled()); + enterString("\033[?25h"); // Enable Cursor (DECTCEM). + assertTrue(mTerminal.isCursorEnabled()); - enterString("\033[?25l"); // Hide Cursor (DECTCEM), again. - assertFalse(mTerminal.isShowingCursor()); + enterString("\033[?25l"); // Disable Cursor (DECTCEM), again. + assertFalse(mTerminal.isCursorEnabled()); mTerminal.reset(); - assertTrue("Resetting the terminal should show the cursor", mTerminal.isShowingCursor()); + assertTrue("Resetting the terminal should enable the cursor", mTerminal.isCursorEnabled()); enterString("\033[?25l"); - assertFalse(mTerminal.isShowingCursor()); - enterString("\033c"); // RIS resetting should reveal cursor. - assertTrue(mTerminal.isShowingCursor()); + assertFalse(mTerminal.isCursorEnabled()); + enterString("\033c"); // RIS resetting should enabled cursor. + assertTrue(mTerminal.isCursorEnabled()); } /** DECSET 2004, controls bracketed paste mode. */ diff --git a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java index 5c9caf1c6d..6189dd6712 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalRenderer.java @@ -61,7 +61,7 @@ public final void render(TerminalEmulator mEmulator, Canvas canvas, int topRow, final int columns = mEmulator.mColumns; final int cursorCol = mEmulator.getCursorCol(); final int cursorRow = mEmulator.getCursorRow(); - final boolean cursorVisible = mEmulator.isShowingCursor(); + final boolean cursorVisible = mEmulator.shouldCursorBeVisible(); final TerminalBuffer screen = mEmulator.getScreen(); final int[] palette = mEmulator.mColors.mCurrentColors; final int cursorShape = mEmulator.getCursorStyle(); diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index 3a0f005261..0e5ef38e24 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -8,6 +8,8 @@ import android.graphics.Canvas; import android.graphics.Typeface; import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.text.Editable; import android.text.InputType; import android.text.TextUtils; @@ -52,6 +54,14 @@ public final class TerminalView extends View { private TextSelectionCursorController mTextSelectionCursorController; + private Handler mTerminalCursorBlinkerHandler; + private TerminalCursorBlinkerThread mTerminalCursorBlinkerThread; + private int mTerminalCursorBlinkerRate; + public static final int TERMINAL_CURSOR_BLINK_RATE_MIN = 100; + public static final int TERMINAL_CURSOR_BLINK_RATE_MAX = 2000; + + private boolean mRendering; + /** The top row of text to display. Ranges from -activeTranscriptRows to 0. */ int mTopRow; int[] mDefaultSelectors = new int[]{-1,-1,-1,-1}; @@ -209,6 +219,8 @@ public void onLongPress(MotionEvent event) { mAccessibilityEnabled = am.isEnabled(); } + + /** * @param client The {@link TerminalViewClient} interface implementation to allow * for communication between {@link TerminalView} and its client. @@ -218,7 +230,7 @@ public void setTerminalViewClient(TerminalViewClient client) { } /** - * Sets terminal view key logging is enabled or not. + * Sets whether terminal view key logging is enabled or not. * * @param value The boolean value that defines the state. */ @@ -226,6 +238,8 @@ public void setIsTerminalViewKeyLoggingEnabled(boolean value) { TERMINAL_VIEW_KEY_LOGGING_ENABLED = value; } + + /** * Attach a {@link TerminalSession} to this view. * @@ -755,7 +769,10 @@ protected void onDraw(Canvas canvas) { if (mTextSelectionCursorController != null) { mTextSelectionCursorController.getSelectors(sel); } + + mRendering = true; mRenderer.render(mEmulator, canvas, mTopRow, sel[0], sel[1], sel[2], sel[3]); + mRendering = false; // render the text selection handles renderTextSelection(); @@ -799,7 +816,6 @@ public void setTopRow(int mTopRow) { - /** * Define functions required for AutoFill API */ @@ -825,6 +841,98 @@ public AutofillValue getAutofillValue() { + /** + * Set terminal cursor blinker rate. It must be between {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} + * and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}. + * + * @param blinkRate The value to set. + */ + public void setTerminalCursorBlinkerRate(int blinkRate) { + mTerminalCursorBlinkerRate = blinkRate; + } + + /** + * Sets whether cursor blinking should be started or stopped. Cursor blinking will only be + * started if {@link #mTerminalCursorBlinkerRate} does not equal 0 and is between + * {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}. + * + * @param start If cursor blinking should be started or stopped. + * @param startOnlyIfCursorEnabled If set to {@code true}, then it will also be checked if the + * cursor is even enabled by {@link TerminalEmulator} before + * starting the cursor blinking thread. + */ + public synchronized void setTerminalCursorBlinkerState(boolean start, boolean startOnlyIfCursorEnabled) { + // Stop any existing cursor blinker threads + stopTerminalCursorBlinkerThread(); + + if (mEmulator == null) return; + + mEmulator.setCursorBlinkingEnabled(false); + + if (start) { + // If cursor blinking is not enabled + if (mTerminalCursorBlinkerRate == 0) { + mClient.logVerbose(LOG_TAG, "Cursor blinking is not enabled"); + return; + } + // If cursor blinking rate is not valid + else if (mTerminalCursorBlinkerRate < TERMINAL_CURSOR_BLINK_RATE_MIN || mTerminalCursorBlinkerRate > TERMINAL_CURSOR_BLINK_RATE_MAX) { + mClient.logError(LOG_TAG, "startCursorBlinkerThread: The cursor blink rate must be in between " + TERMINAL_CURSOR_BLINK_RATE_MIN + "-" + TERMINAL_CURSOR_BLINK_RATE_MAX + ": " + mTerminalCursorBlinkerRate); + return; + } + // If cursor is not enabled + else if (startOnlyIfCursorEnabled && ! mEmulator.isCursorEnabled()) { + mClient.logVerbose(LOG_TAG, "Ignoring call to start cursor blinking since cursor is not enabled"); + return; + } + + // Start cursor blinker thread + mClient.logVerbose(LOG_TAG, "Starting cursor blinker thread with the blink rate: " + mTerminalCursorBlinkerRate); + if (mTerminalCursorBlinkerHandler == null) mTerminalCursorBlinkerHandler = new Handler(Looper.getMainLooper()); + mTerminalCursorBlinkerThread = new TerminalCursorBlinkerThread(mTerminalCursorBlinkerRate); + mEmulator.setCursorBlinkingEnabled(true); + mTerminalCursorBlinkerThread.run(); + } + } + + /** + * Stops the terminal cursor blinker thread + */ + private void stopTerminalCursorBlinkerThread() { + if (mTerminalCursorBlinkerHandler != null && mTerminalCursorBlinkerThread != null) { + mClient.logVerbose(LOG_TAG, "Stopping cursor blinker thread"); + mTerminalCursorBlinkerHandler.removeCallbacks(mTerminalCursorBlinkerThread); + } + } + + private class TerminalCursorBlinkerThread implements Runnable { + int mBlinkRate; + boolean mCursorVisible; + + public TerminalCursorBlinkerThread(int blinkRate) { + mBlinkRate = blinkRate; + } + + public void run() { + try { + if (mEmulator != null) { + mCursorVisible = !mCursorVisible; + // Toggle the blink state and then invalidate() the view so + // that onDraw() is called, which then calls TerminalRenderer.render() + // which checks with TerminalEmulator.shouldCursorBeVisible() to decide whether + // to draw the cursor or not + mEmulator.setCursorBlinkState(mCursorVisible); + if (!mRendering) + invalidate(); + } + } finally { + // Recall the Runnable after mBlinkRate milliseconds to toggle the blink state + mTerminalCursorBlinkerHandler.postDelayed(mTerminalCursorBlinkerThread, mBlinkRate); + } + } + } + + /** * Define functions required for text selection and its handles. @@ -920,7 +1028,6 @@ protected void onDetachedFromWindow() { - /** * Define functions required for long hold toolbar. */ diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java index bedc27f3c9..c84a0f38c0 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java @@ -10,7 +10,7 @@ import java.util.Set; /* - * Version: v0.8.0 + * Version: v0.9.0 * * Changelog * @@ -41,6 +41,10 @@ * - Change the `KEY_USE_BACK_KEY_AS_ESCAPE_KEY` and `KEY_VIRTUAL_VOLUME_KEYS_DISABLED` booleans * to `KEY_BACK_KEY_BEHAVIOUR` and `KEY_VOLUME_KEYS_BEHAVIOUR` String internal values. * - Renamed `SOFT_KEYBOARD_TOGGLE_BEHAVIOUR` to `KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR`. + * + * - 0.9.0 (2021-05-14) + * - Add `*KEY_TERMINAL_CURSOR_BLINK_RATE*`. + * */ /** @@ -115,6 +119,14 @@ public final class TermuxPropertyConstants { + /** Defines the key for the terminal cursor blink rate */ + public static final String KEY_TERMINAL_CURSOR_BLINK_RATE = "terminal-cursor-blink-rate"; // Default: "terminal-cursor-blink-rate" + public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN = 100; + public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX = 2000; + public static final int DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE = 0; + + + /* float */ @@ -221,6 +233,7 @@ public final class TermuxPropertyConstants { /* int */ KEY_BELL_BEHAVIOUR, + KEY_TERMINAL_CURSOR_BLINK_RATE, /* float */ KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java index 4d294d108d..68acf87278 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java @@ -188,6 +188,8 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St /* int */ case TermuxPropertyConstants.KEY_BELL_BEHAVIOUR: return (int) getBellBehaviourInternalPropertyValueFromValue(value); + case TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE: + return (int) getTerminalCursorBlinkRateInternalPropertyValueFromValue(value); /* float */ case TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR: @@ -257,6 +259,35 @@ public static int getBellBehaviourInternalPropertyValueFromValue(String value) { return SharedProperties.getDefaultIfNull(TermuxPropertyConstants.MAP_BELL_BEHAVIOUR.get(SharedProperties.toLowerCase(value)), TermuxPropertyConstants.DEFAULT_IVALUE_BELL_BEHAVIOUR); } + /** + * Returns the int for the value if its not null and is between + * {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and + * {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX}, + * otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR}. + * + * @param value The {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static float getTerminalCursorBlinkRateInternalPropertyValueFromValue(String value) { + return rangeTerminalCursorBlinkRateValue(DataUtils.getIntFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE)); + } + + /** + * Returns the value itself if it is between + * {@code TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN} and + * {@code TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX}, + * otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE}. + * + * @param value The value to clamp. + * @return Returns the clamped value. + */ + public static int rangeTerminalCursorBlinkRateValue(int value) { + return (int) DataUtils.rangedOrDefault(value, + TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE, + TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN, + TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX); + } + /** * Returns the int for the value if its not null and is between * {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and @@ -418,6 +449,10 @@ public int getBellBehaviour() { return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_BELL_BEHAVIOUR, true); } + public int getTerminalCursorBlinkRate() { + return rangeTerminalCursorBlinkRateValue((int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE, true)); + } + public float getTerminalToolbarHeightScaleFactor() { return rangeTerminalToolbarHeightScaleFactorValue((float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true)); } diff --git a/termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalSessionClientBase.java b/termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalSessionClientBase.java index 1f77eaa188..6261972b8a 100644 --- a/termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalSessionClientBase.java +++ b/termux-shared/src/main/java/com/termux/shared/terminal/TermuxTerminalSessionClientBase.java @@ -33,6 +33,12 @@ public void onBell(TerminalSession session) { public void onColorsChanged(TerminalSession changedSession) { } + @Override + public void onTerminalCursorStateChange(boolean state) { + } + + + @Override public void logError(String tag, String message) { Logger.logError(tag, message); From f8ccbb4953e835c78a5bdcb170121207b5622d6a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 16 May 2021 01:31:34 +0500 Subject: [PATCH 32/37] Log invalid values stored in termux.properties file during load time All external and internal values were already logged and required log level to be set to "Verbose" in Termux Settings, but now invalid values and the default value used instead will be logged at log level "Normal" as well. The `TermuxPropertyConstants` class has been updated to `v0.10.0`. Check its Changelog sections for info on changes. --- .../terminal/io/extrakeys/ExtraKeysInfo.java | 6 + .../com/termux/app/utils/PluginUtils.java | 2 +- .../settings/properties/SharedProperties.java | 121 ++++++++++++++++-- .../properties/TermuxPropertyConstants.java | 26 +++- .../properties/TermuxSharedProperties.java | 85 +++++------- 5 files changed, 179 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysInfo.java b/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysInfo.java index 26fec3d6a3..98cc6afc4e 100644 --- a/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysInfo.java +++ b/app/src/main/java/com/termux/app/terminal/io/extrakeys/ExtraKeysInfo.java @@ -1,5 +1,9 @@ package com.termux.app.terminal.io.extrakeys; +import com.termux.shared.logger.Logger; +import com.termux.shared.settings.properties.TermuxPropertyConstants; +import com.termux.shared.settings.properties.TermuxSharedProperties; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -238,6 +242,8 @@ CharDisplayMap getSelectedCharMap() { case "none": return new CharDisplayMap(); default: + if (!TermuxPropertyConstants.DEFAULT_IVALUE_EXTRA_KEYS_STYLE.equals(style)) + Logger.logError(TermuxSharedProperties.LOG_TAG, "The style \"" + style + "\" for the key \"" + TermuxPropertyConstants.KEY_EXTRA_KEYS_STYLE + "\" is invalid. Using default style instead."); return defaultCharDisplay; } } diff --git a/app/src/main/java/com/termux/app/utils/PluginUtils.java b/app/src/main/java/com/termux/app/utils/PluginUtils.java index d1b7dba9c4..b1d5c5db74 100644 --- a/app/src/main/java/com/termux/app/utils/PluginUtils.java +++ b/app/src/main/java/com/termux/app/utils/PluginUtils.java @@ -322,7 +322,7 @@ public static void setupPluginCommandErrorsNotificationChannel(final Context con */ public static String checkIfRunCommandServiceAllowExternalAppsPolicyIsViolated(final Context context) { String errmsg = null; - if (!SharedProperties.isPropertyValueTrue(context, TermuxPropertyConstants.getTermuxPropertiesFile(), TermuxConstants.PROP_ALLOW_EXTERNAL_APPS)) { + if (!SharedProperties.isPropertyValueTrue(context, TermuxPropertyConstants.getTermuxPropertiesFile(), TermuxConstants.PROP_ALLOW_EXTERNAL_APPS, true)) { errmsg = context.getString(R.string.error_run_command_service_allow_external_apps_ungranted); } diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/SharedProperties.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/SharedProperties.java index 9c808e93b2..30ec08a5be 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/SharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/SharedProperties.java @@ -3,6 +3,7 @@ import android.content.Context; import android.widget.Toast; +import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; import com.google.common.primitives.Primitives; import com.termux.shared.logger.Logger; @@ -289,12 +290,14 @@ public static Object getInternalProperty(Context context, File propertiesFile, S * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)}call. * @param propertiesFile The {@link File} to read the {@link Properties} from. * @param key The key to read. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value + * was found in {@link Properties} but was invalid. * @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true", * regardless of case. If the key does not exist in the file or does not equal "true", then * {@code false} will be returned. */ - public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key) { - return (boolean) getBooleanValueForStringValue((String) getProperty(context, propertiesFile, key, null), false); + public static boolean isPropertyValueTrue(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue) { + return (boolean) getBooleanValueForStringValue(key, (String) getProperty(context, propertiesFile, key, null), false, logErrorOnInvalidValue, LOG_TAG); } /** @@ -304,12 +307,14 @@ public static boolean isPropertyValueTrue(Context context, File propertiesFile, * @param context The {@link Context} for the {@link #getPropertiesFromFile(Context,File)} call. * @param propertiesFile The {@link File} to read the {@link Properties} from. * @param key The key to read. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value + * was found in {@link Properties} but was invalid. * @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "false", * regardless of case. If the key does not exist in the file or does not equal "false", then * {@code true} will be returned. */ - public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key) { - return (boolean) getInvertedBooleanValueForStringValue((String) getProperty(context, propertiesFile, key, null), true); + public static boolean isPropertyValueFalse(Context context, File propertiesFile, String key, boolean logErrorOnInvalidValue) { + return (boolean) getInvertedBooleanValueForStringValue(key, (String) getProperty(context, propertiesFile, key, null), true, logErrorOnInvalidValue, LOG_TAG); } @@ -413,16 +418,20 @@ public static Map getMapCopy(Map map) { + /** * Get the boolean value for the {@link String} value. * * @param value The {@link String} value to convert. * @param def The default {@link boolean} value to return. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value} + * was not {@code null} and was invalid. + * @param logTag If log tag to use for logging errors. * @return Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively, * regardless of case. Otherwise returns default value. */ - public static boolean getBooleanValueForStringValue(String value, boolean def) { - return (boolean) getDefaultIfNull(MAP_GENERIC_BOOLEAN.get(toLowerCase(value)), def); + public static boolean getBooleanValueForStringValue(String key, String value, boolean def, boolean logErrorOnInvalidValue, String logTag) { + return (boolean) getDefaultIfNotInMap(key, MAP_GENERIC_BOOLEAN, toLowerCase(value), def, logErrorOnInvalidValue, logTag); } /** @@ -430,11 +439,107 @@ public static boolean getBooleanValueForStringValue(String value, boolean def) { * * @param value The {@link String} value to convert. * @param def The default {@link boolean} value to return. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value} + * was not {@code null} and was invalid. + * @param logTag If log tag to use for logging errors. * @return Returns {@code true} or {@code false} if value is the literal string "false" or "true" respectively, * regardless of case. Otherwise returns default value. */ - public static boolean getInvertedBooleanValueForStringValue(String value, boolean def) { - return (boolean) getDefaultIfNull(MAP_GENERIC_INVERTED_BOOLEAN.get(toLowerCase(value)), def); + public static boolean getInvertedBooleanValueForStringValue(String key, String value, boolean def, boolean logErrorOnInvalidValue, String logTag) { + return (boolean) getDefaultIfNotInMap(key, MAP_GENERIC_INVERTED_BOOLEAN, toLowerCase(value), def, logErrorOnInvalidValue, logTag); + } + + /** + * Get the value for the {@code inputValue} {@link Object} key from a {@link BiMap<>}, otherwise + * default value if key not found in {@code map}. + * + * @param key The shared properties {@link String} key value for which the value is being returned. + * @param map The {@link BiMap<>} value to get the value from. + * @param inputValue The {@link Object} key value of the map. + * @param defaultOutputValue The default {@link boolean} value to return if {@code inputValue} not found in map. + * The default value must exist as a value in the {@link BiMap<>} passed. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code inputValue} + * was not {@code null} and was not found in the map. + * @param logTag If log tag to use for logging errors. + * @return Returns the value for the {@code inputValue} key from the map if it exists. Otherwise + * returns default value. + */ + public static Object getDefaultIfNotInMap(String key, @Nonnull BiMap map, Object inputValue, Object defaultOutputValue, boolean logErrorOnInvalidValue, String logTag) { + Object outputValue = map.get(inputValue); + if (outputValue == null) { + Object defaultInputValue = map.inverse().get(defaultOutputValue); + if (defaultInputValue == null) + Logger.logError(LOG_TAG, "The default output value \"" + defaultOutputValue + "\" for the key \"" + key + "\" does not exist as a value in the BiMap passed to getDefaultIfNotInMap(): " + map.values()); + + if (logErrorOnInvalidValue && inputValue != null) { + if (key != null) + Logger.logError(logTag, "The value \"" + inputValue + "\" for the key \"" + key + "\" is invalid. Using default value \"" + defaultInputValue + "\" instead."); + else + Logger.logError(logTag, "The value \"" + inputValue + "\" is invalid. Using default value \"" + defaultInputValue + "\" instead."); + } + + return defaultOutputValue; + } else { + return outputValue; + } + } + + /** + * Get the {@code int} {@code value} as is if between {@code min} and {@code max} (inclusive), otherwise + * return default value. + * + * @param key The shared properties {@link String} key value for which the value is being returned. + * @param value The {@code int} value to check. + * @param def The default {@code int} value if {@code value} not in range. + * @param min The min allowed {@code int} value. + * @param max The max allowed {@code int} value. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value} + * not in range. + * @param ignoreErrorIfValueZero If logging error should be ignored if value equals 0. + * @param logTag If log tag to use for logging errors. + * @return Returns the {@code value} as is if within range. Otherwise returns default value. + */ + public static int getDefaultIfNotInRange(String key, int value, int def, int min, int max, boolean logErrorOnInvalidValue, boolean ignoreErrorIfValueZero, String logTag) { + if (value < min || value > max) { + if (logErrorOnInvalidValue && (!ignoreErrorIfValueZero || value != 0)) { + if (key != null) + Logger.logError(logTag, "The value \"" + value + "\" for the key \"" + key + "\" is not within the range " + min + "-" + max + " (inclusive). Using default value \"" + def + "\" instead."); + else + Logger.logError(logTag, "The value \"" + value + "\" is not within the range " + min + "-" + max + " (inclusive). Using default value \"" + def + "\" instead."); + } + return def; + } else { + return value; + } + } + + /** + * Get the {@code float} {@code value} as is if between {@code min} and {@code max} (inclusive), otherwise + * return default value. + * + * @param key The shared properties {@link String} key value for which the value is being returned. + * @param value The {@code float} value to check. + * @param def The default {@code float} value if {@code value} not in range. + * @param min The min allowed {@code float} value. + * @param max The max allowed {@code float} value. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if {@code value} + * not in range. + * @param ignoreErrorIfValueZero If logging error should be ignored if value equals 0. + * @param logTag If log tag to use for logging errors. + * @return Returns the {@code value} as is if within range. Otherwise returns default value. + */ + public static float getDefaultIfNotInRange(String key, float value, float def, float min, float max, boolean logErrorOnInvalidValue, boolean ignoreErrorIfValueZero, String logTag) { + if (value < min || value > max) { + if (logErrorOnInvalidValue && (!ignoreErrorIfValueZero || value != 0)) { + if (key != null) + Logger.logError(logTag, "The value \"" + value + "\" for the key \"" + key + "\" is not within the range " + min + "-" + max + " (inclusive). Using default value \"" + def + "\" instead."); + else + Logger.logError(logTag, "The value \"" + value + "\" is not within the range " + min + "-" + max + " (inclusive). Using default value \"" + def + "\" instead."); + } + return def; + } else { + return value; + } } /** diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java index c84a0f38c0..593705c7d8 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java @@ -10,7 +10,7 @@ import java.util.Set; /* - * Version: v0.9.0 + * Version: v0.10.0 * * Changelog * @@ -45,6 +45,9 @@ * - 0.9.0 (2021-05-14) * - Add `*KEY_TERMINAL_CURSOR_BLINK_RATE*`. * + * - 0.10.0 (2021-05-15) + * - Add `MAP_BACK_KEY_BEHAVIOUR`, `MAP_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR`, `MAP_VOLUME_KEYS_BEHAVIOUR`. + * */ /** @@ -179,6 +182,13 @@ public final class TermuxPropertyConstants { public static final String IVALUE_BACK_KEY_BEHAVIOUR_ESCAPE = "escape"; public static final String DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR = IVALUE_BACK_KEY_BEHAVIOUR_BACK; + /** Defines the bidirectional map for back key behaviour values and their internal values */ + public static final ImmutableBiMap MAP_BACK_KEY_BEHAVIOUR = + new ImmutableBiMap.Builder() + .put(IVALUE_BACK_KEY_BEHAVIOUR_BACK, IVALUE_BACK_KEY_BEHAVIOUR_BACK) + .put(IVALUE_BACK_KEY_BEHAVIOUR_ESCAPE, IVALUE_BACK_KEY_BEHAVIOUR_ESCAPE) + .build(); + /** Defines the key for the default working directory */ @@ -205,6 +215,13 @@ public final class TermuxPropertyConstants { public static final String IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE = "enable/disable"; public static final String DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE; + /** Defines the bidirectional map for toggle soft keyboard behaviour values and their internal values */ + public static final ImmutableBiMap MAP_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR = + new ImmutableBiMap.Builder() + .put(IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE, IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_SHOW_HIDE) + .put(IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE, IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR_ENABLE_DISABLE) + .build(); + /** Defines the key for whether volume keys will behave as virtual or literal volume keys */ @@ -214,6 +231,13 @@ public final class TermuxPropertyConstants { public static final String IVALUE_VOLUME_KEY_BEHAVIOUR_VOLUME = "volume"; public static final String DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR = IVALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL; + /** Defines the bidirectional map for volume keys behaviour values and their internal values */ + public static final ImmutableBiMap MAP_VOLUME_KEYS_BEHAVIOUR = + new ImmutableBiMap.Builder() + .put(IVALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL, IVALUE_VOLUME_KEY_BEHAVIOUR_VIRTUAL) + .put(IVALUE_VOLUME_KEY_BEHAVIOUR_VOLUME, IVALUE_VOLUME_KEY_BEHAVIOUR_VOLUME) + .build(); + diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java index 68acf87278..2532a74eb0 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxSharedProperties.java @@ -19,7 +19,7 @@ public class TermuxSharedProperties implements SharedPropertiesParser { protected final SharedProperties mSharedProperties; protected final File mPropertiesFile; - private static final String LOG_TAG = "TermuxSharedProperties"; + public static final String LOG_TAG = "TermuxSharedProperties"; public TermuxSharedProperties(@Nonnull Context context) { mContext = context; @@ -76,12 +76,14 @@ public String getPropertyValue(String key, String def, boolean cached) { * @param cached If {@code true}, then the value is checked from the the {@link Properties} in-memory cache. * Otherwise the {@link Properties} object is read directly from the file * and value is checked from it. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value + * was found in {@link Properties} but was invalid. * @return Returns the {@code true} if the {@link Properties} key {@link String} value equals "true", * regardless of case. If the key does not exist in the file or does not equal "true", then * {@code false} will be returned. */ - public boolean isPropertyValueTrue(String key, boolean cached) { - return (boolean) SharedProperties.getBooleanValueForStringValue((String) getPropertyValue(key, null, cached), false); + public boolean isPropertyValueTrue(String key, boolean cached, boolean logErrorOnInvalidValue) { + return (boolean) SharedProperties.getBooleanValueForStringValue(key, (String) getPropertyValue(key, null, cached), false, logErrorOnInvalidValue, LOG_TAG); } /** @@ -92,12 +94,14 @@ public boolean isPropertyValueTrue(String key, boolean cached) { * @param cached If {@code true}, then the value is checked from the the {@link Properties} in-memory cache. * Otherwise the {@link Properties} object is read directly from the file * and value is checked from it. + * @param logErrorOnInvalidValue If {@code true}, then an error will be logged if key value + * was found in {@link Properties} but was invalid. * @return Returns {@code true} if the {@link Properties} key {@link String} value equals "false", * regardless of case. If the key does not exist in the file or does not equal "false", then * {@code true} will be returned. */ - public boolean isPropertyValueFalse(String key, boolean cached) { - return (boolean) SharedProperties.getInvertedBooleanValueForStringValue((String) getPropertyValue(key, null, cached), true); + public boolean isPropertyValueFalse(String key, boolean cached, boolean logErrorOnInvalidValue) { + return (boolean) SharedProperties.getInvertedBooleanValueForStringValue(key, (String) getPropertyValue(key, null, cached), true, logErrorOnInvalidValue, LOG_TAG); } @@ -143,7 +147,7 @@ public Object getInternalPropertyValue(String key, boolean cached) { // A null value can still be returned by // {@link #getInternalPropertyValueFromValue(Context,String,String)} for some keys value = getInternalPropertyValueFromValue(mContext, key, null); - Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cahce, force returning default value: `" + value + "`"); + Logger.logWarn(LOG_TAG, "The value for \"" + key + "\" not found in SharedProperties cache, force returning default value: `" + value + "`"); return value; } } else { @@ -219,10 +223,10 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St default: // default boolean behaviour if (TermuxPropertyConstants.TERMUX_DEFAULT_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key)) - return (boolean) SharedProperties.getBooleanValueForStringValue(value, false); + return (boolean) SharedProperties.getBooleanValueForStringValue(key, value, false, true, LOG_TAG); // default inverted boolean behaviour else if (TermuxPropertyConstants.TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROPERTIES_LIST.contains(key)) - return (boolean) SharedProperties.getInvertedBooleanValueForStringValue(value, true); + return (boolean) SharedProperties.getInvertedBooleanValueForStringValue(key, value, true, true, LOG_TAG); // just use String object as is (may be null) else return value; @@ -233,8 +237,6 @@ else if (TermuxPropertyConstants.TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROP - - /** * Returns {@code true} or {@code false} if value is the literal string "true" or "false" respectively regardless of case. * Otherwise returns {@code true} if the night mode is currently enabled in the system. @@ -244,7 +246,7 @@ else if (TermuxPropertyConstants.TERMUX_DEFAULT_INVERETED_BOOLEAN_BEHAVIOUR_PROP */ public static boolean getUseBlackUIInternalPropertyValueFromValue(Context context, String value) { int nightMode = context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - return SharedProperties.getBooleanValueForStringValue(value, nightMode == Configuration.UI_MODE_NIGHT_YES); + return SharedProperties.getBooleanValueForStringValue(TermuxPropertyConstants.KEY_USE_BLACK_UI, value, nightMode == Configuration.UI_MODE_NIGHT_YES, true, LOG_TAG); } /** @@ -256,7 +258,7 @@ public static boolean getUseBlackUIInternalPropertyValueFromValue(Context contex * @return Returns the internal value for value. */ public static int getBellBehaviourInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(TermuxPropertyConstants.MAP_BELL_BEHAVIOUR.get(SharedProperties.toLowerCase(value)), TermuxPropertyConstants.DEFAULT_IVALUE_BELL_BEHAVIOUR); + return (int) SharedProperties.getDefaultIfNotInMap(TermuxPropertyConstants.KEY_BELL_BEHAVIOUR, TermuxPropertyConstants.MAP_BELL_BEHAVIOUR, SharedProperties.toLowerCase(value), TermuxPropertyConstants.DEFAULT_IVALUE_BELL_BEHAVIOUR, true, LOG_TAG); } /** @@ -269,23 +271,12 @@ public static int getBellBehaviourInternalPropertyValueFromValue(String value) { * @return Returns the internal value for value. */ public static float getTerminalCursorBlinkRateInternalPropertyValueFromValue(String value) { - return rangeTerminalCursorBlinkRateValue(DataUtils.getIntFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE)); - } - - /** - * Returns the value itself if it is between - * {@code TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN} and - * {@code TermuxPropertyConstants#IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX}, - * otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE}. - * - * @param value The value to clamp. - * @return Returns the clamped value. - */ - public static int rangeTerminalCursorBlinkRateValue(int value) { - return (int) DataUtils.rangedOrDefault(value, - TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE, - TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN, - TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX); + return SharedProperties.getDefaultIfNotInRange(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE, + DataUtils.getIntFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE), + TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE, + TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN, + TermuxPropertyConstants.IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX, + true, true, LOG_TAG); } /** @@ -298,23 +289,12 @@ public static int rangeTerminalCursorBlinkRateValue(int value) { * @return Returns the internal value for value. */ public static float getTerminalToolbarHeightScaleFactorInternalPropertyValueFromValue(String value) { - return rangeTerminalToolbarHeightScaleFactorValue(DataUtils.getFloatFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR)); - } - - /** - * Returns the value itself if it is between - * {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and - * {@code TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX}, - * otherwise returns {@code TermuxPropertyConstants#DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR}. - * - * @param value The value to clamp. - * @return Returns the clamped value. - */ - public static float rangeTerminalToolbarHeightScaleFactorValue(float value) { - return DataUtils.rangedOrDefault(value, + return SharedProperties.getDefaultIfNotInRange(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, + DataUtils.getFloatFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR), TermuxPropertyConstants.DEFAULT_IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, TermuxPropertyConstants.IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN, - TermuxPropertyConstants.IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX); + TermuxPropertyConstants.IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MAX, + true, true, LOG_TAG); } /** @@ -356,7 +336,7 @@ public static Integer getCodePointForSessionShortcuts(String key, String value) * @return Returns the internal value for value. */ public static String getBackKeyBehaviourInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR); + return (String) SharedProperties.getDefaultIfNotInMap(TermuxPropertyConstants.KEY_BACK_KEY_BEHAVIOUR, TermuxPropertyConstants.MAP_BACK_KEY_BEHAVIOUR, SharedProperties.toLowerCase(value), TermuxPropertyConstants.DEFAULT_IVALUE_BACK_KEY_BEHAVIOUR, true, LOG_TAG); } /** @@ -370,8 +350,9 @@ public static String getDefaultWorkingDirectoryInternalPropertyValueFromValue(St if (path == null || path.isEmpty()) return TermuxPropertyConstants.DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY; File workDir = new File(path); if (!workDir.exists() || !workDir.isDirectory() || !workDir.canRead()) { - // Fallback to default directory if user configured working directory does not exist - // or is not a directory or is not readable. + // Fallback to default directory if user configured working directory does not exist, + // is not a directory or is not readable. + Logger.logError(LOG_TAG, "The path \"" + path + "\" for the key \"" + TermuxPropertyConstants.KEY_DEFAULT_WORKING_DIRECTORY + "\" does not exist, is not a directory or is not readable. Using default value \"" + TermuxPropertyConstants.DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY + "\" instead."); return TermuxPropertyConstants.DEFAULT_IVALUE_DEFAULT_WORKING_DIRECTORY; } else { return path; @@ -405,7 +386,7 @@ public static String getExtraKeysStyleInternalPropertyValueFromValue(String valu * @return Returns the internal value for value. */ public static String getSoftKeyboardToggleBehaviourInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR); + return (String) SharedProperties.getDefaultIfNotInMap(TermuxPropertyConstants.KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, TermuxPropertyConstants.MAP_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, SharedProperties.toLowerCase(value), TermuxPropertyConstants.DEFAULT_IVALUE_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, true, LOG_TAG); } /** @@ -415,12 +396,13 @@ public static String getSoftKeyboardToggleBehaviourInternalPropertyValueFromValu * @return Returns the internal value for value. */ public static String getVolumeKeysBehaviourInternalPropertyValueFromValue(String value) { - return SharedProperties.getDefaultIfNull(value, TermuxPropertyConstants.DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR); + return (String) SharedProperties.getDefaultIfNotInMap(TermuxPropertyConstants.KEY_VOLUME_KEYS_BEHAVIOUR, TermuxPropertyConstants.MAP_VOLUME_KEYS_BEHAVIOUR, SharedProperties.toLowerCase(value), TermuxPropertyConstants.DEFAULT_IVALUE_VOLUME_KEYS_BEHAVIOUR, true, LOG_TAG); } + public boolean isEnforcingCharBasedInput() { return (boolean) getInternalPropertyValue(TermuxPropertyConstants.KEY_ENFORCE_CHAR_BASED_INPUT, true); } @@ -450,11 +432,11 @@ public int getBellBehaviour() { } public int getTerminalCursorBlinkRate() { - return rangeTerminalCursorBlinkRateValue((int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE, true)); + return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_CURSOR_BLINK_RATE, true); } public float getTerminalToolbarHeightScaleFactor() { - return rangeTerminalToolbarHeightScaleFactorValue((float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true)); + return (float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true); } public boolean isBackKeyTheEscapeKey() { @@ -476,6 +458,7 @@ public boolean areVirtualVolumeKeysDisabled() { + public void dumpPropertiesToLog() { Properties properties = getProperties(true); StringBuilder propertiesDump = new StringBuilder(); From 6524a619f6c759cd1d0d40762edb80c4ec34e40c Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Sun, 16 May 2021 19:40:25 +0300 Subject: [PATCH 33/37] update bootstrap archives --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 30124a4aef..20d82f40e3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -155,11 +155,11 @@ clean { task downloadBootstraps() { doLast { - def version = "2021.04.13-r1" - downloadBootstrap("aarch64", "ff82e5755d947cd1f3e0b30916d125c6ddd8ba3254801ca7499d73653417e158", version) - downloadBootstrap("arm", "53a7df2d6d0a36a8c9ab5259c8b5457c93b8bae8aec2321a470236b6da54e59a", version) - downloadBootstrap("i686", "f0e1399a13ebed6c5229fde161f9848d9f5eeae7b8cd82f31250a813b52e371", version) - downloadBootstrap("x86_64", "e36c4d8c933dc12b3f48937b7747c7a4dcfaa70f0dd89ad5e8b4465930075ae9", version) + def version = "2021.05.16-r1" + downloadBootstrap("aarch64", "6e340d8ab11d1225b89ee920e0884cbbd944d37765d81c5b06ef34579564fd9a", version) + downloadBootstrap("arm", "3f02bc2b5bd45c2ec5170527e39ee0413246698f11be4799c7bde6d364cfd780", version) + downloadBootstrap("i686", "36a3733fb2d8531d7f8abd989b711919872b9e8a79d7eb2e8b00bef467199187", version) + downloadBootstrap("x86_64", "3885376cc514220c0803e38f70b25f837854029fff2b7fda7a81452623cd9074", version) } } From 89a1e027130811fa8692a9fe0fc8f1e054f8e7e1 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 16 May 2021 19:08:11 +0500 Subject: [PATCH 34/37] Updates to terminal cursor blinking Fixed bug where cursor would become invisible when long holding (arrow) keys when editing commands (outside of text editors like nano). Updated javadocs with info on how cursor blinking works "Performance Improvements" and removed redundant mRendering check --- .../terminal/TermuxTerminalViewClient.java | 15 +- .../java/com/termux/view/TerminalView.java | 135 ++++++++++++------ .../properties/TermuxPropertyConstants.java | 5 +- 3 files changed, 105 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 6c4be3855f..246181199c 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -429,12 +429,15 @@ public void onFocusChange(View view, boolean hasFocus) { public void setTerminalCursorBlinkerState(boolean start) { if (start) { - // Set/Update the cursor blinking rate - mActivity.getTerminalView().setTerminalCursorBlinkerRate(mActivity.getProperties().getTerminalCursorBlinkRate()); + // If set/update the cursor blinking rate is successful, then enable cursor blinker + if (mActivity.getTerminalView().setTerminalCursorBlinkerRate(mActivity.getProperties().getTerminalCursorBlinkRate())) + mActivity.getTerminalView().setTerminalCursorBlinkerState(true, true); + else + Logger.logError(LOG_TAG,"Failed to start cursor blinker"); + } else { + // Disable cursor blinker + mActivity.getTerminalView().setTerminalCursorBlinkerState(false, true); } - - // Set the new state of cursor blinker - mActivity.getTerminalView().setTerminalCursorBlinkerState(start, true); } @@ -455,7 +458,7 @@ public void shareSessionTranscript() { intent.putExtra(Intent.EXTRA_SUBJECT, mActivity.getString(R.string.title_share_transcript)); mActivity.startActivity(Intent.createChooser(intent, mActivity.getString(R.string.title_share_transcript_with))); } catch (Exception e) { - Logger.logStackTraceWithMessage("Failed to get share session transcript of length " + transcriptText.length(), e); + Logger.logStackTraceWithMessage(LOG_TAG,"Failed to get share session transcript of length " + transcriptText.length(), e); } } diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index 0e5ef38e24..5f72bbeea1 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -55,13 +55,12 @@ public final class TerminalView extends View { private TextSelectionCursorController mTextSelectionCursorController; private Handler mTerminalCursorBlinkerHandler; - private TerminalCursorBlinkerThread mTerminalCursorBlinkerThread; + private TerminalCursorBlinkerRunnable mTerminalCursorBlinkerRunnable; private int mTerminalCursorBlinkerRate; + private boolean mCursorInvisibleIgnoreOnce; public static final int TERMINAL_CURSOR_BLINK_RATE_MIN = 100; public static final int TERMINAL_CURSOR_BLINK_RATE_MAX = 2000; - private boolean mRendering; - /** The top row of text to display. Ranges from -activeTranscriptRows to 0. */ int mTopRow; int[] mDefaultSelectors = new int[]{-1,-1,-1,-1}; @@ -699,6 +698,10 @@ public void inputCodePoint(int codePoint, boolean controlDownFromEvent, boolean /** Input the specified keyCode if applicable and return if the input was consumed. */ public boolean handleKeyCode(int keyCode, int keyMod) { + // Ensure cursor is shown when a key is pressed down like long hold on (arrow) keys + if (mEmulator != null) + mEmulator.setCursorBlinkState(true); + TerminalEmulator term = mTermSession.getEmulator(); String code = KeyHandler.getCode(keyCode, keyMod, term.isCursorKeysApplicationMode(), term.isKeypadApplicationMode()); if (code == null) return false; @@ -770,9 +773,7 @@ protected void onDraw(Canvas canvas) { mTextSelectionCursorController.getSelectors(sel); } - mRendering = true; mRenderer.render(mEmulator, canvas, mTopRow, sel[0], sel[1], sel[2], sel[3]); - mRendering = false; // render the text selection handles renderTextSelection(); @@ -843,91 +844,141 @@ public AutofillValue getAutofillValue() { /** * Set terminal cursor blinker rate. It must be between {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} - * and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}. + * and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}, otherwise it will be disabled. + * + * The {@link #setTerminalCursorBlinkerState(boolean, boolean)} must be called after this + * for changes to take effect if not disabling. * * @param blinkRate The value to set. + * @return Returns {@code true} if setting blinker rate was successfully set, otherwise [@code false}. */ - public void setTerminalCursorBlinkerRate(int blinkRate) { - mTerminalCursorBlinkerRate = blinkRate; + public synchronized boolean setTerminalCursorBlinkerRate(int blinkRate) { + boolean result; + + // If cursor blinking rate is not valid + if (blinkRate != 0 && (blinkRate < TERMINAL_CURSOR_BLINK_RATE_MIN || blinkRate > TERMINAL_CURSOR_BLINK_RATE_MAX)) { + mClient.logError(LOG_TAG, "The cursor blink rate must be in between " + TERMINAL_CURSOR_BLINK_RATE_MIN + "-" + TERMINAL_CURSOR_BLINK_RATE_MAX + ": " + blinkRate); + mTerminalCursorBlinkerRate = 0; + result = false; + } else { + mClient.logVerbose(LOG_TAG, "Setting cursor blinker rate to " + blinkRate); + mTerminalCursorBlinkerRate = blinkRate; + result = true; + } + + if (mTerminalCursorBlinkerRate == 0) { + mClient.logVerbose(LOG_TAG, "Cursor blinker disabled"); + stopTerminalCursorBlinker(); + } + + return result; } /** - * Sets whether cursor blinking should be started or stopped. Cursor blinking will only be + * Sets whether cursor blinker should be started or stopped. Cursor blinker will only be * started if {@link #mTerminalCursorBlinkerRate} does not equal 0 and is between * {@link #TERMINAL_CURSOR_BLINK_RATE_MIN} and {@link #TERMINAL_CURSOR_BLINK_RATE_MAX}. * - * @param start If cursor blinking should be started or stopped. + * This should be called when the view holding this activity is resumed or stopped so that + * cursor blinker does not run when activity is not visible. + * + * It should also be called on the + * {@link com.termux.terminal.TerminalSessionClient#onTerminalCursorStateChange(boolean)} + * callback when cursor is enabled or disabled so that blinker is disabled if cursor is not + * to be shown. It should also be checked if activity is visible if blinker is to be started + * before calling this. + * + * How cursor blinker starting works is by registering a {@link Runnable} with the looper of + * the main thread of the app which when run, toggles the cursor blinking state and re-registers + * itself to be called with the delay set by {@link #mTerminalCursorBlinkerRate}. When cursor + * blinking needs to be disabled, we just cancel any callbacks registered. We don't run our own + * "thread" and let the thread for the main looper do the work for us, whose usage is also + * required to update the UI, since it also handles other calls to update the UI as well based + * on a queue. + * + * Note that when moving cursor in text editors like nano, the cursor state is quickly + * toggled `-> off -> on`, which would call this very quickly sequentially. So that if cursor + * is moved 2 or more times quickly, like long hold on arrow keys, it would trigger + * `-> off -> on -> off -> on -> ...`, and the "on" callback at index 2 is automatically + * cancelled by next "off" callback at index 3 before getting a chance to be run. For this case + * we log only if {@link #TERMINAL_VIEW_KEY_LOGGING_ENABLED} is enabled, otherwise would clutter + * the log. We don't start the blinking with a delay to immediately show cursor in case it was + * previously not visible. + * + * @param start If cursor blinker should be started or stopped. * @param startOnlyIfCursorEnabled If set to {@code true}, then it will also be checked if the * cursor is even enabled by {@link TerminalEmulator} before - * starting the cursor blinking thread. + * starting the cursor blinker. */ public synchronized void setTerminalCursorBlinkerState(boolean start, boolean startOnlyIfCursorEnabled) { - // Stop any existing cursor blinker threads - stopTerminalCursorBlinkerThread(); + // Stop any existing cursor blinker callbacks + stopTerminalCursorBlinker(); if (mEmulator == null) return; mEmulator.setCursorBlinkingEnabled(false); if (start) { - // If cursor blinking is not enabled - if (mTerminalCursorBlinkerRate == 0) { - mClient.logVerbose(LOG_TAG, "Cursor blinking is not enabled"); + // If cursor blinker is not enabled or is not valid + if (mTerminalCursorBlinkerRate < TERMINAL_CURSOR_BLINK_RATE_MIN || mTerminalCursorBlinkerRate > TERMINAL_CURSOR_BLINK_RATE_MAX) return; - } - // If cursor blinking rate is not valid - else if (mTerminalCursorBlinkerRate < TERMINAL_CURSOR_BLINK_RATE_MIN || mTerminalCursorBlinkerRate > TERMINAL_CURSOR_BLINK_RATE_MAX) { - mClient.logError(LOG_TAG, "startCursorBlinkerThread: The cursor blink rate must be in between " + TERMINAL_CURSOR_BLINK_RATE_MIN + "-" + TERMINAL_CURSOR_BLINK_RATE_MAX + ": " + mTerminalCursorBlinkerRate); - return; - } - // If cursor is not enabled + // If cursor blinder is to be started only if cursor is enabled else if (startOnlyIfCursorEnabled && ! mEmulator.isCursorEnabled()) { - mClient.logVerbose(LOG_TAG, "Ignoring call to start cursor blinking since cursor is not enabled"); + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logVerbose(LOG_TAG, "Ignoring call to start cursor blinker since cursor is not enabled"); return; } - // Start cursor blinker thread - mClient.logVerbose(LOG_TAG, "Starting cursor blinker thread with the blink rate: " + mTerminalCursorBlinkerRate); - if (mTerminalCursorBlinkerHandler == null) mTerminalCursorBlinkerHandler = new Handler(Looper.getMainLooper()); - mTerminalCursorBlinkerThread = new TerminalCursorBlinkerThread(mTerminalCursorBlinkerRate); + // Start cursor blinker runnable + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logVerbose(LOG_TAG, "Starting cursor blinker with the blink rate " + mTerminalCursorBlinkerRate); + if (mTerminalCursorBlinkerHandler == null) + mTerminalCursorBlinkerHandler = new Handler(Looper.getMainLooper()); + mTerminalCursorBlinkerRunnable = new TerminalCursorBlinkerRunnable(mEmulator, mTerminalCursorBlinkerRate); mEmulator.setCursorBlinkingEnabled(true); - mTerminalCursorBlinkerThread.run(); + mTerminalCursorBlinkerRunnable.run(); } } /** - * Stops the terminal cursor blinker thread + * Cancel the terminal cursor blinker callbacks */ - private void stopTerminalCursorBlinkerThread() { - if (mTerminalCursorBlinkerHandler != null && mTerminalCursorBlinkerThread != null) { - mClient.logVerbose(LOG_TAG, "Stopping cursor blinker thread"); - mTerminalCursorBlinkerHandler.removeCallbacks(mTerminalCursorBlinkerThread); + private void stopTerminalCursorBlinker() { + if (mTerminalCursorBlinkerHandler != null && mTerminalCursorBlinkerRunnable != null) { + if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) + mClient.logVerbose(LOG_TAG, "Stopping cursor blinker"); + mTerminalCursorBlinkerHandler.removeCallbacks(mTerminalCursorBlinkerRunnable); } } - private class TerminalCursorBlinkerThread implements Runnable { - int mBlinkRate; - boolean mCursorVisible; + private class TerminalCursorBlinkerRunnable implements Runnable { - public TerminalCursorBlinkerThread(int blinkRate) { + private final TerminalEmulator mEmulator; + private final int mBlinkRate; + + // Initialize with false so that initial blink state is visible after toggling + boolean mCursorVisible = false; + + public TerminalCursorBlinkerRunnable(TerminalEmulator emulator, int blinkRate) { + mEmulator = emulator; mBlinkRate = blinkRate; } public void run() { try { if (mEmulator != null) { - mCursorVisible = !mCursorVisible; // Toggle the blink state and then invalidate() the view so // that onDraw() is called, which then calls TerminalRenderer.render() // which checks with TerminalEmulator.shouldCursorBeVisible() to decide whether // to draw the cursor or not + mCursorVisible = !mCursorVisible; + //mClient.logVerbose(LOG_TAG, "Toggling cursor blink state to " + mCursorVisible); mEmulator.setCursorBlinkState(mCursorVisible); - if (!mRendering) - invalidate(); + invalidate(); } } finally { // Recall the Runnable after mBlinkRate milliseconds to toggle the blink state - mTerminalCursorBlinkerHandler.postDelayed(mTerminalCursorBlinkerThread, mBlinkRate); + mTerminalCursorBlinkerHandler.postDelayed(this, mBlinkRate); } } } diff --git a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java index 593705c7d8..78a381cc25 100644 --- a/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/settings/properties/TermuxPropertyConstants.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableBiMap; import com.termux.shared.termux.TermuxConstants; import com.termux.shared.logger.Logger; +import com.termux.view.TerminalView; import java.io.File; import java.util.Arrays; @@ -124,8 +125,8 @@ public final class TermuxPropertyConstants { /** Defines the key for the terminal cursor blink rate */ public static final String KEY_TERMINAL_CURSOR_BLINK_RATE = "terminal-cursor-blink-rate"; // Default: "terminal-cursor-blink-rate" - public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN = 100; - public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX = 2000; + public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MIN = TerminalView.TERMINAL_CURSOR_BLINK_RATE_MIN; + public static final int IVALUE_TERMINAL_CURSOR_BLINK_RATE_MAX = TerminalView.TERMINAL_CURSOR_BLINK_RATE_MAX; public static final int DEFAULT_IVALUE_TERMINAL_CURSOR_BLINK_RATE = 0; From 58d577066a03a40e2d7a8c160cfc13c1694ab2a7 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 16 May 2021 19:52:08 +0500 Subject: [PATCH 35/37] Release terminal beep SoundPool resources on activity stop to attempt to prevent exception The following exception may be thrown, likely because of unreleased resources. Related https://stackoverflow.com/a/28708351/14686958 java.util.concurrent.TimeoutException: android.media.SoundPool.finalize() timed out after 10 seconds at android.media.SoundPool.native_release(Native Method) at android.media.SoundPool.release(SoundPool.java:177) at android.media.SoundPool.finalize(SoundPool.java:182) at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250) at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237) at java.lang.Daemons$Daemon.run(Daemons.java:103) at java.lang.Thread.run(Thread.java:764) --- .../terminal/TermuxTerminalSessionClient.java | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java index 7d11689048..7033ac5084 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java @@ -37,20 +37,39 @@ public class TermuxTerminalSessionClient extends TermuxTerminalSessionClientBase private static final int MAX_SESSIONS = 8; - private final SoundPool mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes( - new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); + private SoundPool mBellSoundPool; - private final int mBellSoundId; + private int mBellSoundId; private static final String LOG_TAG = "TermuxTerminalSessionClient"; public TermuxTerminalSessionClient(TermuxActivity activity) { this.mActivity = activity; + } + + /** + * Should be called when mActivity.onResume() is called + */ + public void onResume() { + // Just initialize the mBellSoundPool and load the sound, otherwise bell might not run + // the first time bell key is pressed and play() is called, since sound may not be loaded + // quickly enough before the call to play(). https://stackoverflow.com/questions/35435625 + getBellSoundPool(); + } - mBellSoundId = mBellSoundPool.load(activity, R.raw.bell, 1); + /** + * Should be called when mActivity.onStop() is called + */ + public void onStop() { + // Release mBellSoundPool resources, specially to prevent exceptions like the following to be thrown + // java.util.concurrent.TimeoutException: android.media.SoundPool.finalize() timed out after 10 seconds + // Bell is not played in background anyways + // Related: https://stackoverflow.com/a/28708351/14686958 + releaseBellSoundPool(); } + + @Override public void onTextChanged(TerminalSession changedSession) { if (!mActivity.isVisible()) return; @@ -122,13 +141,12 @@ public void onBell(TerminalSession session) { BellHandler.getInstance(mActivity).doBell(); break; case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_BEEP: - mBellSoundPool.play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f); + getBellSoundPool().play(mBellSoundId, 1.f, 1.f, 1, 0, 1.f); break; case TermuxPropertyConstants.IVALUE_BELL_BEHAVIOUR_IGNORE: // Ignore the bell character. break; } - } @Override @@ -152,6 +170,29 @@ public void onTerminalCursorStateChange(boolean enabled) { + /** Initialize and get mBellSoundPool */ + private synchronized SoundPool getBellSoundPool() { + if (mBellSoundPool == null) { + mBellSoundPool = new SoundPool.Builder().setMaxStreams(1).setAudioAttributes( + new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION).build()).build(); + + mBellSoundId = mBellSoundPool.load(mActivity, R.raw.bell, 1); + } + + return mBellSoundPool; + } + + /** Release mBellSoundPool resources */ + private synchronized void releaseBellSoundPool() { + if (mBellSoundPool != null) { + mBellSoundPool.release(); + mBellSoundPool = null; + } + } + + + /** Try switching to session. */ public void setCurrentSession(TerminalSession session) { if (session == null) return; From d736b1eba5608924080021a236255ba731892016 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 16 May 2021 20:45:44 +0500 Subject: [PATCH 36/37] Implement TermuxActivity callbacks in TermuxTerminalViewClient and TermuxTerminalSessionClient --- .../java/com/termux/app/TermuxActivity.java | 195 +++++++++--------- .../terminal/TermuxTerminalSessionClient.java | 37 ++++ .../terminal/TermuxTerminalViewClient.java | 50 +++++ 3 files changed, 179 insertions(+), 103 deletions(-) diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 766f941f45..7bf806be1e 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -229,23 +229,13 @@ public void onStart() { mIsVisible = true; - if (mTermuxService != null) { - // The service has connected, but data may have changed since we were last in the foreground. - // Get the session stored in shared preferences stored by {@link #onStop} if its valid, - // otherwise get the last session currently running. - mTermuxTerminalSessionClient.setCurrentSession(mTermuxTerminalSessionClient.getCurrentStoredSessionOrLast()); - termuxSessionListNotifyUpdated(); - } + if (mTermuxTerminalSessionClient != null) + mTermuxTerminalSessionClient.onStart(); - registerTermuxActivityBroadcastReceiver(); + if (mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onStart(); - // If user changed the preference from {@link TermuxSettings} activity and returns, then - // update the {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value. - mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.isTerminalViewKeyLoggingEnabled()); - - // The current terminal session may have changed while being away, force - // a refresh of the displayed terminal. - mTerminalView.onScreenUpdated(); + registerTermuxActivityBroadcastReceiver(); } @Override @@ -256,12 +246,64 @@ public void onResume() { if (mIsInvalidState) return; - mTermuxTerminalViewClient.setSoftKeyboardState(true, false); + if (mTermuxTerminalSessionClient != null) + mTermuxTerminalSessionClient.onResume(); + + if (mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onResume(); + } + + @Override + protected void onStop() { + super.onStop(); + + Logger.logDebug(LOG_TAG, "onStop"); + + if (mIsInvalidState) return; + + mIsVisible = false; + + if (mTermuxTerminalSessionClient != null) + mTermuxTerminalSessionClient.onStop(); + + if (mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onStop(); + + unregisterTermuxActivityBroadcastReceiever(); + getDrawer().closeDrawers(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + Logger.logDebug(LOG_TAG, "onDestroy"); + + if (mIsInvalidState) return; + + if (mTermuxService != null) { + // Do not leave service and session clients with references to activity. + mTermuxService.unsetTermuxTerminalSessionClient(); + mTermuxService = null; + } + + try { + unbindService(this); + } catch (Exception e) { + // ignore. + } + } - // Start terminal cursor blinking if enabled - mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true); + @Override + public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + saveTerminalToolbarTextInput(savedInstanceState); } + + + + /** * Part of the {@link ServiceConnection} interface. The service is bound with * {@link #bindService(Intent, ServiceConnection, int)} in {@link #onCreate(Bundle)} which will cause a call to this @@ -319,53 +361,7 @@ public void onServiceDisconnected(ComponentName name) { finishActivityIfNotFinishing(); } - @Override - protected void onStop() { - super.onStop(); - - Logger.logDebug(LOG_TAG, "onStop"); - - if (mIsInvalidState) return; - - mIsVisible = false; - - // Store current session in shared preferences so that it can be restored later in - // {@link #onStart} if needed. - mTermuxTerminalSessionClient.setCurrentStoredSession(); - - // Stop terminal cursor blinking if enabled - mTermuxTerminalViewClient.setTerminalCursorBlinkerState(false); - unregisterTermuxActivityBroadcastReceiever(); - getDrawer().closeDrawers(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - - Logger.logDebug(LOG_TAG, "onDestroy"); - - if (mIsInvalidState) return; - - if (mTermuxService != null) { - // Do not leave service and session clients with references to activity. - mTermuxService.unsetTermuxTerminalSessionClient(); - mTermuxService = null; - } - - try { - unbindService(this); - } catch (Exception e) { - // ignore. - } - } - - @Override - public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { - super.onSaveInstanceState(savedInstanceState); - saveTerminalToolbarTextInput(savedInstanceState); - } @@ -386,6 +382,32 @@ private void setDrawerTheme() { + private void setTermuxTerminalViewAndClients() { + // Set termux terminal view and session clients + mTermuxTerminalSessionClient = new TermuxTerminalSessionClient(this); + mTermuxTerminalViewClient = new TermuxTerminalViewClient(this, mTermuxTerminalSessionClient); + + // Set termux terminal view + mTerminalView = findViewById(R.id.terminal_view); + mTerminalView.setTerminalViewClient(mTermuxTerminalViewClient); + + if (mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onCreate(); + + if (mTermuxTerminalSessionClient != null) + mTermuxTerminalSessionClient.onCreate(); + } + + private void setTermuxSessionsListView() { + ListView termuxSessionsListView = findViewById(R.id.terminal_sessions_list); + mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessions()); + termuxSessionsListView.setAdapter(mTermuxSessionListViewController); + termuxSessionsListView.setOnItemClickListener(mTermuxSessionListViewController); + termuxSessionsListView.setOnItemLongClickListener(mTermuxSessionListViewController); + } + + + private void setTerminalToolbarView(Bundle savedInstanceState) { final ViewPager terminalToolbarViewPager = findViewById(R.id.terminal_toolbar_view_pager); if (mPreferences.shouldShowTerminalToolbar()) terminalToolbarViewPager.setVisibility(View.VISIBLE); @@ -466,36 +488,6 @@ private void setToggleKeyboardView() { - private void setTermuxTerminalViewAndClients() { - // Set termux terminal view and session clients - mTermuxTerminalSessionClient = new TermuxTerminalSessionClient(this); - mTermuxTerminalViewClient = new TermuxTerminalViewClient(this, mTermuxTerminalSessionClient); - - // Set termux terminal view - mTerminalView = findViewById(R.id.terminal_view); - mTerminalView.setTerminalViewClient(mTermuxTerminalViewClient); - - mTerminalView.setTextSize(mPreferences.getFontSize()); - mTerminalView.setKeepScreenOn(mPreferences.shouldKeepScreenOn()); - - // Set {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value - mTerminalView.setIsTerminalViewKeyLoggingEnabled(mPreferences.isTerminalViewKeyLoggingEnabled()); - - mTermuxTerminalSessionClient.checkForFontAndColors(); - } - - private void setTermuxSessionsListView() { - ListView termuxSessionsListView = findViewById(R.id.terminal_sessions_list); - mTermuxSessionListViewController = new TermuxSessionsListViewController(this, mTermuxService.getTermuxSessions()); - termuxSessionsListView.setAdapter(mTermuxSessionListViewController); - termuxSessionsListView.setOnItemClickListener(mTermuxSessionListViewController); - termuxSessionsListView.setOnItemLongClickListener(mTermuxSessionListViewController); - } - - - - - @SuppressLint("RtlHardcoded") @Override public void onBackPressed() { @@ -780,7 +772,7 @@ public void onReceive(Context context, Intent intent) { return; case TERMUX_ACTIVITY.ACTION_RELOAD_STYLE: Logger.logDebug(LOG_TAG, "Received intent to reload styling"); - reloadTermuxActivityStyling(); + reloadActivityStyling(); return; default: } @@ -788,11 +780,7 @@ public void onReceive(Context context, Intent intent) { } } - private void reloadTermuxActivityStyling() { - if (mTermuxTerminalSessionClient != null) { - mTermuxTerminalSessionClient.checkForFontAndColors(); - } - + private void reloadActivityStyling() { if (mProperties!= null) { mProperties.loadTermuxPropertiesFromDisk(); @@ -803,10 +791,11 @@ private void reloadTermuxActivityStyling() { setTerminalToolbarHeight(); - mTermuxTerminalViewClient.setSoftKeyboardState(false, true); - - mTermuxTerminalViewClient.setTerminalCursorBlinkerState(true); + if (mTermuxTerminalSessionClient != null) + mTermuxTerminalSessionClient.onReload(); + if (mTermuxTerminalViewClient != null) + mTermuxTerminalViewClient.onReload(); // To change the activity and drawer theme, activity needs to be recreated. // But this will destroy the activity, and will call the onCreate() again. diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java index 7033ac5084..4e836aef38 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionClient.java @@ -47,6 +47,31 @@ public TermuxTerminalSessionClient(TermuxActivity activity) { this.mActivity = activity; } + /** + * Should be called when mActivity.onCreate() is called + */ + public void onCreate() { + // Set terminal fonts and colors + checkForFontAndColors(); + } + + /** + * Should be called when mActivity.onStart() is called + */ + public void onStart() { + // The service has connected, but data may have changed since we were last in the foreground. + // Get the session stored in shared preferences stored by {@link #onStop} if its valid, + // otherwise get the last session currently running. + if (mActivity.getTermuxService() != null) { + setCurrentSession(getCurrentStoredSessionOrLast()); + termuxSessionListNotifyUpdated(); + } + + // The current terminal session may have changed while being away, force + // a refresh of the displayed terminal. + mActivity.getTerminalView().onScreenUpdated(); + } + /** * Should be called when mActivity.onResume() is called */ @@ -61,6 +86,10 @@ public void onResume() { * Should be called when mActivity.onStop() is called */ public void onStop() { + // Store current session in shared preferences so that it can be restored later in + // {@link #onStart} if needed. + setCurrentStoredSession(); + // Release mBellSoundPool resources, specially to prevent exceptions like the following to be thrown // java.util.concurrent.TimeoutException: android.media.SoundPool.finalize() timed out after 10 seconds // Bell is not played in background anyways @@ -68,6 +97,14 @@ public void onStop() { releaseBellSoundPool(); } + /** + * Should be called when mActivity.reloadActivityStyling() is called + */ + public void onReload() { + // Set terminal fonts and colors + checkForFontAndColors(); + } + @Override diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java index 246181199c..83ea528f5d 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalViewClient.java @@ -63,6 +63,56 @@ public TermuxTerminalViewClient(TermuxActivity activity, TermuxTerminalSessionCl this.mTermuxTerminalSessionClient = termuxTerminalSessionClient; } + /** + * Should be called when mActivity.onCreate() is called + */ + public void onCreate() { + mActivity.getTerminalView().setTextSize(mActivity.getPreferences().getFontSize()); + mActivity.getTerminalView().setKeepScreenOn(mActivity.getPreferences().shouldKeepScreenOn()); + } + + /** + * Should be called when mActivity.onStart() is called + */ + public void onStart() { + + // Set {@link TerminalView#TERMINAL_VIEW_KEY_LOGGING_ENABLED} value + // Also required if user changed the preference from {@link TermuxSettings} activity and returns + mActivity.getTerminalView().setIsTerminalViewKeyLoggingEnabled(mActivity.getPreferences().isTerminalViewKeyLoggingEnabled()); + } + + /** + * Should be called when mActivity.onResume() is called + */ + public void onResume() { + // Show the soft keyboard if required + setSoftKeyboardState(true, false); + + // Start terminal cursor blinking if enabled + setTerminalCursorBlinkerState(true); + } + + /** + * Should be called when mActivity.onStop() is called + */ + public void onStop() { + // Stop terminal cursor blinking if enabled + setTerminalCursorBlinkerState(false); + } + + /** + * Should be called when mActivity.reloadActivityStyling() is called + */ + public void onReload() { + // Show the soft keyboard if required + setSoftKeyboardState(false, true); + + // Start terminal cursor blinking if enabled + setTerminalCursorBlinkerState(true); + } + + + @Override public float onScale(float scale) { if (scale < 0.9f || scale > 1.1f) { From b711a467c189b21742164ef185416751e3d58d7a Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 16 May 2021 23:44:43 +0500 Subject: [PATCH 37/37] Bump to v0.113 --- app/build.gradle | 4 ++-- terminal-emulator/build.gradle | 2 +- terminal-view/build.gradle | 2 +- termux-shared/build.gradle | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 20d82f40e3..c540083510 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { applicationId "com.termux" minSdkVersion project.properties.minSdkVersion.toInteger() targetSdkVersion project.properties.targetSdkVersion.toInteger() - versionCode 112 - versionName "0.112" + versionCode 113 + versionName "0.113" manifestPlaceholders.TERMUX_PACKAGE_NAME = "com.termux" manifestPlaceholders.TERMUX_APP_NAME = "Termux" diff --git a/terminal-emulator/build.gradle b/terminal-emulator/build.gradle index db3f3e39e6..0ca52b83bf 100644 --- a/terminal-emulator/build.gradle +++ b/terminal-emulator/build.gradle @@ -63,7 +63,7 @@ publishing { bar(MavenPublication) { groupId 'com.termux' artifactId 'terminal-emulator' - version "0.112" + version "0.113" artifact(sourceJar) artifact("$buildDir/outputs/aar/terminal-emulator-release.aar") } diff --git a/terminal-view/build.gradle b/terminal-view/build.gradle index 3e78671ba2..c2273772c6 100644 --- a/terminal-view/build.gradle +++ b/terminal-view/build.gradle @@ -42,7 +42,7 @@ publishing { bar(MavenPublication) { groupId 'com.termux' artifactId 'terminal-view' - version "0.112" + version "0.113" artifact(sourceJar) artifact("$buildDir/outputs/aar/terminal-view-release.aar") } diff --git a/termux-shared/build.gradle b/termux-shared/build.gradle index 06238e984d..74128b0b6c 100644 --- a/termux-shared/build.gradle +++ b/termux-shared/build.gradle @@ -57,7 +57,7 @@ publishing { bar(MavenPublication) { groupId 'com.termux' artifactId 'termux-shared' - version "0.112" + version "0.113" artifact(sourceJar) artifact("$buildDir/outputs/aar/termux-shared-release.aar") }