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" />
+