diff --git a/app/build.gradle b/app/build.gradle index 1d9dbf6586..fc8a23d53e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -256,17 +256,17 @@ dependencies { implementation "com.github.PhilJay:MPAndroidChart:3.1.0" implementation "com.github.pfichtner:durationformatter:0.1.1" implementation "de.cketti.library.changelog:ckchangelog:1.2.2" - //noinspection NewerVersionAvailable v2 needs java 17 implementation "net.e175.klaus:solarpositioning:0.1.10" implementation "co.nstant.in:cbor:0.9" // use pristine greendao instead of our custom version, since our custom jitpack-packaged // version contains way too much and our custom patches are in the generator only. - //noinspection NewerVersionAvailable we are running a fork of the greendao v2 generator, - // so we must always use the corresponding core lib implementation "org.greenrobot:greendao:2.2.1" implementation "org.apache.commons:commons-lang3:3.18.0" implementation "org.cyanogenmod:platform.sdk:6.0" implementation 'com.jaredrummler:colorpicker:1.1.0' + implementation 'com.github.wax911.android-emojify:contract:1.9.7' + implementation 'com.github.wax911.android-emojify:emojify:1.9.7' + implementation 'com.github.wax911.android-emojify:gson:1.9.7' implementation 'com.google.protobuf:protobuf-javalite:4.31.1' implementation 'com.android.volley:volley:1.2.1' implementation 'org.msgpack:msgpack-core:0.9.10' @@ -290,9 +290,6 @@ dependencies { // Needed for Armenian transliteration implementation 'org.ahocorasick:ahocorasick:0.6.3' - - // Needed for Emoji transliteration - implementation 'net.fellbaum:jemoji:1.7.4' } preBuild.dependsOn(":GBDaoGenerator:genSources") diff --git a/app/src/main/assets/licenses/android-emojify.txt b/app/src/main/assets/licenses/android-emojify.txt new file mode 100644 index 0000000000..5de187cc9d --- /dev/null +++ b/app/src/main/assets/licenses/android-emojify.txt @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2018 AniTrend + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/app/src/main/assets/licenses/licenses.json b/app/src/main/assets/licenses/licenses.json index 5c544da2a8..ac55db0261 100644 --- a/app/src/main/assets/licenses/licenses.json +++ b/app/src/main/assets/licenses/licenses.json @@ -7,11 +7,11 @@ "path": "licenses/aho-corasick.txt" }, { - "name": "JEmoji", - "owner": "Dominic Fellbaum", + "name": "android-emojify", + "owner": "AniTrend", "type": "Apache 2.0", - "url": "https://github.com/felldo/JEmoji", - "path": "licenses/apache-2.0.txt" + "url": "https://github.com/AniTrend/android-emojify", + "path": "licenses/android-emojify.txt" }, { "name": "Android Jetpack", diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java index e941ba5f97..64f32f3c7d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/banglejs/BangleJSDeviceSupport.java @@ -66,9 +66,6 @@ import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; -import net.fellbaum.jemoji.Emoji; -import net.fellbaum.jemoji.EmojiManager; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -96,10 +93,11 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.SimpleTimeZone; import de.greenrobot.dao.query.QueryBuilder; +import io.wax911.emojify.EmojiManager; +import io.wax911.emojify.parser.EmojiParserKt; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; @@ -1365,8 +1363,8 @@ public class BangleJSDeviceSupport extends AbstractBTLESingleDeviceSupport { private String renderUnicodeWordPartAsImage(final String word) { // check for emoji - Set emojis = EmojiManager.extractEmojis(word); - final boolean hasEmoji = !emojis.isEmpty(); + final EmojiManager emojiManager = EmojiConverter.getEmojiManager(getContext()); + final boolean hasEmoji = !EmojiParserKt.extractEmojis(emojiManager, word).isEmpty(); // if we had emoji, ensure we create 3 bit color (not 1 bit B&W) final BangleJSBitmapStyle style = hasEmoji ? BangleJSBitmapStyle.RGB_3BPP_TRANSPARENT : BangleJSBitmapStyle.MONOCHROME_TRANSPARENT; return "\0"+bitmapToEspruinoString(textToBitmap(word), style); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java index c07b3db358..f5e83cf0df 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverter.java @@ -19,13 +19,24 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.content.Context; -import java.util.Comparator; -import java.util.List; -import java.util.Objects; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; -import net.fellbaum.jemoji.EmojiManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import io.wax911.emojify.EmojiManager; +import io.wax911.emojify.contract.model.IEmoji; +import io.wax911.emojify.parser.EmojiParserKt; +import io.wax911.emojify.parser.candidate.UnicodeCandidate; +import io.wax911.emojify.parser.transformer.EmojiTransformer; +import io.wax911.emojify.serializer.gson.GsonDeserializer; public class EmojiConverter { + private static final Logger LOG = LoggerFactory.getLogger(EmojiConverter.class); + private static final String[][] simpleEmojiMapping = { {"\uD83D\uDE00", ":-D"}, // grinning {"\uD83D\uDE01", ":-D"}, // grinning_face_with_smiling_eyes @@ -63,6 +74,28 @@ public class EmojiConverter { {"\u2764", "<3"}, // heart }; + private static EmojiManager emojiManagerInstance; + + /** + * An emoji transformer that removes fitzpatrick modifiers. This partially reimplements + * EmojiParser.parseToAliases since I was unable to call it directly with FitzpatrickAction.REMOVE + */ + private static final EmojiTransformer EMOJI_TRANSFORMER_NO_FITZPATRICK = new EmojiTransformer() { + @Nullable + @Override + public String invoke(@NonNull final UnicodeCandidate unicodeCandidate) { + final IEmoji emoji = unicodeCandidate.getEmoji(); + if (emoji == null) { + return null; + } + final List aliases = emoji.getAliases(); + if (aliases == null || aliases.isEmpty()) { + return null; + } + return String.format(":%s:", aliases.get(0)); + } + }; + private static String convertSimpleEmojiToAscii(String text) { for (String[] emojiMap : simpleEmojiMapping) { text = text.replace(emojiMap[0], emojiMap[1]); @@ -70,15 +103,22 @@ public class EmojiConverter { return text; } - private static String convertAdvancedEmojiToAscii(final String text, final Context ignored) { - return EmojiManager.replaceAllEmojis( - text, - emoji -> emoji.getAllAliases().stream() - .findAny() - // Discard fitzpatrick modifiers - .map(a -> a.substring(0, a.indexOf(':', 1) + 1)) - .orElse("") - ); + public static synchronized EmojiManager getEmojiManager(final Context context) { + // Do a lazy initialisation not to slowdown the startup and when it is needed + if (emojiManagerInstance == null) { + emojiManagerInstance = EmojiManager.Companion.create(context, new GsonDeserializer()); + } + return emojiManagerInstance; + } + + private static String convertAdvancedEmojiToAscii(final String text, final Context context) { + final EmojiManager emojiManager = getEmojiManager(context); + try { + return EmojiParserKt.parseFromUnicode(emojiManager, text, EMOJI_TRANSFORMER_NO_FITZPATRICK); + } catch (final Exception e) { + LOG.warn("An exception occurred when converting advanced emoji to ASCII: {}", text); + return text; + } } public static String convertUnicodeEmojiToAscii(String text, final Context context) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java index cca1f06ed1..79167d66d7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/LanguageUtils.java @@ -40,7 +40,6 @@ import nodomain.freeyourgadget.gadgetbridge.util.language.impl.BengaliTransliter import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CommonSymbolsTransliterator; import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CroatianTransliterator; import nodomain.freeyourgadget.gadgetbridge.util.language.impl.CzechTransliterator; -import nodomain.freeyourgadget.gadgetbridge.util.language.impl.EmojiTransliterator; import nodomain.freeyourgadget.gadgetbridge.util.language.impl.EstonianTransliterator; import nodomain.freeyourgadget.gadgetbridge.util.language.impl.ExtendedAsciiTransliterator; import nodomain.freeyourgadget.gadgetbridge.util.language.impl.FlattenToAsciiTransliterator; @@ -91,7 +90,6 @@ public class LanguageUtils { put("turkish", new TurkishTransliterator()); put("ukranian", new UkranianTransliterator()); put("armenian", new ArmenianTransliterator()); - put("emoji", new EmojiTransliterator()); }}; /** diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EmojiTransliterator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EmojiTransliterator.java deleted file mode 100644 index 2ad7b05bc7..0000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/language/impl/EmojiTransliterator.java +++ /dev/null @@ -1,34 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.util.language.impl; - -import net.fellbaum.jemoji.EmojiManager; - -import java.util.Comparator; -import java.util.Objects; - -import nodomain.freeyourgadget.gadgetbridge.util.language.Transliterator; - -public class EmojiTransliterator implements Transliterator { - private static boolean isAllASCII(String input) { - boolean isASCII = true; - for (int i = 0; i < input.length(); i++) { - int c = input.charAt(i); - if (c > 0x7F) { - isASCII = false; - break; - } - } - return isASCII; - } - - @Override - public String transliterate(String txt) { - return EmojiManager.replaceAllEmojis(txt, emoji -> Objects.requireNonNullElse( - emoji.getAllAliases() - .stream() - .filter(Objects::nonNull) - // This is required, because in another case emoji's will be returned - .filter(EmojiTransliterator::isAllASCII) - .min(Comparator.comparingInt(String::length)) - .orElse(""), "")); - } -} diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index f6c8614b7b..9656c9c3c1 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -4615,7 +4615,6 @@ @string/ukranian @string/hungarian @string/armenian - @string/emoji @@ -4644,7 +4643,6 @@ ukranian hungarian armenian - emoji diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b94435dbb6..c2cbebe161 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1267,7 +1267,6 @@ Catalan Finnish Bahasa Melayu - Emoji About to transfer data since %1$s Waiting for reconnect About you diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/EmojiTransliteratorTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/EmojiTransliteratorTest.java deleted file mode 100644 index 33ec06e125..0000000000 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/test/EmojiTransliteratorTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.test; - -import junit.framework.TestCase; -import org.junit.Test; -import org.junit.Assert; - -import nodomain.freeyourgadget.gadgetbridge.util.language.impl.EmojiTransliterator; - -public class EmojiTransliteratorTest extends TestCase { - - @Test - public void testSimpleEmoji() { - Assert.assertEquals("<3", new EmojiTransliterator().transliterate("❤\uFE0F")); - Assert.assertEquals(":grinning:", new EmojiTransliterator().transliterate("\uD83D\uDE00")); - } - - @Test - public void testCompoundEmoji() { - Assert.assertEquals(":factory_worker_tone4:", new EmojiTransliterator().transliterate("\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83C\uDFED")); - } -} diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java index 49ada1c1ee..6e07e435c6 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/util/EmojiConverterTest.java @@ -13,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.test.TestBase; public class EmojiConverterTest extends TestBase { @Test public void testConvert() { - final Map snippets = new HashMap<>() {{ + final Map snippets = new HashMap() {{ put( "no emoji", "no emoji" diff --git a/renovate.json b/renovate.json index 9705ff4099..7866de68ef 100644 --- a/renovate.json +++ b/renovate.json @@ -23,6 +23,12 @@ "androidx.camera:*" ] }, + { + "groupName": "android-emojify", + "matchPackageNames": [ + "com.github.wax911.android-emojify:*" + ] + }, { "groupName": "mapsforge", "matchPackageNames": [