From 58a29f006d5dfe4e366d7ccf7a741c45ab77723f Mon Sep 17 00:00:00 2001 From: Fox2Code Date: Wed, 20 Jul 2022 15:25:58 +0200 Subject: [PATCH] Implement background module update check & improve translator utils. (New strings) --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 11 +- .../java/com/fox2code/mmm/MainActivity.java | 18 ++- .../com/fox2code/mmm/MainApplication.java | 20 ++- .../background/BackgroundBootListener.java | 19 +++ .../background/BackgroundUpdateChecker.java | 115 ++++++++++++++++++ .../fox2code/mmm/module/ActionButtonType.java | 3 +- .../java/com/fox2code/mmm/repo/RepoData.java | 19 ++- .../mmm/settings/SettingsActivity.java | 96 ++++++++++++--- .../drawable/ic_baseline_notifications_24.xml | 10 ++ app/src/main/res/values/strings.xml | 20 +++ app/src/main/res/xml/root_preferences.xml | 18 +++ 12 files changed, 317 insertions(+), 33 deletions(-) create mode 100644 app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java create mode 100644 app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java create mode 100644 app/src/main/res/drawable/ic_baseline_notifications_24.xml diff --git a/app/build.gradle b/app/build.gradle index 38436fe..b4bd991 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -97,6 +97,7 @@ dependencies { implementation 'com.github.Fox2Code:FoxCompat:0.0.2' // Utils + implementation 'androidx.work:work-runtime:2.7.1' implementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.9.3' implementation 'com.squareup.okhttp3:okhttp-brotli:4.9.3' implementation 'com.github.topjohnwu.libsu:io:5.0.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 830b145..57b2c75 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,6 +11,8 @@ + + @@ -31,7 +33,14 @@ android:networkSecurityConfig="@xml/network_security_config" android:usesCleartextTraffic="false" tools:targetApi="s" - tools:replace="android:supportsRtl"> + tools:replace="android:supportsRtl" + tools:ignore="ManifestResource"> + + + + + { IntentHelper.startActivity(this, SettingsActivity.class); @@ -395,7 +404,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe this.searchView.clearFocus(); if (this.initMode) return false; if (this.moduleViewListBuilder.setQueryChange(query)) { - new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start(); + new Thread(() -> this.moduleViewListBuilder.applyTo( + moduleList, moduleViewAdapter), "Query update thread").start(); } return true; } @@ -404,7 +414,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe public boolean onQueryTextChange(String query) { if (this.initMode) return false; if (this.moduleViewListBuilder.setQueryChange(query)) { - new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start(); + new Thread(() -> this.moduleViewListBuilder.applyTo( + moduleList, moduleViewAdapter), "Query update thread").start(); } return false; } @@ -413,7 +424,8 @@ public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRe public boolean onClose() { if (this.initMode) return false; if (this.moduleViewListBuilder.setQueryChange(null)) { - new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start(); + new Thread(() -> this.moduleViewListBuilder.applyTo( + moduleList, moduleViewAdapter), "Query update thread").start(); } return false; } diff --git a/app/src/main/java/com/fox2code/mmm/MainApplication.java b/app/src/main/java/com/fox2code/mmm/MainApplication.java index 33b7f0a..8ae52fc 100644 --- a/app/src/main/java/com/fox2code/mmm/MainApplication.java +++ b/app/src/main/java/com/fox2code/mmm/MainApplication.java @@ -17,10 +17,16 @@ import androidx.annotation.StyleRes; import androidx.emoji2.text.DefaultEmojiCompatConfig; import androidx.emoji2.text.EmojiCompat; import androidx.emoji2.text.FontRequestEmojiCompatConfig; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; import com.fox2code.foxcompat.FoxActivity; import com.fox2code.foxcompat.FoxApplication; import com.fox2code.foxcompat.FoxThemeWrapper; +import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.utils.GMSProviderInstaller; import com.fox2code.mmm.utils.Http; @@ -31,6 +37,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import java.util.Random; +import java.util.concurrent.TimeUnit; import io.noties.markwon.Markwon; import io.noties.markwon.html.HtmlPlugin; @@ -47,7 +54,8 @@ import io.noties.prism4j.annotations.PrismBundle; includeAll = true, grammarLocatorClassName = ".Prism4jGrammarLocator" ) -public class MainApplication extends FoxApplication { +public class MainApplication extends FoxApplication + implements androidx.work.Configuration.Provider { private static final String timeFormatString = "dd MMM yyyy"; // Example: 13 july 2001 private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().locale; @@ -146,6 +154,10 @@ public class MainApplication extends FoxApplication { && isDeveloper(); } + public static boolean isBackgroundUpdateCheckEnabled() { + return getSharedPreferences().getBoolean("pref_background_update_check", true); + } + public static boolean isFirstBoot() { return firstBoot; } @@ -196,6 +208,12 @@ public class MainApplication extends FoxApplication { return this.markwonThemeContext; } + @NonNull + @Override + public androidx.work.Configuration getWorkManagerConfiguration() { + return new androidx.work.Configuration.Builder().build(); + } + private class Prism4jSwitchTheme implements Prism4jTheme { private final Prism4jTheme light = new Prism4jThemeDefault(Color.TRANSPARENT); private final Prism4jTheme dark = new Prism4jThemeDarkula(Color.TRANSPARENT); diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java new file mode 100644 index 0000000..0d26547 --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundBootListener.java @@ -0,0 +1,19 @@ +package com.fox2code.mmm.background; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.fox2code.mmm.MainApplication; + +public class BackgroundBootListener extends BroadcastReceiver { + private static final String BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; + + @Override + public void onReceive(Context context, Intent intent) { + if (!BOOT_COMPLETED.equals(intent.getAction())) return; + if (!MainApplication.isBackgroundUpdateCheckEnabled()) return; + BackgroundUpdateChecker.onMainActivityCreate(context); + BackgroundUpdateChecker.doCheck(context); + } +} diff --git a/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java new file mode 100644 index 0000000..c04cd69 --- /dev/null +++ b/app/src/main/java/com/fox2code/mmm/background/BackgroundUpdateChecker.java @@ -0,0 +1,115 @@ +package com.fox2code.mmm.background; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.Build; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationChannelCompat; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.work.Constraints; +import androidx.work.ExistingPeriodicWorkPolicy; +import androidx.work.NetworkType; +import androidx.work.PeriodicWorkRequest; +import androidx.work.WorkManager; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.fox2code.mmm.MainActivity; +import com.fox2code.mmm.MainApplication; +import com.fox2code.mmm.R; +import com.fox2code.mmm.manager.LocalModuleInfo; +import com.fox2code.mmm.manager.ModuleManager; +import com.fox2code.mmm.repo.RepoManager; +import com.fox2code.mmm.repo.RepoModule; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +public class BackgroundUpdateChecker extends Worker { + private static boolean easterEggActive = false; + public static final String NOTIFICATION_CHANNEL_ID = "background_update"; + public static final int NOTIFICATION_ID = 1; + + public BackgroundUpdateChecker(@NonNull Context context, + @NonNull WorkerParameters workerParams) { + super(context, workerParams); + } + + @NonNull + @Override + public Result doWork() { + if (!NotificationManagerCompat.from(this.getApplicationContext()).areNotificationsEnabled() + || !MainApplication.isBackgroundUpdateCheckEnabled()) return Result.success(); + + doCheck(this.getApplicationContext()); + + return Result.success(); + } + + static void doCheck(Context context) { + Thread.currentThread().setPriority(Thread.MIN_PRIORITY); + RepoManager.getINSTANCE().update(null); + ModuleManager.getINSTANCE().scan(); + ModuleManager.getINSTANCE().scan(); + int moduleUpdateCount = 0; + for (LocalModuleInfo localModuleInfo : + ModuleManager.getINSTANCE().getModules().values()) { + RepoModule repoModule = RepoManager.getINSTANCE() + .getModules().get(localModuleInfo.id); + localModuleInfo.checkModuleUpdate(); + if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode) { + moduleUpdateCount++; + } else if (repoModule != null && + repoModule.moduleInfo.versionCode > localModuleInfo.versionCode) { + moduleUpdateCount++; + } + } + if (moduleUpdateCount != 0) { + postNotification(context, moduleUpdateCount); + } + } + + public static void postNotification(Context context, int updateCount) { + if (!easterEggActive) easterEggActive = new Random().nextInt(100) <= updateCount; + NotificationCompat.Builder builder = new NotificationCompat.Builder( + context, NOTIFICATION_CHANNEL_ID) + .setContentTitle(context.getString(easterEggActive ? + R.string.notification_update_title_easter_egg : + R.string.notification_update_title) + .replace("%i", String.valueOf(updateCount))) + .setContentText(context.getString(R.string.notification_update_subtitle)) + .setSmallIcon(R.drawable.ic_baseline_extension_24) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(PendingIntent.getActivity(context, 0, + new Intent(context, MainActivity.class).setFlags( + Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK), + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + PendingIntent.FLAG_IMMUTABLE : 0)).setAutoCancel(true); + NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, builder.build()); + } + + public static void onMainActivityCreate(Context context) { + NotificationManagerCompat notificationManagerCompat = + NotificationManagerCompat.from(context); + notificationManagerCompat.createNotificationChannel( + new NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL_ID, + NotificationManagerCompat.IMPORTANCE_HIGH).setShowBadge(true) + .setName(context.getString(R.string.notification_update_pref)).build()); + notificationManagerCompat.cancel(BackgroundUpdateChecker.NOTIFICATION_ID); + BackgroundUpdateChecker.easterEggActive = false; + WorkManager.getInstance(context).enqueueUniquePeriodicWork("background_checker", + ExistingPeriodicWorkPolicy.REPLACE, new PeriodicWorkRequest.Builder( + BackgroundUpdateChecker.class, 6, TimeUnit.HOURS) + .setConstraints(new Constraints.Builder().setRequiresBatteryNotLow(true) + .setRequiredNetworkType(NetworkType.UNMETERED).build()).build()); + } + + public static void onMainActivityResume(Context context) { + NotificationManagerCompat.from(context).cancel( + BackgroundUpdateChecker.NOTIFICATION_ID); + BackgroundUpdateChecker.easterEggActive = false; + } +} diff --git a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java index 0157144..e9a2ebd 100644 --- a/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java +++ b/app/src/main/java/com/fox2code/mmm/module/ActionButtonType.java @@ -253,7 +253,8 @@ public enum ActionButtonType { if (url.startsWith("https://www.paypal.me/") || url.startsWith("https://www.paypal.com/paypalme/")) { icon = R.drawable.ic_baseline_paypal_24; - } else if (url.startsWith("https://www.patreon.com/")) { + } else if (url.startsWith("https://patreon.com/") || + url.startsWith("https://www.patreon.com/")) { icon = R.drawable.ic_patreon; } return icon; diff --git a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java index 5c50be4..fdd4f45 100644 --- a/app/src/main/java/com/fox2code/mmm/repo/RepoData.java +++ b/app/src/main/java/com/fox2code/mmm/repo/RepoData.java @@ -212,11 +212,14 @@ public class RepoData extends XRepo { return this.id; } + private static boolean isNonNull(String str) { + return str != null && !str.isEmpty() && !"null".equals(str); + } + // Repo data info getters @NonNull public String getName() { - if (this.name != null && - !this.name.isEmpty()) + if (isNonNull(this.name)) return this.name; if (this.defaultName != null) return this.defaultName; @@ -225,8 +228,7 @@ public class RepoData extends XRepo { @NonNull public String getWebsite() { - if (this.website != null && - !this.website.isEmpty()) + if (isNonNull(this.website)) return this.website; if (this.defaultWebsite != null) return this.defaultWebsite; @@ -234,22 +236,19 @@ public class RepoData extends XRepo { } public String getSupport() { - if (this.support != null && - !this.support.isEmpty()) + if (isNonNull(this.support)) return this.support; return this.defaultSupport; } public String getDonate() { - if (this.donate != null && - !this.donate.isEmpty()) + if (isNonNull(this.donate)) return this.donate; return this.defaultDonate; } public String getSubmitModule() { - if (this.submitModule != null && - !this.submitModule.isEmpty()) + if (isNonNull(this.submitModule)) return this.submitModule; return this.defaultSubmitModule; } diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 91d9a84..0563d75 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -6,7 +6,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.content.res.Configuration; import android.os.Build; import android.os.Bundle; import android.util.Log; @@ -33,6 +32,7 @@ import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; +import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.module.ActionButtonType; import com.fox2code.mmm.repo.CustomRepoData; @@ -54,8 +54,10 @@ import org.json.JSONException; import java.io.IOException; import java.util.HashSet; import java.util.Objects; +import java.util.Random; public class SettingsActivity extends FoxActivity implements LanguageActivity { + private static final int LANGUAGE_SUPPORT_LEVEL = 1; private static final String TAG = "SettingsActivity"; private static int devModeStep = 0; @@ -88,6 +90,12 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { System.exit(0); // Exit app process } + @Override + protected void onPause() { + BackgroundUpdateChecker.onMainActivityResume(this); + super.onPause(); + } + public static class SettingsFragment extends PreferenceFragmentCompat implements FoxActivity.OnBackPressedCallback { @Override @@ -145,10 +153,12 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { HashSet supportedLocales = new HashSet<>(); supportedLocales.add("cs"); supportedLocales.add("de"); + supportedLocales.add("el"); supportedLocales.add("es-rMX"); supportedLocales.add("et"); supportedLocales.add("fr"); supportedLocales.add("id"); + supportedLocales.add("it"); supportedLocales.add("ja"); supportedLocales.add("nb-rNO"); supportedLocales.add("pl"); @@ -170,18 +180,20 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { return true; }); - int nightModeFlags = - getContext().getResources().getConfiguration().uiMode & - Configuration.UI_MODE_NIGHT_MASK; - switch (nightModeFlags) { - case Configuration.UI_MODE_NIGHT_YES: - findPreference("pref_force_dark_terminal").setEnabled(false); - break; - case Configuration.UI_MODE_NIGHT_NO: - case Configuration.UI_MODE_NIGHT_UNDEFINED: - findPreference("pref_force_dark_terminal").setEnabled(true); - break; + int level = this.currentLanguageLevel(); + if (level != LANGUAGE_SUPPORT_LEVEL) { + Log.e(TAG, "Detected language level " + level + + ", latest is " + LANGUAGE_SUPPORT_LEVEL); + languageSelector.setSummary(R.string.language_support_outdated); + } else { + if (!"Translated by Fox2Code".equals( // I don't "translate" english + this.getString(R.string.language_translated_by))) { + languageSelector.setSummary(R.string.language_translated_by); + } else { + languageSelector.setSummary(null); + } } + if (!MainApplication.isDeveloper()) { findPreference("pref_disable_low_quality_module_filter").setVisible(false); } @@ -189,6 +201,23 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { || !MainApplication.isDeveloper()) { findPreference("pref_use_magisk_install_command").setVisible(false); } + Preference debugNotification = findPreference("pref_background_update_check_debug"); + debugNotification.setEnabled(MainApplication.isBackgroundUpdateCheckEnabled()); + debugNotification.setVisible(MainApplication.isDeveloper()); + debugNotification.setOnPreferenceClickListener(preference -> { + BackgroundUpdateChecker.postNotification( + this.requireContext(), new Random().nextInt(4) + 2); + return true; + }); + findPreference("pref_background_update_check").setOnPreferenceChangeListener((preference, newValue) -> { + boolean enabled = Boolean.parseBoolean(String.valueOf(newValue)); + debugNotification.setEnabled(enabled); + if (!enabled) { + BackgroundUpdateChecker.onMainActivityResume( + this.requireContext()); + } + return true; + }); final LibsBuilder libsBuilder = new LibsBuilder().withShowLoadingProgress(false) .withLicenseShown(true).withAboutMinimalDesign(false); @@ -201,13 +230,30 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { "https://github.com/Fox2Code/FoxMagiskModuleManager/releases"); return true; }); + if (BuildConfig.DEBUG || BuildConfig.ENABLE_AUTO_UPDATER) { + findPreference("pref_report_bug").setOnPreferenceClickListener(p -> { + devModeStep = 0; + IntentHelper.openUrl(p.getContext(), + "https://github.com/Fox2Code/FoxMagiskModuleManager/issues"); + return true; + }); + } else { + findPreference("pref_report_bug").setVisible(false); + } findPreference("pref_source_code").setOnPreferenceClickListener(p -> { - if (devModeStep == 2 && (BuildConfig.DEBUG || !MainApplication.isDeveloper())) { + if (devModeStep == 2) { devModeStep = 0; - MainApplication.getSharedPreferences().edit() - .putBoolean("developer", true).apply(); - Toast.makeText(getContext(), // Tell the user something changed - R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show(); + if (MainApplication.isDeveloper() && !BuildConfig.DEBUG) { + MainApplication.getSharedPreferences().edit() + .putBoolean("developer", false).apply(); + Toast.makeText(getContext(), // Tell the user something changed + R.string.dev_mode_disabled, Toast.LENGTH_SHORT).show(); + } else { + MainApplication.getSharedPreferences().edit() + .putBoolean("developer", true).apply(); + Toast.makeText(getContext(), // Tell the user something changed + R.string.dev_mode_enabled, Toast.LENGTH_SHORT).show(); + } return true; } IntentHelper.openUrl(p.getContext(), @@ -221,6 +267,7 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { }); findPreference("pref_show_licenses").setOnPreferenceClickListener(p -> { devModeStep = devModeStep == 1 ? 2 : 0; + BackgroundUpdateChecker.onMainActivityResume(this.requireContext()); openFragment(libsBuilder.supportFragment(), R.string.licenses); return true; }); @@ -250,6 +297,21 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { .commit(); return true; } + + private int currentLanguageLevel() { + int declaredLanguageLevel = + this.getResources().getInteger(R.integer.language_support_level); + if (declaredLanguageLevel != LANGUAGE_SUPPORT_LEVEL) + return declaredLanguageLevel; + if (!this.getResources().getConfiguration().locale.getLanguage().equals("en") && + this.getResources().getString(R.string.notification_update_pref) + .equals("Background modules update check") && + this.getResources().getString(R.string.notification_update_desc) + .equals("May increase battery usage")) { + return 0; + } + return LANGUAGE_SUPPORT_LEVEL; + } } public static class RepoFragment extends PreferenceFragmentCompat { diff --git a/app/src/main/res/drawable/ic_baseline_notifications_24.xml b/app/src/main/res/drawable/ic_baseline_notifications_24.xml new file mode 100644 index 0000000..6994560 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_notifications_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6525e78..7948e57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -27,6 +27,10 @@ Description Uninstall Config + Favourite + Report bugs + Sniffed modules + Sniffing modules Submit a module Requires Android 6.0+ Requires Android 12+ @@ -96,6 +100,7 @@ so this option behind is hidden behind developer mode.\nTurn this on at your own risk! Developer mode on + Developer mode off English app language Show low-quality modules @@ -128,6 +133,21 @@ Backup modules Restore modules This operation require an internet connection + + + Found %i module updates + Sniffed %i module updates + Click to open the app + Background modules update check + May increase battery usage + Test Notification + false + + 1 + Some translations for the current language are + not up-to-date, please consider contributing to the app translations on GitHub + + Translated by Fox2Code diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 38cc9e1..5325cb9 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -31,6 +31,19 @@ app:title="@string/use_magisk_install_command_pref" app:summary="@string/use_magisk_install_command_desc" app:singleLineTitle="false" /> + + + + @@ -111,6 +124,11 @@ app:icon="@drawable/ic_baseline_system_update_24" app:title="@string/app_update" app:singleLineTitle="false" /> +