(misc) tweaks

Signed-off-by: androidacy-user <opensource@androidacy.com>
pull/299/head
androidacy-user 1 year ago
parent c4f1869331
commit ac845a0c0c

@ -191,6 +191,11 @@
android:resource="@xml/shared_file_paths" />
</provider>
<service
android:name=".background.BackgroundUpdateCheckerService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />
<meta-data
android:name="io.sentry.auto-init"
android:value="false" />

@ -52,6 +52,7 @@ import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
@ -89,6 +90,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
@SuppressLint("RestrictedApi")
// Use FoxProcess wrapper helper.
private static final boolean wrapped = !FoxProcessExt.isRootLoader();
private static boolean SHOWCASE_MODE_TRUE = false;
public static boolean isOfficial = false;
private static long secret;
private static Locale timeFormatLocale = Resources.getSystem().getConfiguration().getLocales().get(0);
@ -98,6 +100,7 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
private static MainApplication INSTANCE;
private static boolean firstBoot;
private static HashMap<Object, Object> mSharedPrefs;
private static final ArrayList<String> callers = new ArrayList<>();
static {
Shell.setDefaultBuilder(shellBuilder = Shell.Builder.create().setFlags(Shell.FLAG_REDIRECT_STDERR).setTimeout(10).setInitializers(InstallerInitializer.class));
@ -140,6 +143,24 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
Timber.d("Creating shared prefs map");
mSharedPrefs = new HashMap<>();
}
/*
this part is only here because with added encryption, parts of code that were previously calling this over and over again or on each invocation of a method are causing performance issues.
*/
if (BuildConfig.DEBUG) {
// get file, function, and line number
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
// get the caller of this method
StackTraceElement caller = stackTrace[3];
Timber.d("Shared prefs file: %s, caller: %s:%d", name, caller.getMethodName(), caller.getLineNumber());
// add the caller to an array. if the last 3 callers are the same, then we are in a loop, log at error level
callers.add(name + ":" + caller.getLineNumber() + ":" + caller.getMethodName());
// get the last 3 callers
List<String> last3 = callers.subList(Math.max(callers.size() - 3, 0), callers.size());
// if the last 3 callers are the same, then we are in a loop, log at error level
if (last3.size() == 3 && last3.get(0).equals(last3.get(1)) && last3.get(1).equals(last3.get(2))) {
Timber.e("Shared prefs loop detected. File: %s, caller: %s:%d", name, caller.getMethodName(), caller.getLineNumber());
}
}
if (mSharedPrefs.containsKey(name)) {
Timber.d("Returning cached shared prefs");
return (SharedPreferences) mSharedPrefs.get(name);
@ -167,7 +188,10 @@ public class MainApplication extends FoxApplication implements androidx.work.Con
}
public static boolean isShowcaseMode() {
return getPreferences("mmm").getBoolean("pref_showcase_mode", false);
if (SHOWCASE_MODE_TRUE) return true;
boolean showcaseMode = getPreferences("mmm").getBoolean("pref_showcase_mode", false);
SHOWCASE_MODE_TRUE = showcaseMode;
return showcaseMode;
}
public static boolean shouldPreventReboot() {

@ -43,6 +43,7 @@ import timber.log.Timber;
@SuppressWarnings("KotlinInternalInJava")
public final class AndroidacyRepoData extends RepoData {
public static String ANDROIDACY_DEVICE_ID = null;
public static String token = MainApplication.getPreferences("androidacy").getString("pref_androidacy_api_token", null);
static {
@ -86,10 +87,15 @@ public final class AndroidacyRepoData extends RepoData {
// Generates a unique device ID. This is used to identify the device in the API for rate
// limiting and fraud detection.
public static String generateDeviceId() {
// first, check if ANDROIDACY_DEVICE_ID is already set
if (ANDROIDACY_DEVICE_ID != null) {
return ANDROIDACY_DEVICE_ID;
}
// Try to get the device ID from the shared preferences
SharedPreferences sharedPreferences = MainApplication.getPreferences("androidacy");
String deviceIdPref = sharedPreferences.getString("device_id", null);
if (deviceIdPref != null) {
ANDROIDACY_DEVICE_ID = deviceIdPref;
return deviceIdPref;
} else {
// Really not that scary - just hashes some device info. We can't even get the info
@ -119,6 +125,7 @@ public final class AndroidacyRepoData extends RepoData {
digest = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException ignored) {
// This should never happen so we can just return the original device ID
ANDROIDACY_DEVICE_ID = deviceId;
return deviceId;
}
byte[] hash = digest.digest(deviceId.getBytes());
@ -135,6 +142,8 @@ public final class AndroidacyRepoData extends RepoData {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("device_id", hexString.toString());
editor.apply();
// Set ANDROIDACY_DEVICE_ID
ANDROIDACY_DEVICE_ID = hexString.toString();
// Return it
return hexString.toString();
}
@ -151,8 +160,7 @@ public final class AndroidacyRepoData extends RepoData {
// set role and permissions on userInfo property
userInfo = new String[][]{{"role", memberLevel}, {"permissions", String.valueOf(memberPermissions)}};
return true;
} catch (
HttpException e) {
} catch (HttpException e) {
if (e.getErrorCode() == 401) {
Timber.w("Invalid token, resetting...");
// Remove saved preference
@ -162,8 +170,7 @@ public final class AndroidacyRepoData extends RepoData {
return false;
}
throw e;
} catch (
JSONException e) {
} catch (JSONException e) {
// response is not JSON
Timber.w("Invalid token, resetting...");
Timber.w(e);
@ -185,8 +192,7 @@ public final class AndroidacyRepoData extends RepoData {
editor.apply();
return false;
}
if (Http.needCaptchaAndroidacy())
return false;
if (Http.needCaptchaAndroidacy()) return false;
// Implementation details discussed on telegram
// First, ping the server to check if it's alive
try {
@ -208,15 +214,13 @@ public final class AndroidacyRepoData extends RepoData {
}
return false;
}
} catch (
Exception e) {
} catch (Exception e) {
Timber.e(e, "Failed to ping server");
return false;
}
String deviceId = generateDeviceId();
long time = System.currentTimeMillis();
if (this.androidacyBlockade > time)
return true; // fake it till you make it. Basically,
if (this.androidacyBlockade > time) return true; // fake it till you make it. Basically,
// don't fail just because we're rate limited. API and web rate limits are different.
this.androidacyBlockade = time + 30_000L;
try {
@ -234,8 +238,7 @@ public final class AndroidacyRepoData extends RepoData {
} else {
Timber.i("Using validated cached token");
}
} catch (
IOException e) {
} catch (IOException e) {
if (HttpException.shouldTimeout(e)) {
Timber.e(e, "We are being rate limited!");
this.androidacyBlockade = time + 3_600_000L;
@ -256,8 +259,7 @@ public final class AndroidacyRepoData extends RepoData {
//noinspection SuspiciousRegexArgument
Timber.d("Token: %s", token.substring(0, token.length() - 4).replaceAll(".", "*") + token.substring(token.length() - 4));
memberLevel = jsonObject.getString("role");
} catch (
JSONException e) {
} catch (JSONException e) {
Timber.e(e, "Failed to parse token");
// Show a toast
Looper mainLooper = Looper.getMainLooper();
@ -280,8 +282,7 @@ public final class AndroidacyRepoData extends RepoData {
editor.apply();
Timber.i("Token saved to shared preference");
}
} catch (
Exception e) {
} catch (Exception e) {
if (HttpException.shouldTimeout(e)) {
Timber.e(e, "We are being rate limited!");
this.androidacyBlockade = time + 3_600_000L;
@ -384,8 +385,7 @@ public final class AndroidacyRepoData extends RepoData {
moduleInfo.minMagisk = // Allow 24.1 to mean 24100
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) + (Integer.parseInt(minMagisk.substring(c + 1)) * 100);
}
} catch (
Exception e) {
} catch (Exception e) {
moduleInfo.minMagisk = 0;
}
moduleInfo.needRamdisk = jsonObject.optBoolean("needRamdisk", false);
@ -437,8 +437,7 @@ public final class AndroidacyRepoData extends RepoData {
private String injectToken(String url) {
// Do not inject token for non Androidacy urls
if (!AndroidacyUtil.isAndroidacyLink(url))
return url;
if (!AndroidacyUtil.isAndroidacyLink(url)) return url;
if (this.testMode) {
if (url.startsWith("https://production-api.androidacy.com/")) {
Timber.e("Got non test mode url: %s", AndroidacyUtil.hideToken(url));

@ -51,10 +51,10 @@ public class BackgroundUpdateChecker extends Worker {
if (!MainApplication.getPreferences("mmm").getBoolean("pref_background_update_check", false)) {
return;
}
if (MainApplication.getINSTANCE().isInForeground()) {
//if (MainApplication.getINSTANCE().isInForeground()) {
// don't check if app is in foreground, this is a background check
return;
}
// return;
//}
// next, check if user requires wifi
if (MainApplication.getPreferences("mmm").getBoolean("pref_background_update_check_wifi", true)) {
// check if wifi is connected
@ -68,7 +68,7 @@ public class BackgroundUpdateChecker extends Worker {
// start foreground service
Intent intent = new Intent(context, BackgroundUpdateCheckerService.class);
intent.setAction(BackgroundUpdateCheckerService.ACTION_START_FOREGROUND_SERVICE);
ContextCompat.startForegroundService(context, intent);
context.startService(intent);
}
public static void postNotification(Context context, HashMap<String, String> updateable, int updateCount, boolean test) {

@ -6,16 +6,17 @@ import static com.fox2code.mmm.background.BackgroundUpdateChecker.postNotificati
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.IBinder;
import androidx.core.app.NotificationChannelCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import com.fox2code.foxcompat.app.internal.FoxIntentActivity;
import com.fox2code.mmm.AppUpdateManager;
import com.fox2code.mmm.MainApplication;
import com.fox2code.mmm.R;
@ -31,7 +32,7 @@ import java.util.HashMap;
import timber.log.Timber;
@SuppressLint("RestrictedApi")
public class BackgroundUpdateCheckerService extends FoxIntentActivity {
public class BackgroundUpdateCheckerService extends Service {
public static final String NOTIFICATION_CHANNEL_ID = "background_update";
public static final String NOTIFICATION_CHANNEL_ID_APP = "background_update_app";
public static final String ACTION_START_FOREGROUND_SERVICE = "ACTION_START_FOREGROUND_SERVICE";
@ -68,83 +69,81 @@ public class BackgroundUpdateCheckerService extends FoxIntentActivity {
}
}
public void onCreate() {
@Override
public IBinder onBind(Intent intent) {
Context context = MainApplication.getINSTANCE().getApplicationContext();
// check if action is ACTION_START_FOREGROUND_SERVICE, bail out if not
if (!ACTION_START_FOREGROUND_SERVICE.equals(getIntent().getAction())) {
return;
}
Timber.d("Starting background update checker service");
// acquire lock
synchronized (lock) {
// post checking notification if notofiications are enabled
if (ContextCompat.checkSelfPermission(MainApplication.getINSTANCE(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.createNotificationChannel(new NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL_ID_ONGOING, NotificationManagerCompat.IMPORTANCE_MIN).setName(context.getString(R.string.notification_channel_category_background_update)).setDescription(context.getString(R.string.notification_channel_category_background_update_description)).setGroup(NOTFIICATION_GROUP).build());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setSmallIcon(R.drawable.ic_baseline_update_24);
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setCategory(NotificationCompat.CATEGORY_SERVICE);
builder.setShowWhen(false);
builder.setOnlyAlertOnce(true);
builder.setOngoing(true);
builder.setAutoCancel(false);
builder.setGroup("update");
builder.setContentTitle(context.getString(R.string.notification_channel_background_update));
builder.setContentText(context.getString(R.string.notification_channel_background_update_description));
notificationManager.notify(NOTIFICATION_ID_ONGOING, builder.build());
} else {
Timber.d("Not posting notification because of missing permission");
}
Thread.currentThread().setPriority(2);
ModuleManager.getINSTANCE().scanAsync();
RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().runAfterScan(() -> {
int moduleUpdateCount = 0;
HashMap<String, RepoModule> repoModules = RepoManager.getINSTANCE().getModules();
// hasmap of updateable modules names
HashMap<String, String> updateableModules = new HashMap<>();
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if ("twrp-keep".equals(localModuleInfo.id)) continue;
// exclude all modules with id's stored in the pref pref_background_update_check_excludes
try {
if (MainApplication.getPreferences("mmm").getStringSet("pref_background_update_check_excludes", null).contains(localModuleInfo.id))
continue;
} catch (Exception ignored) {
// check if ACTION_START_FOREGROUND_SERVICE was called
if (intent.getAction() != null && intent.getAction().equals(ACTION_START_FOREGROUND_SERVICE)) {
synchronized (lock) {
// post checking notification if notifications are enabled
if (ContextCompat.checkSelfPermission(MainApplication.getINSTANCE(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.createNotificationChannel(new NotificationChannelCompat.Builder(NOTIFICATION_CHANNEL_ID_ONGOING, NotificationManagerCompat.IMPORTANCE_MIN).setName(context.getString(R.string.notification_channel_category_background_update)).setDescription(context.getString(R.string.notification_channel_category_background_update_description)).setGroup(NOTFIICATION_GROUP).build());
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setSmallIcon(R.drawable.ic_baseline_update_24);
builder.setPriority(NotificationCompat.PRIORITY_MIN);
builder.setCategory(NotificationCompat.CATEGORY_SERVICE);
builder.setShowWhen(false);
builder.setOnlyAlertOnce(true);
builder.setOngoing(true);
builder.setAutoCancel(false);
builder.setGroup("update_bg");
builder.setContentTitle(context.getString(R.string.notification_channel_background_update));
builder.setContentText(context.getString(R.string.notification_channel_background_update_description));
notificationManager.notify(NOTIFICATION_ID_ONGOING, builder.build());
} else {
Timber.d("Not posting notification because of missing permission");
}
ModuleManager.getINSTANCE().scanAsync();
RepoManager.getINSTANCE().update(null);
ModuleManager.getINSTANCE().runAfterScan(() -> {
int moduleUpdateCount = 0;
HashMap<String, RepoModule> repoModules = RepoManager.getINSTANCE().getModules();
// hashmap of updateable modules names
HashMap<String, String> updateableModules = new HashMap<>();
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if ("twrp-keep".equals(localModuleInfo.id)) continue;
// exclude all modules with id's stored in the pref pref_background_update_check_excludes
try {
if (MainApplication.getPreferences("mmm").getStringSet("pref_background_update_check_excludes", null).contains(localModuleInfo.id))
continue;
} catch (Exception ignored) {
}
RepoModule repoModule = repoModules.get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate();
if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode && !PropUtils.isNullString(localModuleInfo.updateVersion)) {
moduleUpdateCount++;
updateableModules.put(localModuleInfo.name, localModuleInfo.version);
} else if (repoModule != null && repoModule.moduleInfo.versionCode > localModuleInfo.versionCode && !PropUtils.isNullString(repoModule.moduleInfo.version)) {
moduleUpdateCount++;
updateableModules.put(localModuleInfo.name, localModuleInfo.version);
}
}
RepoModule repoModule = repoModules.get(localModuleInfo.id);
localModuleInfo.checkModuleUpdate();
if (localModuleInfo.updateVersionCode > localModuleInfo.versionCode && !PropUtils.isNullString(localModuleInfo.updateVersion)) {
moduleUpdateCount++;
updateableModules.put(localModuleInfo.name, localModuleInfo.version);
} else if (repoModule != null && repoModule.moduleInfo.versionCode > localModuleInfo.versionCode && !PropUtils.isNullString(repoModule.moduleInfo.version)) {
moduleUpdateCount++;
updateableModules.put(localModuleInfo.name, localModuleInfo.version);
if (moduleUpdateCount != 0) {
Timber.d("Found %d updates", moduleUpdateCount);
postNotification(context, updateableModules, moduleUpdateCount, false);
}
}
if (moduleUpdateCount != 0) {
Timber.d("Found %d updates", moduleUpdateCount);
postNotification(context, updateableModules, moduleUpdateCount, false);
}
});
// check for app updates
if (MainApplication.getPreferences("mmm").getBoolean("pref_background_update_check_app", false)) {
try {
boolean shouldUpdate = AppUpdateManager.getAppUpdateManager().checkUpdate(true);
if (shouldUpdate) {
Timber.d("Found app update");
postNotificationForAppUpdate(context);
});
// check for app updates
if (MainApplication.getPreferences("mmm").getBoolean("pref_background_update_check_app", false)) {
try {
boolean shouldUpdate = AppUpdateManager.getAppUpdateManager().checkUpdate(true);
if (shouldUpdate) {
Timber.d("Found app update");
postNotificationForAppUpdate(context);
}
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// remove checking notification
if (ContextCompat.checkSelfPermission(MainApplication.getINSTANCE(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
Timber.d("Removing notification");
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(NOTIFICATION_ID_ONGOING);
// remove checking notification
if (ContextCompat.checkSelfPermission(MainApplication.getINSTANCE(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
Timber.d("Removing notification");
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(NOTIFICATION_ID_ONGOING);
}
}
}
return null;
}
}

@ -361,6 +361,42 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity {
return true;
});
// handle restart required for showcase mode
findPreference("pref_showcase_mode").setOnPreferenceChangeListener((p, v) -> {
if (v.equals(true)) {
new MaterialAlertDialogBuilder(requireContext()).setTitle(R.string.restart).setMessage(R.string.showcase_mode_dialogue_message).setPositiveButton(R.string.ok, (dialog, which) -> {
// Toggle showcase mode on
((TwoStatePreference) findPreference("pref_showcase_mode")).setChecked(true);
editor.putBoolean("pref_showcase_mode", true).apply();
// restart app
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent;
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
Timber.d("Restarting app to save showcase mode preference: %s", v);
System.exit(0); // Exit app process
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
// Revert to showcase mode on
((TwoStatePreference) findPreference("pref_showcase_mode")).setChecked(false);
editor.putBoolean("pref_showcase_mode", false).apply();
// restart app
Intent mStartActivity = new Intent(requireContext(), MainActivity.class);
mStartActivity.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent;
mPendingIntent = PendingIntent.getActivity(requireContext(), mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
AlarmManager mgr = (AlarmManager) requireContext().getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
Timber.d("Restarting app to save showcase mode preference: %s", v);
System.exit(0); // Exit app process
}).show();
}
return true;
});
Preference languageSelector = findPreference("pref_language_selector");
languageSelector.setOnPreferenceClickListener(preference -> {
LanguageSwitcher ls = new LanguageSwitcher(getActivity());

@ -399,4 +399,5 @@
<string name="language_not_available">Language %s has not been translated. Help translate it?</string>
<string name="blur_desc">Creates a blur effect behind some dialogs and elements. Note that blur may not perform well on some devices and may not work for everyone.</string>
<string name="error_encrypted_shared_preferences">An error occurred reading shared preferences. Please reset the app.</string>
<string name="showcase_mode_dialogue_message">An app restart is required to enable showcase mode.</string>
</resources>

@ -156,7 +156,9 @@
app:singleLineTitle="false"
app:summary="@string/dns_over_https_desc"
app:title="@string/dns_over_https_pref" />
<!-- Note: Lockdown mode used to be called showcase mode -->
<!-- TO DO: figure out why the f*** we need a showcase mode -->
<!-- like seriously, why? -->
<SwitchPreferenceCompat
app:defaultValue="false"
app:icon="@drawable/ic_baseline_lock_24"

Loading…
Cancel
Save