From d763b4b85b5d9bddc2e06058c8f26912585e6d8b Mon Sep 17 00:00:00 2001 From: androidacy-user Date: Tue, 31 Jan 2023 12:19:30 -0500 Subject: [PATCH] work more on in-app updates Signed-off-by: androidacy-user --- app/src/main/AndroidManifest.xml | 3 + .../com/fox2code/mmm/AppUpdateManager.java | 28 +- .../java/com/fox2code/mmm/UpdateActivity.java | 272 ++++++++++++++++++ .../mmm/settings/SettingsActivity.java | 8 +- .../drawable/baseline_system_update_24.xml | 5 + app/src/main/res/layout/activity_update.xml | 62 ++-- app/src/main/res/values/strings.xml | 12 +- app/src/main/res/xml/root_preferences.xml | 2 +- 8 files changed, 337 insertions(+), 55 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_system_update_24.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bf551f5..cc96551 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,10 +40,13 @@ tools:targetApi="tiramisu"> compatDataId = new HashMap<>(); private final Object updateLock = new Object(); private final File compatFile; private String latestRelease; - private String latestPreRelease; private long lastChecked; - private boolean preReleaseNewer; private AppUpdateManager() { this.compatFile = new File(MainApplication.getINSTANCE().getFilesDir(), "compat.txt"); this.latestRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_release", BuildConfig.VERSION_NAME); - this.latestPreRelease = MainApplication.getBootSharedPreferences().getString("updater_latest_pre_release", BuildConfig.VERSION_NAME); this.lastChecked = 0; - this.preReleaseNewer = true; if (this.compatFile.isFile()) { try { this.parseCompatibilityFlags(new FileInputStream(this.compatFile)); @@ -110,13 +106,6 @@ public class AppUpdateManager { } if (latestRelease != null) this.latestRelease = latestRelease; - if (latestPreRelease != null) { - this.latestPreRelease = latestPreRelease; - this.preReleaseNewer = preReleaseNewer; - } else if (!preReleaseNewer) { - this.latestPreRelease = ""; - this.preReleaseNewer = false; - } if (BuildConfig.DEBUG) Timber.d("Latest release: %s", latestRelease); if (BuildConfig.DEBUG) @@ -145,7 +134,7 @@ public class AppUpdateManager { } public boolean peekShouldUpdate() { - if (!BuildConfig.ENABLE_AUTO_UPDATER) + if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) return false; // Convert both BuildConfig.VERSION_NAME and latestRelease to int int currentVersion = 0, latestVersion = 0; @@ -155,20 +144,13 @@ public class AppUpdateManager { } catch ( NumberFormatException ignored) { } - int latestPreReleaseVersion = 0; - // replace all non-numeric characters with empty string - try { - latestPreReleaseVersion = Integer.parseInt(this.latestPreRelease.replaceAll("\\D", "")); - } catch ( - NumberFormatException ignored) { - } - return currentVersion < latestVersion || (this.preReleaseNewer && currentVersion < latestPreReleaseVersion); + return currentVersion < latestVersion; } public boolean peekHasUpdate() { - if (!BuildConfig.ENABLE_AUTO_UPDATER) + if (!BuildConfig.ENABLE_AUTO_UPDATER || BuildConfig.DEBUG) return false; - return !BuildConfig.VERSION_NAME.equals(this.preReleaseNewer ? this.latestPreRelease : this.latestRelease); + return this.peekShouldUpdate(); } private void parseCompatibilityFlags(InputStream inputStream) throws IOException { diff --git a/app/src/main/java/com/fox2code/mmm/UpdateActivity.java b/app/src/main/java/com/fox2code/mmm/UpdateActivity.java index c9afee7..1c830c0 100644 --- a/app/src/main/java/com/fox2code/mmm/UpdateActivity.java +++ b/app/src/main/java/com/fox2code/mmm/UpdateActivity.java @@ -1,14 +1,286 @@ package com.fox2code.mmm; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; +import com.fox2code.mmm.utils.io.Http; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.progressindicator.LinearProgressIndicator; +import com.google.android.material.textview.MaterialTextView; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Objects; + +@SuppressWarnings("UnnecessaryReturnStatement") public class UpdateActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_update); + // Get the progress bar and make it indeterminate for now + LinearProgressIndicator progressIndicator = findViewById(R.id.update_progress); + progressIndicator.setIndeterminate(true); + // get status text view + MaterialTextView statusTextView = findViewById(R.id.update_progress_text); + // set status text to please wait + statusTextView.setText(R.string.please_wait); + // for debug builds, set update_debug_warning to visible and return + if (BuildConfig.DEBUG) { + findViewById(R.id.update_debug_warning).setVisibility(MaterialTextView.VISIBLE); + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + statusTextView.setVisibility(MaterialTextView.INVISIBLE); + return; + } + // Now, parse the intent + Bundle extras = getIntent().getExtras(); + // if extras is null, then we are in a bad state or user launched the activity manually + if (extras == null) { + // set status text to error + statusTextView.setText(R.string.error_no_extras); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // return + return; + } + + // get action + ACTIONS action = ACTIONS.valueOf(extras.getString("action")); + // if action is null, then we are in a bad state or user launched the activity manually + if (Objects.isNull(action)) { + // set status text to error + statusTextView.setText(R.string.error_no_action); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // return + return; + } + + // For check action, we need to check if there is an update using the AppUpdateManager.peekShouldUpdate() + if (action == ACTIONS.CHECK) { + checkForUpdate(); + } else if (action == ACTIONS.DOWNLOAD) { + try { + downloadUpdate(); + } catch (JSONException e) { + e.printStackTrace(); + // set status text to error + statusTextView.setText(R.string.error_download_update); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + } + } else if (action == ACTIONS.INSTALL) { + // ensure path was passed and points to a file within our cache directory + String path = extras.getString("path"); + if (path == null) { + // set status text to error + statusTextView.setText(R.string.no_file_found); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // return + return; + } + File file = new File(path); + if (!file.exists()) { + // set status text to error + statusTextView.setText(R.string.no_file_found); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // return + return; + } + if (!Objects.equals(file.getParentFile(), getCacheDir())) { + // set status text to error + statusTextView.setText(R.string.no_file_found); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // return + return; + } + // set status text to installing + statusTextView.setText(R.string.installing_update); + // set progress bar to indeterminate + progressIndicator.setIndeterminate(true); + // install update + installUpdate(file); + } + } + + public void checkForUpdate() { + LinearProgressIndicator progressIndicator = findViewById(R.id.update_progress); + progressIndicator.setIndeterminate(true); + // get status text view + MaterialTextView statusTextView = findViewById(R.id.update_progress_text); + // set status text to checking for update + statusTextView.setText(R.string.checking_for_update); + // set progress bar to indeterminate + progressIndicator.setIndeterminate(true); + // check for update + boolean shouldUpdate = AppUpdateManager.getAppUpdateManager().peekShouldUpdate(); + // if shouldUpdate is true, then we have an update + if (shouldUpdate) { + // set status text to update available + statusTextView.setText(R.string.update_available); + // set button text to download + MaterialButton button = findViewById(R.id.update_button); + button.setText(R.string.download_update); + // return + } else { + // set status text to no update available + statusTextView.setText(R.string.no_update_available); + // set progress bar to error + // return + } + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + return; + } + + public void downloadUpdate() throws JSONException { + LinearProgressIndicator progressIndicator = findViewById(R.id.update_progress); + progressIndicator.setIndeterminate(true); + // get status text view + MaterialTextView statusTextView = findViewById(R.id.update_progress_text); + byte[] lastestJSON = new byte[0]; + try { + lastestJSON = Http.doHttpGet(AppUpdateManager.RELEASES_API_URL, false); + } catch ( + Exception e) { + e.printStackTrace(); + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + statusTextView.setText(R.string.error_download_update); + } + // convert to JSON + JSONObject latestJSON = new JSONObject(new String(lastestJSON)); + // we already know that there is an update, so we can get the latest version of our architecture. We're going to have to iterate through the assets to find the one we want + JSONArray assets = latestJSON.getJSONArray("assets"); + // get the asset we want + JSONObject asset = null; + for (int i = 0; i < assets.length(); i++) { + JSONObject asset1 = assets.getJSONObject(i); + if (asset1.getString("name").contains(Build.SUPPORTED_ABIS[0])) { + asset = asset1; + break; + } + } + // if asset is null, then we are in a bad state + if (Objects.isNull(asset)) { + // set status text to error + statusTextView.setText(R.string.error_no_asset); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + // return + return; + } + // get the download url + String downloadUrl = Objects.requireNonNull(asset).getString("browser_download_url"); + // get the download size + long downloadSize = asset.getLong("size"); + // set status text to downloading update + statusTextView.setText(getString(R.string.downloading_update, 0)); + // set progress bar to 0 + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(0, false); + // download the update + byte[] update = new byte[0]; + try { + update = Http.doHttpGet(downloadUrl, (downloaded, total, done) -> { + // update progress bar + progressIndicator.setProgressCompat((int) (((float) downloaded / (float) total) * 100), true); + // update status text + statusTextView.setText(getString(R.string.downloading_update, (int) (((float) downloaded / (float) total) * 100))); + }); + } catch ( + Exception e) { + e.printStackTrace(); + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + statusTextView.setText(R.string.error_download_update); + } + // if update is null, then we are in a bad state + if (Objects.isNull(update)) { + // set status text to error + statusTextView.setText(R.string.error_download_update); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + // return + return; + } + // if update is not the same size as the download size, then we are in a bad state + if (update.length != downloadSize) { + // set status text to error + statusTextView.setText(R.string.error_download_update); + // set progress bar to error + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + // return + return; + } + // set status text to installing update + statusTextView.setText(R.string.installing_update); + // set progress bar to 100 + progressIndicator.setIndeterminate(true); + progressIndicator.setProgressCompat(100, false); + // save the update to the cache + File updateFile = null; + try { + updateFile = new File(getCacheDir(), "update.apk"); + FileOutputStream fileOutputStream = new FileOutputStream(updateFile); + fileOutputStream.write(update); + fileOutputStream.close(); + } catch ( + IOException e) { + e.printStackTrace(); + progressIndicator.setIndeterminate(false); + progressIndicator.setProgressCompat(100, false); + statusTextView.setText(R.string.error_download_update); + } + // install the update + installUpdate(updateFile); + // return + return; + } + + private void installUpdate(File updateFile) { + // get status text view + MaterialTextView statusTextView = findViewById(R.id.update_progress_text); + // set status text to installing update + statusTextView.setText(R.string.installing_update); + // set progress bar to 100 + LinearProgressIndicator progressIndicator = findViewById(R.id.update_progress); + progressIndicator.setIndeterminate(true); + progressIndicator.setProgressCompat(100, false); + // install the update + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(updateFile), "application/vnd.android.package-archive"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + // return + return; + } + + public enum ACTIONS { + // action can be CHECK, DOWNLOAD, INSTALL + CHECK, DOWNLOAD, INSTALL } } \ No newline at end of file diff --git a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java index 52c1d6e..548b642 100644 --- a/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java +++ b/app/src/main/java/com/fox2code/mmm/settings/SettingsActivity.java @@ -49,6 +49,7 @@ import com.fox2code.mmm.Constants; import com.fox2code.mmm.MainActivity; import com.fox2code.mmm.MainApplication; import com.fox2code.mmm.R; +import com.fox2code.mmm.UpdateActivity; import com.fox2code.mmm.androidacy.AndroidacyRepoData; import com.fox2code.mmm.background.BackgroundUpdateChecker; import com.fox2code.mmm.installer.InstallerInitializer; @@ -491,12 +492,15 @@ public class SettingsActivity extends FoxActivity implements LanguageActivity { linkClickable.setVisible(BuildConfig.ENABLE_AUTO_UPDATER && (BuildConfig.DEBUG || AppUpdateManager.getAppUpdateManager().peekHasUpdate())); linkClickable.setOnPreferenceClickListener(p -> { devModeStep = 0; - IntentHelper.openUrl(p.getContext(), "https://github.com/Fox2Code/FoxMagiskModuleManager/releases"); + // open UpdateActivity with CHECK action + Intent intent = new Intent(requireContext(), UpdateActivity.class); + intent.setAction(UpdateActivity.ACTIONS.CHECK.name()); + startActivity(intent); return true; }); linkClickable.setOnPreferenceLongClickListener(p -> { String toastText = requireContext().getString(R.string.link_copied); - clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code/FoxMagiskModuleManager/releases")); + clipboard.setPrimaryClip(ClipData.newPlainText(toastText, "https://github.com/Fox2Code/FoxMagiskModuleManager/releases/latest")); Toast.makeText(requireContext(), toastText, Toast.LENGTH_SHORT).show(); return true; }); diff --git a/app/src/main/res/drawable/baseline_system_update_24.xml b/app/src/main/res/drawable/baseline_system_update_24.xml new file mode 100644 index 0000000..4ddaa2e --- /dev/null +++ b/app/src/main/res/drawable/baseline_system_update_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/activity_update.xml b/app/src/main/res/layout/activity_update.xml index aec5912..fcd1e6f 100644 --- a/app/src/main/res/layout/activity_update.xml +++ b/app/src/main/res/layout/activity_update.xml @@ -1,69 +1,77 @@ - + android:orientation="vertical" tools:context=".UpdateActivity"> - + + + + android:textAppearance="?attr/textAppearanceHeadline6" /> - + android:textAppearance="?attr/textAppearanceBody2" /> + android:layout_marginTop="16dp" /> - + android:textAppearance="?attr/textAppearanceBody2" /> + android:visibility="invisible" /> + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b0da650..25c3b4f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -329,6 +329,14 @@ Modules to exclude from automatic update checks In-app Updater Update app - An update is available for FoxMMM. Please wait while we download and install it. - Please wait... + An update may be available for FoxMMM. Please wait while we download and install it. + Please wait...ERROR: Invalid data received + You appear to be running a debug build. Debug builds must be updated manually, and do not support in-app updates + ERROR: Invalid action specified. Refusing to continue.Update found + Checking for updates… + You\'re up to date! + Download update + An error occurred downloading the update information. + ERROR: Failed to parse update information + Downloading update… %1$d\%Installing update…ERROR: Could not find update package.Check for app updates diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 285cf42..175daf9 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -62,7 +62,7 @@ app:icon="@drawable/ic_baseline_system_update_24" app:key="pref_update" app:singleLineTitle="false" - app:title="@string/app_update" /> + app:title="@string/check_for_updates" />