package com.fox2code.mmm; import androidx.annotation.NonNull; import androidx.appcompat.widget.SearchView; import androidx.cardview.widget.CardView; import androidx.core.graphics.ColorUtils; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.os.Bundle; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; import android.widget.TextView; import com.fox2code.mmm.compat.CompatActivity; import com.fox2code.mmm.compat.CompatDisplay; import com.fox2code.mmm.installer.InstallerInitializer; import com.fox2code.mmm.manager.LocalModuleInfo; import com.fox2code.mmm.manager.ModuleManager; import com.fox2code.mmm.repo.RepoManager; import com.fox2code.mmm.settings.SettingsActivity; import com.fox2code.mmm.utils.Http; import com.fox2code.mmm.utils.IntentHelper; import com.google.android.material.progressindicator.LinearProgressIndicator; import eightbitlab.com.blurview.BlurView; import eightbitlab.com.blurview.RenderScriptBlur; public class MainActivity extends CompatActivity implements SwipeRefreshLayout.OnRefreshListener, SearchView.OnQueryTextListener, SearchView.OnCloseListener, OverScrollManager.OverScrollHelper { private static final String TAG = "MainActivity"; private static final int PRECISION = 10000; 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; private RecyclerView moduleList; private CardView searchCard; private SearchView searchView; private boolean initMode; public MainActivity() { this.moduleViewListBuilder = new ModuleViewListBuilder(this); this.moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); } @Override protected void onCreate(Bundle savedInstanceState) { this.initMode = true; super.onCreate(savedInstanceState); this.setActionBarExtraMenuButton(R.drawable.ic_baseline_settings_24, v -> { IntentHelper.startActivity(this, SettingsActivity.class); return true; }, R.string.pref_category_settings); 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); 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.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 OverScrollManager.install(this.moduleList, this); this.swipeRefreshLayout.setOnRefreshListener(this); this.actionBarBlur.setBackground(this.actionBarBackground); this.actionBarBlur.setupWith(this.moduleList).setFrameClearDrawable( this.getWindow().getDecorView().getBackground()) .setBlurAlgorithm(new RenderScriptBlur(this)) .setBlurRadius(4F).setBlurAutoUpdate(true) .setHasFixedTransformationMatrix(true); this.updateBlurState(); 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(CompatDisplay.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()); 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) moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); ModuleManager.getINSTANCE().scan(); moduleViewListBuilder.appendInstalledModules(); this.commonNext(); } @Override public void onFailure(int error) { Log.i(TAG, "Failed to get magisk path!"); moduleViewListBuilder.addNotification(NotificationType.NO_ROOT); this.commonNext(); } public void commonNext() { swipeRefreshBlocker = System.currentTimeMillis() + 5_000L; updateScreenInsets(); // Fix an edge case if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); if (!Http.hasWebView()) // Check Http for WebView availability moduleViewListBuilder.addNotification(NotificationType.NO_WEB_VIEW); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); runOnUiThread(() -> { progressIndicator.setIndeterminate(false); progressIndicator.setMax(PRECISION); // Fix insets not being accounted for correctly updateScreenInsets(getResources().getConfiguration()); }); Log.i(TAG, "Scanning for modules!"); 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))); if (!NotificationType.NO_INTERNET.shouldRemove()) { moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } else { // Compatibility data still needs to be updated AppUpdateManager appUpdateManager = AppUpdateManager.getAppUpdateManager(); if (BuildConfig.ENABLE_AUTO_UPDATER && appUpdateManager.checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); if (!BuildConfig.ENABLE_AUTO_UPDATER || appUpdateManager.isLastCheckSuccess()) AppUpdateManager.getAppUpdateManager().checkUpdateCompat(); if (max != 0) { int current = 0; for (LocalModuleInfo localModuleInfo : ModuleManager.getINSTANCE().getModules().values()) { if (localModuleInfo.updateJson != null) { 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)); } } } } runOnUiThread(() -> { progressIndicator.setProgressCompat(PRECISION, true); progressIndicator.setVisibility(View.GONE); searchView.setEnabled(true); setActionBarBackground(null); updateScreenInsets(getResources().getConfiguration()); }); moduleViewListBuilder.appendRemoteModules(); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); Log.i(TAG, "Finished app opening state!"); } }, true); this.initMode = false; } private void cardIconifyUpdate() { boolean iconified = this.searchView.isIconified(); int backgroundAttr = iconified ? 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 - CompatDisplay.dpToPixel(12))); this.moduleViewListBuilder.setFooterPx( bottomInset + this.searchCard.getHeight()); this.searchCard.setRadius(this.searchCard.getHeight() / 2F); this.moduleViewListBuilder.updateInsets(); this.actionBarBlur.invalidate(); this.overScrollInsetTop = combinedBarsHeight; this.overScrollInsetBottom = bottomInset; Log.d(TAG, "( " + bottomInset + ", " + this.searchCard.getHeight() + ")"); } 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); } } @Override public void refreshUI() { super.refreshUI(); if (this.initMode) return; 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(); this.moduleViewListBuilder.setQuery(null); Log.i(TAG, "Item After"); this.moduleViewListBuilder.refreshNotificationsUI(this.moduleViewAdapter); InstallerInitializer.tryGetMagiskPathAsync(new InstallerInitializer.Callback() { @Override public void onPathReceived(String path) { if (InstallerInitializer.peekMagiskVersion() < Constants.MAGISK_VER_CODE_INSTALL_COMMAND) moduleViewListBuilder.addNotification(NotificationType.MAGISK_OUTDATED); if (!MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.INSTALL_FROM_STORAGE); ModuleManager.getINSTANCE().scan(); moduleViewListBuilder.appendInstalledModules(); this.commonNext(); } @Override public void onFailure(int error) { moduleViewListBuilder.addNotification(NotificationType.NO_ROOT); this.commonNext(); } public void commonNext() { Log.i(TAG, "Common Before"); if (MainApplication.isShowcaseMode()) moduleViewListBuilder.addNotification(NotificationType.SHOWCASE_MODE); if (!NotificationType.NO_INTERNET.shouldRemove()) moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); else if (AppUpdateManager.getAppUpdateManager().checkUpdate(false)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); RepoManager.getINSTANCE().updateEnabledStates(); moduleViewListBuilder.appendRemoteModules(); Log.i(TAG, "Common Before applyTo"); moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); Log.i(TAG, "Common After"); } }); this.initMode = false; } @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { this.updateScreenInsets(newConfig); super.onConfigurationChanged(newConfig); } @Override public void onRefresh() { if (this.swipeRefreshBlocker > System.currentTimeMillis() || this.initMode || this.progressIndicator == null || this.progressIndicator.getVisibility() == View.VISIBLE) { this.swipeRefreshLayout.setRefreshing(false); return; // Do not double scan } 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 RepoManager.getINSTANCE().update(value -> runOnUiThread(() -> this.progressIndicator.setProgressCompat( (int) (value * PRECISION), true))); if (!NotificationType.NO_INTERNET.shouldRemove()) moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); else if (AppUpdateManager.getAppUpdateManager().checkUpdate(true)) moduleViewListBuilder.addNotification(NotificationType.UPDATE_AVAILABLE); runOnUiThread(() -> { this.progressIndicator.setVisibility(View.GONE); this.swipeRefreshLayout.setRefreshing(false); }); if (!NotificationType.NO_INTERNET.shouldRemove()) { this.moduleViewListBuilder.addNotification(NotificationType.NO_INTERNET); } RepoManager.getINSTANCE().updateEnabledStates(); this.moduleViewListBuilder.appendRemoteModules(); this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); },"Repo update thread").start(); } @Override public boolean onQueryTextSubmit(final String query) { this.searchView.clearFocus(); if (this.initMode) return false; if (this.moduleViewListBuilder.setQueryChange(query)) { new Thread(() -> { this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); }, "Query update thread").start(); } return true; } @Override public boolean onQueryTextChange(String query) { if (this.initMode) return false; if (this.moduleViewListBuilder.setQueryChange(query)) { new Thread(() -> { this.moduleViewListBuilder.applyTo(moduleList, moduleViewAdapter); }, "Query update thread").start(); } 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(); } return false; } @Override public int getOverScrollInsetTop() { return this.overScrollInsetTop; } @Override public int getOverScrollInsetBottom() { return this.overScrollInsetBottom; } }