You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MagiskModuleManager/app/src/main/java/com/fox2code/mmm/MainActivity.java

855 lines
46 KiB
Java

4 years ago
package com.fox2code.mmm;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
4 years ago
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Build;
4 years ago
import android.os.Bundle;
import android.provider.Settings;
import android.text.InputType;
4 years ago
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.WindowManager;
4 years ago
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
4 years ago
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fox2code.foxcompat.FoxActivity;
import com.fox2code.foxcompat.FoxDisplay;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
4 years ago
import com.fox2code.mmm.installer.InstallerInitializer;
4 years ago
import com.fox2code.mmm.manager.LocalModuleInfo;
4 years ago
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.module.ModuleViewAdapter;
import com.fox2code.mmm.module.ModuleViewListBuilder;
4 years ago
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.settings.SettingsActivity;
import com.fox2code.mmm.utils.BlurUtils;
import com.fox2code.mmm.utils.ExternalHelper;
4 years ago
import com.fox2code.mmm.utils.Http;
4 years ago
import com.fox2code.mmm.utils.IntentHelper;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.materialswitch.MaterialSwitch;
4 years ago
import com.google.android.material.progressindicator.LinearProgressIndicator;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.urlconnection.CronetURLStreamHandlerFactory;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Objects;
import eightbitlab.com.blurview.BlurView;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
4 years ago
private static final String TAG = "MainActivity";
private static final int PRECISION = 10000;
public static boolean doSetupNowRunning = true;
4 years ago
public final ModuleViewListBuilder moduleViewListBuilder;
public LinearProgressIndicator progressIndicator;
private ModuleViewAdapter moduleViewAdapter;
private SwipeRefreshLayout swipeRefreshLayout;
private int swipeRefreshLayoutOrigStartOffset;
private int swipeRefreshLayoutOrigEndOffset;
private long swipeRefreshBlocker = 0;
private int overScrollInsetTop;
private int overScrollInsetBottom;
private TextView actionBarPadding;
private BlurView actionBarBlur;
private ColorDrawable actionBarBackground;
4 years ago
private RecyclerView moduleList;
private CardView searchCard;
private SearchView searchView;
private boolean initMode;
private boolean doSetupRestarting = false;
private boolean urlFactoryInstalled = false;
4 years ago
public MainActivity() {
this.moduleViewListBuilder = new ModuleViewListBuilder(this);
this.moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
}
@Override
protected void onResume() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onResume();
}
4 years ago
@Override
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
// Ensure HTTP Cache directories are created
Http.ensureCacheDirs(this);
if (!urlFactoryInstalled) {
try {
ExperimentalCronetEngine cronetEngine = new ExperimentalCronetEngine.Builder(this).build();
CronetURLStreamHandlerFactory cronetURLStreamHandlerFactory = new CronetURLStreamHandlerFactory(cronetEngine);
try {
URL.setURLStreamHandlerFactory(cronetURLStreamHandlerFactory);
} catch (
Error e) {
Log.e(TAG, "Failed to install Cronet URLStreamHandlerFactory");
}
urlFactoryInstalled = true;
} catch (
Throwable t) {
Log.e(TAG, "Failed to install CronetURLStreamHandlerFactory - other");
}
}
if (doSetupRestarting) {
doSetupRestarting = false;
}
BackgroundUpdateChecker.onMainActivityCreate(this);
4 years ago
super.onCreate(savedInstanceState);
if (!MainApplication.getSharedPreferences().getBoolean("first_time_user", true)) {
this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> {
IntentHelper.startActivity(this, SettingsActivity.class);
return true;
}, R.string.pref_category_settings);
}
4 years ago
setContentView(R.layout.activity_main);
this.setTitle(R.string.app_name);
this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
setActionBarBackground(null);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = this.getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode = // Support cutout in Android 9
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
this.getWindow().setAttributes(layoutParams);
}
this.actionBarPadding = findViewById(R.id.action_bar_padding);
this.actionBarBlur = findViewById(R.id.action_bar_blur);
this.actionBarBackground = new ColorDrawable(Color.TRANSPARENT);
4 years ago
this.progressIndicator = findViewById(R.id.progress_bar);
this.swipeRefreshLayout = findViewById(R.id.swipe_refresh);
this.swipeRefreshLayoutOrigStartOffset = this.swipeRefreshLayout.getProgressViewStartOffset();
this.swipeRefreshLayoutOrigEndOffset = this.swipeRefreshLayout.getProgressViewEndOffset();
this.swipeRefreshBlocker = Long.MAX_VALUE;
4 years ago
this.moduleList = findViewById(R.id.module_list);
this.searchCard = findViewById(R.id.search_card);
this.searchView = findViewById(R.id.search_bar);
this.moduleViewAdapter = new ModuleViewAdapter();
this.moduleList.setAdapter(this.moduleViewAdapter);
this.moduleList.setLayoutManager(new LinearLayoutManager(this));
this.moduleList.setItemViewCacheSize(4); // Default is 2
this.swipeRefreshLayout.setOnRefreshListener(this);
this.actionBarBlur.setBackground(this.actionBarBackground);
BlurUtils.setupBlur(this.actionBarBlur, this, R.id.blur_frame);
this.updateBlurState();
checkShowInitialSetup();
4 years ago
this.moduleList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState != RecyclerView.SCROLL_STATE_IDLE)
MainActivity.this.searchView.clearFocus();
}
});
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.searchView.setMinimumHeight(FoxDisplay.dpToPixel(16));
this.searchView.setImeOptions(EditorInfo.IME_ACTION_SEARCH | EditorInfo.IME_FLAG_NO_FULLSCREEN);
4 years ago
this.searchView.setOnQueryTextListener(this);
this.searchView.setOnCloseListener(this);
this.searchView.setOnQueryTextFocusChangeListener((v, h) -> {
if (!h) {
String query = this.searchView.getQuery().toString();
if (query.isEmpty()) {
this.searchView.setIconified(true);
}
}
this.cardIconifyUpdate();
});
this.searchView.setEnabled(false); // Enabled later
this.cardIconifyUpdate();
this.updateScreenInsets(this.getResources().getConfiguration());
4 years ago
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
Log.i(TAG, "Got magisk path: " + path);
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
4 years ago
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
4 years ago
this.commonNext();
}
@Override
public void onFailure(int error) {
Log.i(TAG, "Failed to get magisk path!");
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
4 years ago
this.commonNext();
}
public void commonNext() {
if (BuildConfig.DEBUG) {
Log.i(TAG, "Common next");
moduleViewListBuilder.addNotification(NotificationType.DEBUG);
}
updateScreenInsets(); // Fix an edge case
if (waitInitialSetupFinished()) {
if (BuildConfig.DEBUG) {
Log.i(TAG, "Initial setup not finished, waiting...");
}
return;
}
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
4 years ago
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
if (!Http.hasWebView()) // Check Http for WebView availability
moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW);
4 years ago
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
runOnUiThread(() -> {
progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION);
// Fix insets not being accounted for correctly
updateScreenInsets(getResources().getConfiguration());
4 years ago
});
// On every preferences change, log the change if debug is enabled
if (BuildConfig.DEBUG) {
Log.i("PrefsListener", "onCreate: Preferences: " + MainApplication.getSharedPreferences().getAll());
// Log all preferences changes
MainApplication.getSharedPreferences().registerOnSharedPreferenceChangeListener((prefs, key) -> Log.i("PrefsListener", "onSharedPreferenceChanged: " + key + " = " + prefs.getAll().get(key)));
}
4 years ago
Log.i(TAG, "Scanning for modules!");
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Initialize Update");
4 years ago
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Log.w(TAG, "Need update on create?");
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update Compat");
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
4 years ago
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED);
4 years ago
} else {
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
4 years ago
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Json Update");
4 years ago
if (max != 0) {
4 years ago
int current = 0;
// noodleDebug.push("");
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
4 years ago
if (localModuleInfo.updateJson != null) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", localModuleInfo.id);
4 years ago
try {
localModuleInfo.checkModuleUpdate();
} catch (
Exception e) {
Log.e("MainActivity", "Failed to fetch update of: " + localModuleInfo.id, e);
4 years ago
}
current++;
final int currentTmp = current;
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
4 years ago
}
}
}
}
4 years ago
runOnUiThread(() -> {
4 years ago
progressIndicator.setProgressCompat(PRECISION, true);
4 years ago
progressIndicator.setVisibility(View.GONE);
searchView.setEnabled(true);
setActionBarBackground(null);
updateScreenInsets(getResources().getConfiguration());
4 years ago
});
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
4 years ago
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
Log.i(TAG, "Finished app opening state!");
// noodleDebug.unbind();
4 years ago
}
}, true);
ExternalHelper.INSTANCE.refreshHelper(this);
4 years ago
this.initMode = false;
// Show an material alert dialog if lastEventId is not "" or null in the private sentry shared preferences
//noinspection ConstantConditions
if (MainApplication.isCrashReportingEnabled() && !BuildConfig.SENTRY_TOKEN.isEmpty()) {
SharedPreferences preferences = getSharedPreferences("sentry", MODE_PRIVATE);
String lastExitReason = preferences.getString("lastExitReason", "");
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Last Exit Reason: " + lastExitReason);
if (lastExitReason.equals("crash")) {
String lastEventId = preferences.getString("lastEventId", "");
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Last Event ID: " + lastEventId);
if (!lastEventId.equals("")) {
// Three edit texts for the user to enter their email, name and a description of the issue
EditText email = new EditText(this);
email.setHint(R.string.email);
email.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
EditText name = new EditText(this);
name.setHint(R.string.name);
name.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
EditText description = new EditText(this);
description.setHint(R.string.additional_info);
// Set description to be multiline and auto resize
description.setSingleLine(false);
description.setMaxHeight(1000);
// Make description required-
new MaterialAlertDialogBuilder(this).setCancelable(false).setTitle(R.string.sentry_dialogue_title).setMessage(R.string.sentry_dialogue_message).setView(new LinearLayout(this) {{
setOrientation(LinearLayout.VERTICAL);
setPadding(40, 20, 40, 10);
addView(email);
addView(name);
addView(description);
}}).setPositiveButton(R.string.submit, (dialog, which) -> {
// Make sure the user has entered a description
if (description.getText().toString().equals("")) {
Toast.makeText(this, R.string.sentry_dialogue_no_description, Toast.LENGTH_LONG).show();
dialog.cancel();
}
preferences.edit().remove("lastEventId").apply();
preferences.edit().putString("lastExitReason", "").apply();
// Prevent strict mode violation
new Thread(() -> {
try {
HttpURLConnection connection = (HttpURLConnection) new URL("https" + "://sentry.io/api/0/projects/androidacy-i6/foxmmm/user-feedback/").openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/json");
connection.setRequestProperty("Authorization", "Bearer " + BuildConfig.SENTRY_TOKEN);
// Setups the JSON body
String nameString = name.getText().toString();
String emailString = email.getText().toString();
if (nameString.equals(""))
nameString = "Anonymous";
if (emailString.equals(""))
emailString = "Anonymous";
JSONObject body = new JSONObject();
body.put("event_id", lastEventId);
body.put("name", nameString);
body.put("email", emailString);
body.put("comments", description.getText().toString());
// Send the request
connection.setDoOutput(true);
connection.getOutputStream().write(body.toString().getBytes());
connection.connect();
// For debug builds, log the response code and response body
if (BuildConfig.DEBUG) {
Log.i("NoodleDebug", "Response Code: " + connection.getResponseCode());
}
// Check if the request was successful
if (connection.getResponseCode() == 200) {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_success, Toast.LENGTH_LONG).show());
} else {
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
} catch (
IOException |
JSONException ignored) {
// Show a toast if the user feedback could not be submitted
runOnUiThread(() -> Toast.makeText(this, R.string.sentry_dialogue_failed_toast, Toast.LENGTH_LONG).show());
}
}).start();
}).setNegativeButton(R.string.cancel, (dialog, which) -> {
preferences.edit().remove("lastEventId").apply();
preferences.edit().putString("lastExitReason", "").apply();
Log.w(TAG, "User cancelled sentry dialog");
}).show();
}
}
}
4 years ago
}
private void cardIconifyUpdate() {
boolean iconified = this.searchView.isIconified();
int backgroundAttr = iconified ? MainApplication.isMonetEnabled() ? R.attr.colorSecondaryContainer : // Monet is special...
4 years ago
R.attr.colorSecondary : R.attr.colorPrimarySurface;
Resources.Theme theme = this.searchCard.getContext().getTheme();
TypedValue value = new TypedValue();
theme.resolveAttribute(backgroundAttr, value, true);
this.searchCard.setCardBackgroundColor(value.data);
this.searchCard.setAlpha(iconified ? 0.80F : 1F);
}
private void updateScreenInsets() {
this.runOnUiThread(() -> this.updateScreenInsets(this.getResources().getConfiguration()));
}
private void updateScreenInsets(Configuration configuration) {
boolean landscape = configuration.orientation == Configuration.ORIENTATION_LANDSCAPE;
int bottomInset = (landscape ? 0 : this.getNavigationBarHeight());
int statusBarHeight = getStatusBarHeight();
int actionBarHeight = getActionBarHeight();
int combinedBarsHeight = statusBarHeight + actionBarHeight;
this.actionBarPadding.setMinHeight(combinedBarsHeight);
this.swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayoutOrigStartOffset + combinedBarsHeight, swipeRefreshLayoutOrigEndOffset + combinedBarsHeight);
this.moduleViewListBuilder.setHeaderPx(Math.max(statusBarHeight, combinedBarsHeight - FoxDisplay.dpToPixel(4)));
this.moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4) + bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets();
//this.actionBarBlur.invalidate();
this.overScrollInsetTop = combinedBarsHeight;
this.overScrollInsetBottom = bottomInset;
Log.i(TAG, "( " + bottomInset + ", " + this.searchCard.getHeight() + ")");
4 years ago
}
private void updateBlurState() {
boolean isLightMode = this.isLightTheme();
int colorBackground;
try {
colorBackground = this.getColorCompat(android.R.attr.windowBackground);
} catch (
Resources.NotFoundException e) {
colorBackground = this.getColorCompat(isLightMode ? R.color.white : R.color.black);
}
if (MainApplication.isBlurEnabled()) {
this.actionBarBlur.setBlurEnabled(true);
this.actionBarBackground.setColor(ColorUtils.setAlphaComponent(colorBackground, 0x02));
this.actionBarBackground.setColor(Color.TRANSPARENT);
} else {
this.actionBarBlur.setBlurEnabled(false);
this.actionBarBlur.setOverlayColor(Color.TRANSPARENT);
this.actionBarBackground.setColor(colorBackground);
}
}
4 years ago
@Override
public void refreshUI() {
super.refreshUI();
if (this.initMode)
return;
4 years ago
this.initMode = true;
Log.i(TAG, "Item Before");
this.searchView.setQuery("", false);
this.searchView.clearFocus();
this.searchView.setIconified(true);
this.cardIconifyUpdate();
this.updateScreenInsets();
this.updateBlurState();
4 years ago
this.moduleViewListBuilder.setQuery(null);
Log.i(TAG, "Item After");
4 years ago
this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter);
4 years ago
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
checkShowInitialSetup();
// Wait for doSetupNow to finish
while (doSetupNowRunning) {
try {
//noinspection BusyWait
Thread.sleep(100);
} catch (
InterruptedException ignored) {
}
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
4 years ago
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
ModuleManager.getINSTANCE().scan();
ModuleManager.getINSTANCE().runAfterScan(moduleViewListBuilder::appendInstalledModules);
4 years ago
this.commonNext();
}
@Override
public void onFailure(int error) {
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
4 years ago
this.commonNext();
}
public void commonNext() {
Log.i(TAG, "Common Before");
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove())
4 years ago
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
4 years ago
else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
RepoManager.getINSTANCE().updateEnabledStates();
if (RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
runOnUiThread(() -> {
progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION);
});
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Update");
RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> progressIndicator.setProgressCompat((int) (value * PRECISION), true)));
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE);
});
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
4 years ago
Log.i(TAG, "Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
Log.i(TAG, "Common After");
}
});
this.initMode = false;
}
@Override
protected void onWindowUpdated() {
this.updateScreenInsets();
}
4 years ago
@Override
public void onRefresh() {
if (this.swipeRefreshBlocker > System.currentTimeMillis() || this.initMode || this.progressIndicator == null || this.progressIndicator.getVisibility() == View.VISIBLE || doSetupNowRunning) {
this.swipeRefreshLayout.setRefreshing(false);
4 years ago
return; // Do not double scan
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Refresh");
4 years ago
this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
4 years ago
// this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> {
4 years ago
Http.cleanDnsCache(); // Allow DNS reload from network
// noodleDebug.push("Check Update");
final int max = ModuleManager.getINSTANCE().getUpdatableModuleCount();
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
if (!NotificationType.NO_INTERNET.shouldRemove()) {
4 years ago
moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET);
} else if (!NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED);
} else {
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Check Json Update");
if (max != 0) {
int current = 0;
for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) {
if (localModuleInfo.updateJson != null) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (
Exception e) {
Log.e("MainActivity", "Failed to fetch update of: " + localModuleInfo.id, e);
}
current++;
final int currentTmp = current;
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
}
}
}
}
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Apply");
4 years ago
runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false);
});
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
RepoManager.getINSTANCE().updateEnabledStates();
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendRemoteModules);
4 years ago
this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
/*
noodleDebug.unbind();
*/
}, "Repo update thread").start();
4 years ago
}
@Override
public boolean onQueryTextSubmit(final String query) {
this.searchView.clearFocus();
if (this.initMode)
return false;
4 years ago
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
4 years ago
}
return true;
}
@Override
public boolean onQueryTextChange(String query) {
if (this.initMode)
return false;
4 years ago
if (this.moduleViewListBuilder.setQueryChange(query)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
4 years ago
}
return false;
}
@Override
public boolean onClose() {
if (this.initMode)
return false;
4 years ago
if (this.moduleViewListBuilder.setQueryChange(null)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
4 years ago
}
return false;
}
@Override
public int getOverScrollInsetTop() {
return this.overScrollInsetTop;
}
@Override
public int getOverScrollInsetBottom() {
return this.overScrollInsetBottom;
}
@SuppressLint("RestrictedApi")
private void ensurePermissions() {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Ensure Permissions");
// First, check if user has said don't ask again by checking if pref_dont_ask_again_notification_permission is true
if (!PreferenceManager.getDefaultSharedPreferences(this).getBoolean("pref_dont_ask_again_notification_permission", false)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Request Notification Permission");
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
// Show a dialog explaining why we need this permission, which is to show
// notifications for updates
runOnUiThread(() -> {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Show Notification Permission Dialog");
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.permission_notification_title);
builder.setMessage(R.string.permission_notification_message);
// Don't ask again checkbox
View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null);
CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(R.string.dont_ask_again);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply());
builder.setView(view);
builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> {
// Request the permission
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
doSetupNowRunning = false;
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss();
doSetupNowRunning = false;
});
builder.show();
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Show Notification Permission Dialog Done");
});
} else {
// Request the permission
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Request Notification Permission");
this.requestPermissions(new String[]{Manifest.permission.POST_NOTIFICATIONS}, 0);
if (BuildConfig.DEBUG) {
// Log if granted via onRequestPermissionsResult
Log.i("NoodleDebug", "Request Notification Permission Done. Result: " + (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED));
}
doSetupNowRunning = false;
}
// Next branch is for < android 13 and user has blocked notifications
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && !NotificationManagerCompat.from(this).areNotificationsEnabled()) {
runOnUiThread(() -> {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.permission_notification_title);
builder.setMessage(R.string.permission_notification_message);
// Don't ask again checkbox
View view = getLayoutInflater().inflate(R.layout.dialog_checkbox, null);
CheckBox checkBox = view.findViewById(R.id.checkbox);
checkBox.setText(R.string.dont_ask_again);
checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> PreferenceManager.getDefaultSharedPreferences(this).edit().putBoolean("pref_dont_ask_again_notification_permission", isChecked).apply());
builder.setView(view);
builder.setPositiveButton(R.string.permission_notification_grant, (dialog, which) -> {
// Open notification settings
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
doSetupNowRunning = false;
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
// Set pref_background_update_check to false and dismiss dialog
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putBoolean("pref_background_update_check", false).apply();
dialog.dismiss();
doSetupNowRunning = false;
});
builder.show();
});
} else {
doSetupNowRunning = false;
}
} else {
if (BuildConfig.DEBUG)
Log.i("NoodleDebug", "Notification Permission Already Granted or Don't Ask Again");
doSetupNowRunning = false;
}
}
// Method to show a setup box on first launch
@SuppressLint({"InflateParams", "RestrictedApi", "UnspecifiedImmutableFlag", "ApplySharedPref"})
private void checkShowInitialSetup() {
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "Checking if we need to run setup");
// Check if this is the first launch
SharedPreferences prefs = MainApplication.getSharedPreferences();
boolean firstLaunch = prefs.getBoolean("first_time_user", true);
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "First launch: " + firstLaunch);
if (firstLaunch) {
doSetupNowRunning = true;
// Show setup box. Put the setup_box in the main activity layout
View view = getLayoutInflater().inflate(R.layout.setup_box, null);
// Make the setup_box linear layout the sole child of the root_container constraint layout
setContentView(view);
// Handle action bar. Set it to setup_title and make it visible
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(R.string.app_name);
// Set solid color background
actionBar.show();
}
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setChecked(BuildConfig.ENABLE_AUTO_UPDATER);
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setChecked(BuildConfig.DEFAULT_ENABLE_CRASH_REPORTING);
// Repos are a little harder, as the enabled_repos build config is an arraylist
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("androidacy_repo"));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setChecked(BuildConfig.ENABLED_REPOS.contains("magisk_alt_repo"));
// On debug builds, log when a switch is toggled
if (BuildConfig.DEBUG) {
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Background Update Check: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Crash Reporting: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Androidacy Repo: " + isChecked));
((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).setOnCheckedChangeListener((buttonView, isChecked) -> Log.i("SetupWizard", "Magisk Alt Repo: " + isChecked));
}
// Set up the buttons
// Cancel button
MaterialButton cancelButton = view.findViewById(R.id.setup_cancel);
cancelButton.setText(R.string.cancel);
cancelButton.setOnClickListener(v -> {
// Set first launch to false and finish the activity
prefs.edit().putBoolean("first_time_user", false).commit();
finish();
startActivity(getIntent());
});
// Setup button
MaterialButton setupButton = view.findViewById(R.id.setup_continue);
setupButton.setOnClickListener(v -> {
// Set first launch to false
// get instance of editor
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean("first_time_user", false);
// Set the background update check pref
editor.putBoolean("pref_background_update_check", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_background_update_check))).isChecked());
// Set the crash reporting pref
editor.putBoolean("pref_crash_reporting", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_crash_reporting))).isChecked());
// Set the repos
// first pref_magisk_alt_repo_enabled then pref_androidacy_repo_enabled
editor.putBoolean("pref_magisk_alt_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_magisk_alt_repo))).isChecked());
editor.putBoolean("pref_androidacy_repo_enabled", ((MaterialSwitch) Objects.requireNonNull(view.findViewById(R.id.setup_androidacy_repo))).isChecked());
// Loop through the setup_theme radio group and set the theme pref
RadioGroup themeRadioGroup = view.findViewById(R.id.setup_theme);
int selectedTheme = themeRadioGroup.getCheckedRadioButtonId();
// system, light, dark, black, and transparent_light
if (selectedTheme == R.id.setup_theme_light) {
editor.putString("pref_theme", "light");
} else if (selectedTheme == R.id.setup_theme_dark) {
editor.putString("pref_theme", "dark");
} else if (selectedTheme == R.id.setup_theme_system) {
editor.putString("pref_theme", "system");
} else if (selectedTheme == R.id.setup_theme_black) {
editor.putString("pref_theme", "black");
} else if (selectedTheme == R.id.setup_theme_transparent_light) {
editor.putString("pref_theme", "transparent_light");
}
// Commit the changes
editor.commit();
// Sleep for 1 second to allow the user to see the changes
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Log the changes if debug
if (BuildConfig.DEBUG) {
Log.i("SetupWizard", "Background update check: " + prefs.getBoolean("pref_background_update_check", false));
Log.i("SetupWizard", "Crash reporting: " + prefs.getBoolean("pref_crash_reporting", false));
Log.i("SetupWizard", "Magisk Alt Repo: " + prefs.getBoolean("pref_magisk_alt_repo_enabled", false));
Log.i("SetupWizard", "Androidacy Repo: " + prefs.getBoolean("pref_androidacy_repo_enabled", false));
}
// Restart the activity
doSetupRestarting = true;
finish();
startActivity(getIntent());
});
} else {
ensurePermissions();
}
}
/**
* @return true if the load workflow must be stopped.
*/
private boolean waitInitialSetupFinished() {
if (BuildConfig.DEBUG)
Log.i("SetupWizard", "waitInitialSetupFinished");
if (doSetupNowRunning)
updateScreenInsets(); // Fix an edge case
try {
// Wait for doSetupNow to finish
while (doSetupNowRunning) {
//noinspection BusyWait
Thread.sleep(50);
}
} catch (
InterruptedException e) {
return true;
}
return doSetupRestarting;
}
4 years ago
}