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

770 lines
42 KiB
Java

package com.fox2code.mmm;
import static com.fox2code.mmm.MainApplication.o;
import static com.fox2code.mmm.manager.ModuleInfo.FLAG_MM_REMOTE_MODULE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.view.inputmethod.EditorInfo;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SearchView;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.fox2code.foxcompat.app.FoxActivity;
import com.fox2code.foxcompat.view.FoxDisplay;
import com.fox2code.mmm.androidacy.AndroidacyRepoData;
import com.fox2code.mmm.background.BackgroundUpdateChecker;
import com.fox2code.mmm.installer.InstallerInitializer;
import com.fox2code.mmm.manager.LocalModuleInfo;
import com.fox2code.mmm.manager.ModuleManager;
import com.fox2code.mmm.module.ModuleViewAdapter;
import com.fox2code.mmm.module.ModuleViewListBuilder;
import com.fox2code.mmm.repo.RepoManager;
import com.fox2code.mmm.repo.RepoModule;
import com.fox2code.mmm.settings.SettingsActivity;
import com.fox2code.mmm.utils.ExternalHelper;
import com.fox2code.mmm.utils.RuntimeUtils;
import com.fox2code.mmm.utils.io.net.Http;
import com.fox2code.mmm.utils.realm.ReposList;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import org.matomo.sdk.extra.TrackHelper;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import timber.log.Timber;
public class MainActivity extends FoxActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper {
private static final int PRECISION = 10000;
public static boolean doSetupNowRunning = true;
public static boolean doSetupRestarting = false;
public static List<LocalModuleInfo> localModuleInfoList = new ArrayList<>();
public static List<RepoModule> onlineModuleInfoList = new ArrayList<>();
public final ModuleViewListBuilder moduleViewListBuilder;
public final ModuleViewListBuilder moduleViewListBuilderOnline;
public LinearProgressIndicator progressIndicator;
private ModuleViewAdapter moduleViewAdapter;
private ModuleViewAdapter moduleViewAdapterOnline;
private SwipeRefreshLayout swipeRefreshLayout;
private int swipeRefreshLayoutOrigStartOffset;
private int swipeRefreshLayoutOrigEndOffset;
private long swipeRefreshBlocker = 0;
private int overScrollInsetTop;
private int overScrollInsetBottom;
private RecyclerView moduleList;
private RecyclerView moduleListOnline;
private CardView searchCard;
private SearchView searchView;
private boolean initMode;
private RuntimeUtils runtimeUtils;
public static Boolean isShowingWeblateSb = false; // race condition
public MainActivity() {
this.moduleViewListBuilder = new ModuleViewListBuilder(this);
this.moduleViewListBuilderOnline = new ModuleViewListBuilder(this);
this.moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
}
@Override
protected void onResume() {
BackgroundUpdateChecker.onMainActivityResume(this);
super.onResume();
}
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
this.initMode = true;
if (doSetupRestarting) {
doSetupRestarting = false;
}
BackgroundUpdateChecker.onMainActivityCreate(this);
super.onCreate(savedInstanceState);
TrackHelper.track().screen(this).with(MainApplication.getINSTANCE().getTracker());
// track enabled repos
RealmConfiguration realmConfig = new RealmConfiguration.Builder().name("ReposList.realm").encryptionKey(MainApplication.getINSTANCE().getKey()).directory(MainApplication.getINSTANCE().getDataDirWithPath("realms")).schemaVersion(1).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).build();
Realm realm = Realm.getInstance(realmConfig);
StringBuilder enabledRepos = new StringBuilder();
realm.executeTransaction(r -> {
for (ReposList r2 : r.where(ReposList.class).equalTo("enabled", true).findAll()) {
enabledRepos.append(r2.getUrl()).append(":").append(r2.getName()).append(",");
}
});
if (enabledRepos.length() > 0) {
enabledRepos.setLength(enabledRepos.length() - 1);
}
TrackHelper.track().event("enabled_repos", enabledRepos.toString()).with(MainApplication.getINSTANCE().getTracker());
realm.close();
// hide this behind a buildconfig flag for now, but crash the app if it's not an official build and not debug
if (BuildConfig.ENABLE_PROTECTION && !o && !BuildConfig.DEBUG) {
throw new RuntimeException("This is not an official build of AMM");
} else if (!o && !BuildConfig.DEBUG) {
Timber.w("You may be running an untrusted build.");
// Show a toast to warn the user
Toast.makeText(this, R.string.not_official_build, Toast.LENGTH_LONG).show();
}
Timestamp ts = new Timestamp(System.currentTimeMillis() - (30L * 24 * 60 * 60 * 1000));
// check if this build has expired
Timestamp buildTime = new Timestamp(BuildConfig.BUILD_TIME);
// if the build time is more than 30 days ago, throw an exception
if (ts.getTime() >= buildTime.getTime()) {
throw new IllegalStateException("This build has expired. Please download a stable build or update to the latest version.");
}
setContentView(R.layout.activity_main);
this.setTitle(R.string.app_name);
// set window flags to ignore status bar
this.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
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.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;
this.moduleList = findViewById(R.id.module_list);
this.moduleListOnline = findViewById(R.id.module_list_online);
this.searchCard = findViewById(R.id.search_card);
this.searchView = findViewById(R.id.search_bar);
this.searchView.setIconified(true);
this.moduleViewAdapter = new ModuleViewAdapter();
this.moduleViewAdapterOnline = new ModuleViewAdapter();
this.moduleList.setAdapter(this.moduleViewAdapter);
this.moduleListOnline.setAdapter(this.moduleViewAdapterOnline);
this.moduleList.setLayoutManager(new LinearLayoutManager(this));
this.moduleListOnline.setLayoutManager(new LinearLayoutManager(this));
this.moduleList.setItemViewCacheSize(4); // Default is 2
this.swipeRefreshLayout.setOnRefreshListener(this);
this.runtimeUtils = new RuntimeUtils();
// add background blur if enabled
this.updateBlurState();
//hideActionBar();
runtimeUtils.checkShowInitialSetup(this, this);
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();
// hide search view when scrolling
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
MainActivity.this.searchCard.animate().translationY(-MainActivity.this.searchCard.getHeight()).setInterpolator(new AccelerateInterpolator(2)).start();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// if the user scrolled up, show the search bar
if (dy < 0) {
MainActivity.this.searchCard.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
}
}
});
// same for online
this.moduleListOnline.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
if (newState != RecyclerView.SCROLL_STATE_IDLE)
MainActivity.this.searchView.clearFocus();
// hide search view when scrolling
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
MainActivity.this.searchCard.animate().translationY(-MainActivity.this.searchCard.getHeight()).setInterpolator(new AccelerateInterpolator(2)).start();
}
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// if the user scrolled up, show the search bar
if (dy < 0) {
MainActivity.this.searchCard.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
}
}
});
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);
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());
// on the bottom nav, there's a settings item. open the settings activity when it's clicked.
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnItemSelectedListener(item -> {
if (item.getItemId() == R.id.settings_menu_item) {
TrackHelper.track().event("view_list", "settings").with(MainApplication.getINSTANCE().getTracker());
startActivity(new Intent(MainActivity.this, SettingsActivity.class));
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
finish();
} else if (item.getItemId() == R.id.online_menu_item) {
TrackHelper.track().event("view_list", "online_modules").with(MainApplication.getINSTANCE().getTracker());
// set module_list_online as visible and module_list as gone. fade in/out
this.moduleListOnline.setAlpha(0F);
this.moduleListOnline.setVisibility(View.VISIBLE);
this.moduleListOnline.animate().alpha(1F).setDuration(300).setListener(null);
this.moduleList.animate().alpha(0F).setDuration(300).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
MainActivity.this.moduleList.setVisibility(View.GONE);
}
});
// clear search view
this.searchView.setQuery("", false);
this.searchView.clearFocus();
} else if (item.getItemId() == R.id.installed_menu_item) {
TrackHelper.track().event("view_list", "installed_modules").with(MainApplication.getINSTANCE().getTracker());
// set module_list_online as gone and module_list as visible. fade in/out
this.moduleList.setAlpha(0F);
this.moduleList.setVisibility(View.VISIBLE);
this.moduleList.animate().alpha(1F).setDuration(300).setListener(null);
this.moduleListOnline.animate().alpha(0F).setDuration(300).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
MainActivity.this.moduleListOnline.setVisibility(View.GONE);
}
});
// set search view to cleared
this.searchView.setQuery("", false);
this.searchView.clearFocus();
}
return true;
});
// update the padding of blur_frame to match the new bottom nav height
View blurFrame = findViewById(R.id.blur_frame);
blurFrame.post(() -> blurFrame.setPadding(blurFrame.getPaddingLeft(), blurFrame.getPaddingTop(), blurFrame.getPaddingRight(), bottomNavigationView.getHeight()));
// for some reason, root_container has a margin at the top. remove it.
View rootContainer = findViewById(R.id.root_container);
rootContainer.post(() -> {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) rootContainer.getLayoutParams();
params.topMargin = 0;
rootContainer.setLayoutParams(params);
rootContainer.setY(0F);
});
// reset update module and update module count in main application
MainApplication.getINSTANCE().resetUpdateModule();
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
Timber.i("Got magisk path: %s", path);
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
Objects.requireNonNull(ModuleManager.getInstance()).scan();
ModuleManager.getInstance().runAfterScan(moduleViewListBuilder::appendInstalledModules);
ModuleManager.getInstance().runAfterScan(moduleViewListBuilderOnline::appendRemoteModules);
this.commonNext();
}
@Override
public void onFailure(int error) {
Timber.e("Failed to get magisk path!");
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
moduleViewListBuilderOnline.addNotification(InstallerInitializer.getErrorNotification());
this.commonNext();
}
public void commonNext() {
if (BuildConfig.DEBUG) {
Timber.d("Common next");
moduleViewListBuilder.addNotification(NotificationType.DEBUG);
}
NotificationType.NO_INTERNET.autoAdd(moduleViewListBuilderOnline);
// hide progress bar is repo-manager says we have no internet
if (!RepoManager.getINSTANCE().hasConnectivity()) {
Timber.i("No connection, hiding progress");
runOnUiThread(() -> {
progressIndicator.setVisibility(View.GONE);
progressIndicator.setIndeterminate(false);
progressIndicator.setMax(PRECISION);
});
}
updateScreenInsets(); // Fix an edge case
Context context = MainActivity.this;
if (runtimeUtils.waitInitialSetupFinished(context, MainActivity.this)) {
Timber.d("waiting...");
return;
}
swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
if (!Http.hasWebView()) {
// Check Http for WebView availability
moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW);
// disable online tab
runOnUiThread(() -> {
bottomNavigationView.getMenu().getItem(1).setEnabled(false);
bottomNavigationView.setSelectedItemId(R.id.installed_menu_item);
});
}
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
Timber.i("Scanning for modules!");
if (BuildConfig.DEBUG) Timber.i("Initialize Update");
final int max = Objects.requireNonNull(ModuleManager.getInstance()).getUpdatableModuleCount();
if (RepoManager.getINSTANCE().getCustomRepoManager() != null && RepoManager.getINSTANCE().getCustomRepoManager().needUpdate()) {
Timber.w("Need update on create");
} else if (RepoManager.getINSTANCE().getCustomRepoManager() == null) {
Timber.w("CustomRepoManager is null");
}
// update compat metadata
if (BuildConfig.DEBUG) Timber.i("Check Update Compat");
AppUpdateManager.getAppUpdateManager().checkUpdateCompat();
if (BuildConfig.DEBUG) Timber.i("Check Update");
// update repos
if (Http.hasWebView()) {
RepoManager.getINSTANCE().update(value -> runOnUiThread(max == 0 ? () -> progressIndicator.setProgressCompat((int) (value * PRECISION), true) : () -> progressIndicator.setProgressCompat((int) (value * PRECISION * 0.75F), true)));
}
// various notifications
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilderOnline);
NotificationType.DEBUG.autoAdd(moduleViewListBuilder);
NotificationType.DEBUG.autoAdd(moduleViewListBuilderOnline);
if (Http.hasWebView() && !NotificationType.REPO_UPDATE_FAILED.shouldRemove()) {
moduleViewListBuilder.addNotification(NotificationType.REPO_UPDATE_FAILED);
} else {
if (!Http.hasWebView()) {
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE);
searchView.setEnabled(false);
updateScreenInsets(getResources().getConfiguration());
});
return;
}
// Compatibility data still needs to be updated
AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager();
if (BuildConfig.DEBUG) Timber.i("Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG) Timber.i("Check Json Update");
if (max != 0) {
int current = 0;
for (LocalModuleInfo localModuleInfo : ModuleManager.getInstance().getModules().values()) {
// if it has updateJson and FLAG_MM_REMOTE_MODULE is not set on flags, check for json update
// this is a dirty hack until we better store if it's a remote module
// the reasoning is that remote repos are considered "validated" while local modules are not
// for instance, a potential attacker could hijack a perfectly legitimate module and inject an updateJson with a malicious update - thereby bypassing any checks repos may have, without anyone noticing until it's too late
if (localModuleInfo.updateJson != null && (localModuleInfo.flags & FLAG_MM_REMOTE_MODULE) == 0) {
if (BuildConfig.DEBUG) Timber.i(localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (Exception e) {
Timber.e(e);
}
current++;
final int currentTmp = current;
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
}
}
}
}
if (BuildConfig.DEBUG) Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
moduleViewListBuilder.applyTo(moduleListOnline, moduleViewAdapterOnline);
moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
// if moduleViewListBuilderOnline has the upgradeable notification, show a badge on the online repo nav item
if (MainApplication.getINSTANCE().modulesHaveUpdates) {
Timber.i("Applying badge");
new Handler(Looper.getMainLooper()).post(() -> {
final var badge = bottomNavigationView.getOrCreateBadge(R.id.online_menu_item);
badge.setVisible(true);
badge.setNumber(MainApplication.getINSTANCE().updateModuleCount);
badge.applyTheme(MainApplication.getInitialApplication().getTheme());
Timber.i("Badge applied");
});
}
runOnUiThread(() -> {
progressIndicator.setProgressCompat(PRECISION, true);
progressIndicator.setVisibility(View.GONE);
searchView.setEnabled(true);
updateScreenInsets(getResources().getConfiguration());
});
maybeShowUpgrade();
Timber.i("Finished app opening state!");
}
}, true);
// if system lang is not in MainApplication.supportedLocales, show a snackbar to ask user to help translate
if (!MainApplication.supportedLocales.contains(this.getResources().getConfiguration().getLocales().get(0).getLanguage())) {
// call showWeblateSnackbar() with language code and language name
runtimeUtils.showWeblateSnackbar(this, this, this.getResources().getConfiguration().getLocales().get(0).getLanguage(), this.getResources().getConfiguration().getLocales().get(0).getDisplayLanguage());
}
ExternalHelper.INSTANCE.refreshHelper(this);
this.initMode = false;
// add preference listener to set isMatomoAllowed
SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, key) -> {
if (key.equals("pref_analytics_enabled")) {
MainApplication.getINSTANCE().isMatomoAllowed = sharedPreferences.getBoolean(key, false);
MainApplication.getINSTANCE().getTracker().setOptOut(MainApplication.getINSTANCE().isMatomoAllowed);
Timber.d("Matomo is allowed change: %s", MainApplication.getINSTANCE().isMatomoAllowed);
}
if (MainApplication.getINSTANCE().isMatomoAllowed) {
String value = sharedPreferences.getString(key, null);
// then log
if (value != null) {
TrackHelper.track().event("pref_changed", key + "=" + value).with(MainApplication.getINSTANCE().getTracker());
}
}
Timber.d("Preference changed: %s", key);
};
MainApplication.getSharedPreferences("mmm").registerOnSharedPreferenceChangeListener(listener);
}
private void cardIconifyUpdate() {
boolean iconified = this.searchView.isIconified();
int backgroundAttr = iconified ? MainApplication.isMonetEnabled() ? com.google.android.material.R.attr.colorSecondaryContainer : // Monet is special...
com.google.android.material.R.attr.colorSecondary : com.google.android.material.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);
}
public 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() + FoxDisplay.dpToPixel(2);
this.swipeRefreshLayout.setProgressViewOffset(false, swipeRefreshLayoutOrigStartOffset + statusBarHeight, swipeRefreshLayoutOrigEndOffset + statusBarHeight);
this.moduleViewListBuilder.setHeaderPx(statusBarHeight);
this.moduleViewListBuilderOnline.setHeaderPx(statusBarHeight);
this.moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4) + bottomInset + this.searchCard.getHeight());
this.moduleViewListBuilderOnline.setFooterPx(FoxDisplay.dpToPixel(4) + bottomInset + this.searchCard.getHeight());
this.searchCard.setRadius(this.searchCard.getHeight() / 2F);
this.moduleViewListBuilder.updateInsets();
//this.actionBarBlur.invalidate();
this.overScrollInsetTop = statusBarHeight;
this.overScrollInsetBottom = bottomInset;
// set root_container to have zero padding
findViewById(R.id.root_container).setPadding(0, statusBarHeight, 0, 0);
}
private void updateBlurState() {
if (MainApplication.isBlurEnabled()) {
// set bottom navigation bar color to transparent blur
BottomNavigationView bottomNavigationView = findViewById(R.id.bottom_navigation);
if (bottomNavigationView != null) {
bottomNavigationView.setBackgroundColor(Color.TRANSPARENT);
bottomNavigationView.setAlpha(0.8F);
} else {
Timber.w("Bottom navigation view not found");
}
// set dialogs to have transparent blur
getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
}
}
@Override
public void refreshUI() {
super.refreshUI();
if (this.initMode) return;
this.initMode = true;
Timber.i("Item Before");
this.searchView.setQuery("", false);
this.searchView.clearFocus();
this.searchView.setIconified(true);
this.cardIconifyUpdate();
this.updateScreenInsets();
this.updateBlurState();
this.moduleViewListBuilder.setQuery(null);
Timber.i("Item After");
this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter);
InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() {
@Override
public void onPathReceived(String path) {
Context context = MainActivity.this;
MainActivity mainActivity = MainActivity.this;
runtimeUtils.checkShowInitialSetup(context, mainActivity);
// Wait for doSetupNow to finish
while (doSetupNowRunning) {
try {
//noinspection BusyWait
Thread.sleep(100);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND)
moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED);
if (!MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE);
Objects.requireNonNull(ModuleManager.getInstance()).scan();
ModuleManager.getInstance().runAfterScan(moduleViewListBuilder::appendInstalledModules);
this.commonNext();
}
@Override
public void onFailure(int error) {
Timber.e("Error: %s", error);
moduleViewListBuilder.addNotification(InstallerInitializer.getErrorNotification());
moduleViewListBuilderOnline.addNotification(InstallerInitializer.getErrorNotification());
this.commonNext();
}
public void commonNext() {
Timber.i("Common Before");
if (MainApplication.isShowcaseMode())
moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE);
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilderOnline);
NotificationType.NO_INTERNET.autoAdd(moduleViewListBuilderOnline);
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) Timber.i("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) Timber.i("Apply");
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
Timber.i("Common Before applyTo");
moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
Timber.i("Common After");
}
});
this.initMode = false;
}
@Override
protected void onWindowUpdated() {
this.updateScreenInsets();
}
@Override
public void onRefresh() {
if (this.swipeRefreshBlocker > System.currentTimeMillis() || this.initMode || this.progressIndicator == null || this.progressIndicator.getVisibility() == View.VISIBLE || doSetupNowRunning) {
this.swipeRefreshLayout.setRefreshing(false);
return; // Do not double scan
}
if (BuildConfig.DEBUG) Timber.i("Refresh");
this.progressIndicator.setVisibility(View.VISIBLE);
this.progressIndicator.setProgressCompat(0, false);
this.swipeRefreshBlocker = System.currentTimeMillis() + 5_000L;
// this.swipeRefreshLayout.setRefreshing(true); ??
new Thread(() -> {
Http.cleanDnsCache(); // Allow DNS reload from network
final int max = Objects.requireNonNull(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()) {
moduleViewListBuilderOnline.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) Timber.i("Check App Update");
if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true))
moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE);
if (BuildConfig.DEBUG) Timber.i("Check Json Update");
if (max != 0) {
int current = 0;
for (LocalModuleInfo localModuleInfo : ModuleManager.getInstance().getModules().values()) {
if (localModuleInfo.updateJson != null && (localModuleInfo.flags & FLAG_MM_REMOTE_MODULE) == 0) {
if (BuildConfig.DEBUG) Timber.i(localModuleInfo.id);
try {
localModuleInfo.checkModuleUpdate();
} catch (Exception e) {
Timber.e(e);
}
current++;
final int currentTmp = current;
runOnUiThread(() -> progressIndicator.setProgressCompat((int) ((1F * currentTmp / max) * PRECISION * 0.25F + (PRECISION * 0.75F)), true));
}
}
}
}
if (BuildConfig.DEBUG) Timber.i("Apply");
runOnUiThread(() -> {
this.progressIndicator.setVisibility(View.GONE);
this.swipeRefreshLayout.setRefreshing(false);
});
NotificationType.NEED_CAPTCHA_ANDROIDACY.autoAdd(moduleViewListBuilder);
RepoManager.getINSTANCE().updateEnabledStates();
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilder::appendInstalledModules);
RepoManager.getINSTANCE().runAfterUpdate(moduleViewListBuilderOnline::appendRemoteModules);
this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter);
this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline);
}, "Repo update thread").start();
}
@Override
public boolean onQueryTextSubmit(final String query) {
this.searchView.clearFocus();
if (this.initMode) return false;
TrackHelper.track().search(query).with(MainApplication.getINSTANCE().getTracker());
if (this.moduleViewListBuilder.setQueryChange(query)) {
Timber.i("Query submit: %s on offline list", query);
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
}
// same for online list
if (this.moduleViewListBuilderOnline.setQueryChange(query)) {
Timber.i("Query submit: %s on online list", query);
new Thread(() -> this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline), "Query update thread").start();
}
return true;
}
@Override
public boolean onQueryTextChange(String query) {
if (this.initMode) return false;
TrackHelper.track().search(query).with(MainApplication.getINSTANCE().getTracker());
if (this.moduleViewListBuilder.setQueryChange(query)) {
Timber.i("Query submit: %s on offline list", query);
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
}
// same for online list
if (this.moduleViewListBuilderOnline.setQueryChange(query)) {
Timber.i("Query submit: %s on online list", query);
new Thread(() -> this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline), "Query update thread").start();
}
return false;
}
@Override
public boolean onClose() {
if (this.initMode) return false;
if (this.moduleViewListBuilder.setQueryChange(null)) {
new Thread(() -> this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter), "Query update thread").start();
}
// same for online list
if (this.moduleViewListBuilderOnline.setQueryChange(null)) {
new Thread(() -> this.moduleViewListBuilderOnline.applyTo(moduleListOnline, moduleViewAdapterOnline), "Query update thread").start();
}
return false;
}
@Override
public int getOverScrollInsetTop() {
return this.overScrollInsetTop;
}
@Override
public int getOverScrollInsetBottom() {
return this.overScrollInsetBottom;
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
this.updateScreenInsets();
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
this.updateScreenInsets();
}
public void maybeShowUpgrade() {
if (AndroidacyRepoData.getInstance() == null || AndroidacyRepoData.getInstance().memberLevel == null) {
// wait for up to 10 seconds for AndroidacyRepoData to be initialized
int i = 0;
while (AndroidacyRepoData.getInstance() == null && i < 10) {
try {
//noinspection BusyWait
Thread.sleep(1000);
} catch (InterruptedException e) {
Timber.e(e);
}
i++;
}
if (AndroidacyRepoData.getInstance().isEnabled() && AndroidacyRepoData.getInstance().memberLevel == null) {
Timber.d("Member level is null, waiting for it to be initialized");
i = 0;
while (AndroidacyRepoData.getInstance().memberLevel == null && i < 20) {
i++;
try {
//noinspection BusyWait
Thread.sleep(500);
} catch (InterruptedException e) {
Timber.e(e);
}
}
}
// if it's still null, but it's enabled, throw an error
if (AndroidacyRepoData.getInstance().isEnabled() && AndroidacyRepoData.getInstance().memberLevel == null) {
Timber.e("AndroidacyRepoData is enabled, but member level is null");
}
if (AndroidacyRepoData.getInstance() != null && AndroidacyRepoData.getInstance().isEnabled() && Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
runtimeUtils.showUpgradeSnackbar(this, this);
} else {
if (!AndroidacyRepoData.getInstance().isEnabled()) {
Timber.i("AndroidacyRepoData is disabled, not showing upgrade snackbar 1");
} else if (!Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
Timber.i("AndroidacyRepoData is not Guest, not showing upgrade snackbar 1. Level: %s", AndroidacyRepoData.getInstance().memberLevel);
} else {
Timber.i("Unknown error, not showing upgrade snackbar 1");
}
}
} else if (AndroidacyRepoData.getInstance().isEnabled() && Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
runtimeUtils.showUpgradeSnackbar(this, this);
} else {
if (!AndroidacyRepoData.getInstance().isEnabled()) {
Timber.i("AndroidacyRepoData is disabled, not showing upgrade snackbar 2");
} else if (!Objects.equals(AndroidacyRepoData.getInstance().memberLevel, "Guest")) {
Timber.i("AndroidacyRepoData is not Guest, not showing upgrade snackbar 2. Level: %s", AndroidacyRepoData.getInstance().memberLevel);
} else {
Timber.i("Unknown error, not showing upgrade snackbar 2");
}
}
}
}