diff --git a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt index a14bc9f..2dfffd7 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/MainActivity.kt @@ -8,6 +8,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.SuppressLint import android.content.Context +import android.content.DialogInterface import android.content.Intent import android.content.res.Configuration import android.graphics.Color @@ -57,6 +58,8 @@ import com.fox2code.mmm.utils.io.net.Http.Companion.cleanDnsCache import com.fox2code.mmm.utils.io.net.Http.Companion.hasWebView import com.fox2code.mmm.utils.room.ReposListDatabase import com.google.android.material.bottomnavigation.BottomNavigationView +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.progressindicator.LinearProgressIndicator import org.matomo.sdk.extra.TrackHelper import timber.log.Timber @@ -81,6 +84,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis private var moduleListOnline: RecyclerView? = null private var searchCard: CardView? = null private var searchView: SearchView? = null + private var rebootFab: FloatingActionButton? = null private var initMode = false private var runtimeUtils: RuntimeUtils? = null @@ -113,9 +117,7 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis // track enabled repos Thread { val db = Room.databaseBuilder( - applicationContext, - ReposListDatabase::class.java, - "ReposList.db" + applicationContext, ReposListDatabase::class.java, "ReposList.db" ).build() val repoDao = db.reposListDao() val repos = repoDao.getAll() @@ -175,6 +177,11 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis searchView = findViewById(R.id.search_bar) val searchView = searchView!! searchView.isIconified = true + // when the search view is collapsed or user hits x, hide the search view + searchView.setOnCloseListener { + searchView.visibility = View.GONE + false + } moduleViewAdapter = ModuleViewAdapter() moduleViewAdapterOnline = ModuleViewAdapter() val moduleList = moduleList!! @@ -190,14 +197,47 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis updateBlurState() //hideActionBar(); runtimeUtils!!.checkShowInitialSetup(this, this) + rebootFab = findViewById(R.id.reboot_fab) + val rebootFab = rebootFab!! + + // set on click listener for reboot fab + rebootFab.setOnClickListener { + // show reboot dialog with options to reboot, reboot to recovery, bootloader, or edl, and use RuntimeUtils to reboot + val rebootDialog = MaterialAlertDialogBuilder(this@MainActivity) + .setTitle(R.string.reboot) + .setItems( + arrayOf( + getString(R.string.reboot), + getString(R.string.reboot_recovery), + getString(R.string.reboot_bootloader), + getString(R.string.reboot_edl) + ) + ) { _: DialogInterface?, which: Int -> + when (which) { + 0 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.REBOOT) + 1 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.RECOVERY) + 2 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.BOOTLOADER) + 3 -> RuntimeUtils.reboot(this@MainActivity, RuntimeUtils.RebootMode.EDL) + } + } + .setNegativeButton(R.string.cancel, null) + .create() + rebootDialog.show() + } val searchCard = searchCard!! + // copy reboot fab style to search card + searchCard.elevation = rebootFab.elevation + searchCard.translationY = rebootFab.translationY + searchCard.foreground = rebootFab.foreground moduleList.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { if (newState != RecyclerView.SCROLL_STATE_IDLE) searchView.clearFocus() - // hide search view when scrolling + // hide search view and reboot fab when scrolling if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { searchCard.animate().translationY(-searchCard.height.toFloat()) .setInterpolator(AccelerateInterpolator(2f)).start() + rebootFab.animate().translationY(rebootFab.height.toFloat()) + .setInterpolator(AccelerateInterpolator(2f)).start() } } @@ -207,6 +247,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis if (dy < 0) { searchCard.animate().translationY(0f) .setInterpolator(DecelerateInterpolator(2f)).start() + rebootFab.animate().translationY(0f).setInterpolator(DecelerateInterpolator(2f)) + .start() } } }) @@ -218,6 +260,8 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis if (newState == RecyclerView.SCROLL_STATE_DRAGGING) { searchCard.animate().translationY(-searchCard.height.toFloat()) .setInterpolator(AccelerateInterpolator(2f)).start() + rebootFab.animate().translationY(rebootFab.height.toFloat()) + .setInterpolator(AccelerateInterpolator(2f)).start() } } @@ -227,10 +271,10 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis if (dy < 0) { searchCard.animate().translationY(0f) .setInterpolator(DecelerateInterpolator(2f)).start() + rebootFab.animate().translationY(0f) } } }) - searchCard.radius = searchCard.height / 2f searchView.minimumHeight = FoxDisplay.dpToPixel(16f) searchView.imeOptions = EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN searchView.setOnQueryTextListener(this) @@ -390,19 +434,20 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis if (BuildConfig.DEBUG) Timber.i("Check Update") // update repos if (hasWebView()) { - val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener { - override fun update(value: Double) { - runOnUiThread(if (max == 0) Runnable { - progressIndicator.setProgressCompat( - (value * PRECISION).toInt(), true - ) - } else Runnable { - progressIndicator.setProgressCompat( - (value * PRECISION * 0.75f).toInt(), true - ) - }) + val updateListener: SyncManager.UpdateListener = + object : SyncManager.UpdateListener { + override fun update(value: Double) { + runOnUiThread(if (max == 0) Runnable { + progressIndicator.setProgressCompat( + (value * PRECISION).toInt(), true + ) + } else Runnable { + progressIndicator.setProgressCompat( + (value * PRECISION * 0.75f).toInt(), true + ) + }) + } } - } RepoManager.getINSTANCE()!!.update(updateListener) } // various notifications @@ -525,7 +570,6 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis moduleViewListBuilderOnline.setHeaderPx(statusBarHeight) moduleViewListBuilder.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height) moduleViewListBuilderOnline.setFooterPx(FoxDisplay.dpToPixel(4f) + bottomInset + searchCard!!.height) - searchCard!!.radius = searchCard!!.height / 2f moduleViewListBuilder.updateInsets() //this.actionBarBlur.invalidate(); overScrollInsetTop = statusBarHeight @@ -611,15 +655,16 @@ class MainActivity : FoxActivity(), OnRefreshListener, SearchView.OnQueryTextLis progressIndicator!!.max = PRECISION } if (BuildConfig.DEBUG) Timber.i("Check Update") - val updateListener: SyncManager.UpdateListener = object : SyncManager.UpdateListener { - override fun update(value: Double) { - runOnUiThread { - progressIndicator!!.setProgressCompat( - (value * PRECISION).toInt(), true - ) + val updateListener: SyncManager.UpdateListener = + object : SyncManager.UpdateListener { + override fun update(value: Double) { + runOnUiThread { + progressIndicator!!.setProgressCompat( + (value * PRECISION).toInt(), true + ) + } } } - } RepoManager.getINSTANCE()!!.update(updateListener) runOnUiThread { progressIndicator!!.setProgressCompat(PRECISION, true) diff --git a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyRepoData.kt b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyRepoData.kt index 389cce3..0fcd796 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyRepoData.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/androidacy/AndroidacyRepoData.kt @@ -98,8 +98,18 @@ class AndroidacyRepoData(cacheRoot: File?, testMode: Boolean) : RepoData( editor.remove("pref_androidacy_api_token") editor.apply() return false + } else { + val handler = Handler(Looper.getMainLooper()) + handler.post { + Toast.makeText( + INSTANCE, + INSTANCE!!.getString(R.string.androidacy_api_error, e.errorCode), + Toast.LENGTH_LONG + ).show() + } } - throw e + Timber.w(e) + false } catch (e: JSONException) { // response is not JSON Timber.w("Invalid token, resetting...") diff --git a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt index 5150c96..79b4ee1 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/installer/InstallerActivity.kt @@ -38,6 +38,7 @@ import com.fox2code.mmm.androidacy.AndroidacyUtil import com.fox2code.mmm.module.ActionButtonType import com.fox2code.mmm.utils.FastException import com.fox2code.mmm.utils.IntentHelper +import com.fox2code.mmm.utils.RuntimeUtils import com.fox2code.mmm.utils.io.Files.Companion.copy import com.fox2code.mmm.utils.io.Files.Companion.fixJavaZipHax import com.fox2code.mmm.utils.io.Files.Companion.fixSourceArchiveShit @@ -636,24 +637,25 @@ class InstallerActivity : FoxActivity() { } setDisplayHomeAsUpEnabled(true) progressIndicator!!.visibility = View.GONE - - // This should be improved ? - val rbtCmd = - "/system/bin/svc power reboot || /system/bin/reboot || setprop sys.powerctl reboot" rebootFloatingButton!!.setOnClickListener { _: View? -> - if (warnReboot || MainApplication.shouldPreventReboot()) { + if (MainApplication.shouldPreventReboot()) { + // toast and do nothing + Toast.makeText( + this, + R.string.install_terminal_reboot_prevented, + Toast.LENGTH_SHORT + ).show() + } else { val builder = MaterialAlertDialogBuilder(this) builder.setTitle(R.string.install_terminal_reboot_now) .setMessage(R.string.install_terminal_reboot_now_message) .setCancelable(false).setIcon( R.drawable.ic_reboot_24 ).setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> - Shell.cmd(rbtCmd).submit() + RuntimeUtils.reboot(this, RuntimeUtils.RebootMode.REBOOT) } .setNegativeButton(R.string.no) { x: DialogInterface, _: Int -> x.dismiss() } .show() - } else { - Shell.cmd(rbtCmd).submit() } } rebootFloatingButton!!.isEnabled = true diff --git a/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt b/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt index aa466f3..80268fe 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/manager/ModuleManager.kt @@ -27,7 +27,7 @@ import java.io.InputStreamReader import java.nio.charset.StandardCharsets class ModuleManager private constructor() : SyncManager() { - private val moduleInfos: HashMap = HashMap() + private var moduleInfos: HashMap = HashMap() private val bootPrefs: SharedPreferences = MainApplication.bootSharedPreferences!! private var updatableModuleCount = 0 @@ -200,12 +200,15 @@ class ModuleManager private constructor() : SyncManager() { } } - val modules: HashMap + var modules: HashMap = HashMap() get() { afterScan() return moduleInfos } - + set(value) { + moduleInfos = value + field = value + } @Suppress("unused") fun getUpdatableModuleCount(): Int { afterScan() diff --git a/app/src/main/kotlin/com/fox2code/mmm/module/ModuleHolder.kt b/app/src/main/kotlin/com/fox2code/mmm/module/ModuleHolder.kt index 477e0c0..b4e95b5 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/module/ModuleHolder.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/module/ModuleHolder.kt @@ -43,7 +43,7 @@ class ModuleHolder : Comparable { constructor(notificationType: NotificationType) { moduleId = "" - this.notificationType = Objects.requireNonNull(notificationType) + this.notificationType = notificationType separator = null footerPx = -1 } @@ -106,17 +106,16 @@ class ModuleHolder : Comparable { val type: Type get() = if (footerPx != -1) { - Timber.i("Module %s is footer", moduleId) Type.FOOTER } else if (separator != null) { - Timber.i("Module %s is separator", moduleId) Type.SEPARATOR } else if (notificationType != null) { - Timber.i("Module %s is notification", moduleId) Type.NOTIFICATION } else if (moduleInfo == null) { + Timber.i("Module %s is null and probably is a remote module", moduleId) Type.INSTALLABLE } else if (moduleInfo!!.versionCode < moduleInfo!!.updateVersionCode || repoModule != null && moduleInfo!!.versionCode < repoModule!!.moduleInfo.versionCode) { + Timber.i("Module %s is updateable", moduleId) var ignoreUpdate = false try { if (getSharedPreferences("mmm")?.getStringSet("pref_background_update_check_excludes", HashSet())!! @@ -195,6 +194,7 @@ class ModuleHolder : Comparable { Type.UPDATABLE } } else { + Timber.i("Module %s is installed", moduleId) Type.INSTALLED } diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt index 0f8b06c..c848851 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/utils/RuntimeUtils.kt @@ -28,10 +28,15 @@ import com.fox2code.mmm.R import com.fox2code.mmm.SetupActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar +import com.topjohnwu.superuser.Shell import timber.log.Timber @Suppress("UNUSED_PARAMETER") class RuntimeUtils { + enum class RebootMode { + REBOOT, RECOVERY, BOOTLOADER, EDL + } + @SuppressLint("RestrictedApi") private fun ensurePermissions(context: Context, activity: MainActivity) { if (BuildConfig.DEBUG) Timber.i("Ensure Permissions") @@ -269,4 +274,46 @@ class RuntimeUtils { prefs.edit().putLong("ugsns4", System.currentTimeMillis()).apply() Timber.i("showUpgradeSnackbar done") } + + companion object { + fun reboot(mainActivity: FoxActivity, reboot: RebootMode) { + // reboot based on the reboot cmd from the enum we were passed + when (reboot) { + RebootMode.REBOOT -> { + showRebootDialog(mainActivity) { + Shell.cmd("/system/bin/svc power reboot || /system/bin/reboot").submit() + } + } + RebootMode.RECOVERY -> { + // KEYCODE_POWER = 26, hide incorrect "Factory data reset" message + showRebootDialog(mainActivity) { + Shell.cmd("/system/bin/input keyevent 26").submit() + } + } + RebootMode.BOOTLOADER -> { + showRebootDialog(mainActivity) { + Shell.cmd("/system/bin/svc power reboot bootloader || /system/bin/reboot bootloader") + .submit() + } + } + RebootMode.EDL -> { + showRebootDialog(mainActivity) { + Shell.cmd("/system/bin/reboot edl").submit() + } + } + } + } + + private fun showRebootDialog(mainActivity: FoxActivity, function: () -> Unit) { + val dialog = MaterialAlertDialogBuilder(mainActivity) + .setTitle(R.string.reboot) + .setMessage(R.string.install_terminal_reboot_now_message) + .setPositiveButton(R.string.reboot) { _, _ -> + function() + } + .setNegativeButton(R.string.cancel) { _, _ -> } + .create() + dialog.show() + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt b/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt index d7eff16..402b5c5 100644 --- a/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt +++ b/app/src/main/kotlin/com/fox2code/mmm/utils/io/net/Http.kt @@ -10,6 +10,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.SharedPreferences import android.net.ConnectivityManager +import android.net.Network import android.net.NetworkCapabilities import android.net.Uri import android.os.Build @@ -181,6 +182,9 @@ enum class Http {; } companion object { + private var connectivityListener: ConnectivityManager.NetworkCallback? = null + private var lastConnectivityResult: Boolean = false + private var lastConnectivityCheck: Long = 0 private var limitedRetries: Int = 0 private var httpClient: OkHttpClient? = null private var httpClientDoH: OkHttpClient? = null @@ -719,15 +723,52 @@ enum class Http {; @JvmStatic fun hasConnectivity(context: Context): Boolean { + // cache result for 10 seconds so we don't spam the system + if (System.currentTimeMillis() - lastConnectivityCheck < 10000) { + return lastConnectivityResult + } // Check if we have internet connection using connectivity manager val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager // are we connected to a network with internet capabilities? val networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork) - return networkCapabilities != null && networkCapabilities.hasCapability( + val systemSaysYes = networkCapabilities != null && networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) + Timber.d("System says we have internet: $systemSaysYes") + // if we don't already have a listener, add one, so we can invalidate the cache when the network changes + if (connectivityListener == null) { + connectivityListener = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + Timber.d("Network became available") + lastConnectivityCheck = 0 + } + + override fun onLost(network: Network) { + super.onLost(network) + Timber.d("Network became unavailable") + lastConnectivityCheck = 0 + } + } + connectivityManager.registerDefaultNetworkCallback(connectivityListener!!) + } + if (!systemSaysYes) return false + // check ourselves + val hasInternet = try { + val resp = doHttpGet("https://production-api.androidacy.com/ping", false) + val respString = String(resp) + Timber.d("Ping response: $respString") + true + } catch (e: HttpException) { + Timber.e(e, "Failed to check internet connection") + false + } + Timber.d("We say we have internet: $hasInternet") + lastConnectivityCheck = System.currentTimeMillis() + lastConnectivityResult = systemSaysYes && hasInternet + return lastConnectivityResult } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_restart_alt_24.xml b/app/src/main/res/drawable/baseline_restart_alt_24.xml new file mode 100644 index 0000000..b915471 --- /dev/null +++ b/app/src/main/res/drawable/baseline_restart_alt_24.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/app/src/main/res/drawable/baseline_search_24.xml b/app/src/main/res/drawable/baseline_search_24.xml new file mode 100644 index 0000000..502968e --- /dev/null +++ b/app/src/main/res/drawable/baseline_search_24.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/search_bar_background.xml b/app/src/main/res/drawable/search_bar_background.xml new file mode 100644 index 0000000..34c25c8 --- /dev/null +++ b/app/src/main/res/drawable/search_bar_background.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0cef242..78df2b5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -64,34 +64,51 @@ app:layout_constraintBottom_toTopOf="@id/bottom_navigation" app:layout_constraintEnd_toEndOf="parent" tools:ignore="RtlHardcoded"> - + + + + android:background="@null" + app:useDrawerArrowDrawable="true" + android:alpha="1" + tools:ignore="DuplicateClickableBoundsCheck" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 92214b7..32c8bfd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -367,4 +367,12 @@ Could not create repos db Failed to create module cache db Device is not compatible with blur + Reboot + Search + Reboot normally + Reboot to recovery + Reboot to bootloader + Reboot to EDL mode + Reboot is disabled in app settings + Error while communicating with API: %d