diff --git a/app/Android.bp b/app/Android.bp
index 4f79f13217074fa00374ab24df4386901ec0148c..a370fb09daefabcd05103193bba77700557eccc7 100644
--- a/app/Android.bp
+++ b/app/Android.bp
@@ -10,7 +10,10 @@ android_app {
// Include SettingsLib and its dependencies
defaults: ["SettingsLibDefaults"],
- srcs: ["src/main/java/**/*.java"],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
resource_dirs: ["src/main/res"],
manifest: "src/main/AndroidManifest.xml",
diff --git a/app/privapp_whitelist_org.lineageos.updater.xml b/app/privapp_whitelist_org.lineageos.updater.xml
index d88ad9cb1bdc20bdbf3a9a4f82c89b00f50dba89..6361985ba8bf6185be934610761d68c8f8941860 100644
--- a/app/privapp_whitelist_org.lineageos.updater.xml
+++ b/app/privapp_whitelist_org.lineageos.updater.xml
@@ -16,9 +16,11 @@
-->
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 80fd2bcb5f154f8e4d6d72ed0b1e44cec03454b8..6f60113d2e03ac17f29461c32290bb5b97c8360e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,8 +11,11 @@
+
+
+ sortedUpdates = controller.getUpdates();
if (sortedUpdates.isEmpty()) {
findViewById(R.id.no_new_updates_view).setVisibility(View.VISIBLE);
- findViewById(R.id.recycler_view).setVisibility(View.GONE);
+ findViewById(R.id.content).setVisibility(View.GONE);
} else {
findViewById(R.id.no_new_updates_view).setVisibility(View.GONE);
- findViewById(R.id.recycler_view).setVisibility(View.VISIBLE);
+ findViewById(R.id.content).setVisibility(View.VISIBLE);
sortedUpdates.sort((u1, u2) -> Long.compare(u2.getTimestamp(), u1.getTimestamp()));
for (UpdateInfo update : sortedUpdates) {
updateIds.add(update.getDownloadId());
@@ -426,7 +428,7 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
preferences.edit().putLong(Constants.PREF_LAST_UPDATE_CHECK, millis).apply();
updateLastCheckedString();
if (json.exists() && Utils.isUpdateCheckEnabled(this) &&
- Utils.checkForNewUpdates(json, jsonNew)) {
+ Utils.checkForNewUpdates(jsonNew, this)) {
UpdatesCheckReceiver.updateRepeatingUpdatesCheck(this);
}
// In case we set a one-shot check because of a previous failure
@@ -554,7 +556,7 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
mRefreshIconView.setEnabled(false);
}
} else {
- findViewById(R.id.recycler_view).setVisibility(View.GONE);
+ findViewById(R.id.content).setVisibility(View.GONE);
findViewById(R.id.no_new_updates_view).setVisibility(View.GONE);
findViewById(R.id.refresh_progress).setVisibility(View.VISIBLE);
}
@@ -568,11 +570,12 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
}
} else {
findViewById(R.id.refresh_progress).setVisibility(View.GONE);
- if (mAdapter.getItemCount() > 0) {
- findViewById(R.id.recycler_view).setVisibility(View.VISIBLE);
- } else {
- findViewById(R.id.no_new_updates_view).setVisibility(View.VISIBLE);
- }
+ }
+
+ if (mAdapter.getItemCount() > 0) {
+ findViewById(R.id.content).setVisibility(View.VISIBLE);
+ } else {
+ findViewById(R.id.no_new_updates_view).setVisibility(View.VISIBLE);
}
}
@@ -581,6 +584,7 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
View view = LayoutInflater.from(this).inflate(R.layout.preferences_dialog, null);
Spinner autoCheckInterval = view.findViewById(R.id.preferences_auto_updates_check_interval);
SwitchCompat autoDelete = view.findViewById(R.id.preferences_auto_delete_updates);
+ SwitchCompat allUpdates = view.findViewById(R.id.preferences_all_updates);
SwitchCompat meteredNetworkWarning = view.findViewById(
R.id.preferences_metered_network_warning);
SwitchCompat abPerfMode = view.findViewById(R.id.preferences_ab_perf_mode);
@@ -591,11 +595,31 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ List intervalList = new ArrayList<>(Arrays.asList(getResources().getStringArray(
+ R.array.menu_auto_updates_check_interval_entries)));
+
+ if (Utils.isDevModeOn(this)) {
+ // Add additional intervals while enabling developer options is on
+ intervalList.addAll(Arrays.asList(getResources().getStringArray(
+ R.array.menu_auto_updates_check_interval_entries_dev)));
+ } else if (Utils.getUpdateCheckSetting(this) > 3) {
+ prefs.edit().putInt(Constants.PREF_AUTO_UPDATES_CHECK_INTERVAL,
+ Constants.AUTO_UPDATES_CHECK_INTERVAL_DAILY).apply();
+ }
+
+ ArrayAdapter adapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_list_item_1, intervalList);
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+ autoCheckInterval.setAdapter(adapter);
autoCheckInterval.setSelection(Utils.getUpdateCheckSetting(this));
- autoDelete.setChecked(prefs.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false));
+
+ autoDelete.setChecked(prefs.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, true));
+ allUpdates.setChecked(prefs.getBoolean(Constants.PREF_ALL_UPDATES, false));
meteredNetworkWarning.setChecked(prefs.getBoolean(Constants.PREF_METERED_NETWORK_WARNING,
prefs.getBoolean(Constants.PREF_MOBILE_DATA_WARNING, true)));
- abPerfMode.setChecked(prefs.getBoolean(Constants.PREF_AB_PERF_MODE, false));
+ abPerfMode.setChecked(prefs.getBoolean(Constants.PREF_AB_PERF_MODE,
+ getResources().getBoolean(R.bool.config_prioritizeUpdateProcess)));
if (getResources().getBoolean(R.bool.config_hideRecoveryUpdate)) {
// Hide the update feature if explicitly requested.
@@ -633,6 +657,7 @@ public class UpdatesActivity extends UpdatesListActivity implements UpdateImport
.putInt(Constants.PREF_AUTO_UPDATES_CHECK_INTERVAL,
autoCheckInterval.getSelectedItemPosition())
.putBoolean(Constants.PREF_AUTO_DELETE_UPDATES, autoDelete.isChecked())
+ .putBoolean(Constants.PREF_ALL_UPDATES, allUpdates.isChecked())
.putBoolean(Constants.PREF_METERED_NETWORK_WARNING,
meteredNetworkWarning.isChecked())
.putBoolean(Constants.PREF_AB_PERF_MODE, abPerfMode.isChecked())
diff --git a/app/src/main/java/org/lineageos/updater/UpdatesCheckReceiver.java b/app/src/main/java/org/lineageos/updater/UpdatesCheckReceiver.java
index 9f454239338717f4a93dedfd3e7354745ce13f53..8ad0f4de669f8e5d4618494bae21bf7445fb002d 100644
--- a/app/src/main/java/org/lineageos/updater/UpdatesCheckReceiver.java
+++ b/app/src/main/java/org/lineageos/updater/UpdatesCheckReceiver.java
@@ -24,14 +24,17 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.SystemClock;
+import android.provider.Settings;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.preference.PreferenceManager;
import org.json.JSONException;
+import org.lineageos.updater.controller.UpdaterService;
import org.lineageos.updater.download.DownloadClient;
import org.lineageos.updater.misc.Constants;
+import org.lineageos.updater.misc.JsonValidator;
import org.lineageos.updater.misc.Utils;
import java.io.File;
@@ -51,29 +54,53 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
+ final SharedPreferences preferences =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ SharedPreferences.Editor editor = preferences.edit();
+
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ // Check if the current value is empty or null and set anon hash.
+ String eLicenseID = Settings.Secure.getString(context.getContentResolver(),
+ "e_license_id");
+ String anonHash = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.OTA_ANON_HASH);
+ if (anonHash == null || anonHash.isEmpty()) {
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.OTA_ANON_HASH, (eLicenseID != null &&
+ !eLicenseID.isEmpty()) ? eLicenseID : Utils.generateRandomID());
+ }
+
Utils.cleanupDownloadsDir(context);
+
+ // Reset resume or update check failed on reboot
+ editor.putBoolean(Constants.AUTO_UPDATE_CHECK_FAILED, false).apply();
+ editor.putString(Constants.RESUME_DOWNLOAD_ID, "").apply();
}
- final SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(context);
+ final File json = Utils.getCachedUpdateList(context);
+ if (json.exists() && !JsonValidator.validateJsonFile(json) && json.delete()) {
+ Log.i(TAG, "Removing cached json file due validation failure");
+ }
if (!Utils.isUpdateCheckEnabled(context)) {
return;
}
- if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- // Set a repeating alarm on boot to check for new updates once per day
- scheduleRepeatingUpdatesCheck(context);
- }
+ // Exact alarms does not support repeating, so update to make it
+ // work like repeating alarms. To check for update at the exact time.
+ updateRepeatingUpdatesCheck(context);
if (!Utils.isNetworkAvailable(context)) {
- Log.d(TAG, "Network not available, scheduling new check");
- scheduleUpdatesCheck(context);
+ if (!UpdaterService.isNetworkCallBackActive()) {
+ editor.putBoolean(Constants.AUTO_UPDATE_CHECK_FAILED, true).apply();
+ UpdaterService.setupNetworkCallback(true);
+ }
return;
+ } else if (UpdaterService.isNetworkCallBackActive()) {
+ editor.putBoolean(Constants.AUTO_UPDATE_CHECK_FAILED, false).apply();
+ UpdaterService.setupNetworkCallback(false);
}
- final File json = Utils.getCachedUpdateList(context);
final File jsonNew = new File(json.getAbsolutePath() + UUID.randomUUID());
String url = Utils.getServerURL(context);
DownloadClient.DownloadCallback callback = new DownloadClient.DownloadCallback() {
@@ -90,16 +117,19 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
@Override
public void onSuccess() {
try {
- if (json.exists() && Utils.checkForNewUpdates(json, jsonNew)) {
+ if (!JsonValidator.validateJsonFile(jsonNew)) {
+ Log.i(TAG, "Could not parse list, scheduling new check");
+ scheduleUpdatesCheck(context);
+ return;
+ }
+ if (json.exists() && Utils.checkForNewUpdates(jsonNew, context)) {
showNotification(context);
updateRepeatingUpdatesCheck(context);
}
//noinspection ResultOfMethodCallIgnored
jsonNew.renameTo(json);
long currentMillis = System.currentTimeMillis();
- preferences.edit()
- .putLong(Constants.PREF_LAST_UPDATE_CHECK, currentMillis)
- .apply();
+ editor.putLong(Constants.PREF_LAST_UPDATE_CHECK, currentMillis).apply();
// In case we set a one-shot check because of a previous failure
cancelUpdatesCheck(context);
} catch (IOException | JSONException e) {
@@ -160,12 +190,10 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
PendingIntent updateCheckIntent = getRepeatingUpdatesCheckIntent(context);
AlarmManager alarmMgr = context.getSystemService(AlarmManager.class);
- alarmMgr.setRepeating(AlarmManager.RTC, System.currentTimeMillis() +
- Utils.getUpdateCheckInterval(context), Utils.getUpdateCheckInterval(context),
- updateCheckIntent);
+ long nextCheck = System.currentTimeMillis() + Utils.getUpdateCheckInterval(context);
+ alarmMgr.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextCheck, updateCheckIntent);
- Date nextCheckDate = new Date(System.currentTimeMillis() +
- Utils.getUpdateCheckInterval(context));
+ Date nextCheckDate = new Date(nextCheck);
Log.d(TAG, "Setting automatic updates check: " + nextCheckDate);
}
@@ -181,14 +209,14 @@ public class UpdatesCheckReceiver extends BroadcastReceiver {
}
public static void scheduleUpdatesCheck(Context context) {
- long millisToNextCheck = AlarmManager.INTERVAL_HOUR * 2;
+ long millisToNextCheck = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
PendingIntent updateCheckIntent = getUpdatesCheckIntent(context);
AlarmManager alarmMgr = context.getSystemService(AlarmManager.class);
- alarmMgr.set(AlarmManager.ELAPSED_REALTIME,
- SystemClock.elapsedRealtime() + millisToNextCheck,
- updateCheckIntent);
+ long nextCheck = SystemClock.elapsedRealtime() + millisToNextCheck;
+ alarmMgr.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME,
+ nextCheck, updateCheckIntent);
- Date nextCheckDate = new Date(System.currentTimeMillis() + millisToNextCheck);
+ Date nextCheckDate = new Date(nextCheck);
Log.d(TAG, "Setting one-shot updates check: " + nextCheckDate);
}
diff --git a/app/src/main/java/org/lineageos/updater/UpdatesDbHelper.java b/app/src/main/java/org/lineageos/updater/UpdatesDbHelper.java
index 6c11a60c40289968ac5935cefbbc3f75708fdacf..bf78b829f5009b85ef26692c85389fdaf154e088 100644
--- a/app/src/main/java/org/lineageos/updater/UpdatesDbHelper.java
+++ b/app/src/main/java/org/lineageos/updater/UpdatesDbHelper.java
@@ -30,7 +30,7 @@ import java.util.List;
public class UpdatesDbHelper extends SQLiteOpenHelper {
- public static final int DATABASE_VERSION = 1;
+ public static final int DATABASE_VERSION = 3;
public static final String DATABASE_NAME = "updates.db";
public static class UpdateEntry implements BaseColumns {
@@ -41,7 +41,9 @@ public class UpdatesDbHelper extends SQLiteOpenHelper {
public static final String COLUMN_NAME_TIMESTAMP = "timestamp";
public static final String COLUMN_NAME_TYPE = "type";
public static final String COLUMN_NAME_VERSION = "version";
+ public static final String COLUMN_NAME_DISPLAY_VERSION = "display_version";
public static final String COLUMN_NAME_SIZE = "size";
+ public static final String COLUMN_NAME_ANDROID_VERSION = "android_version";
}
private static final String SQL_CREATE_ENTRIES =
@@ -53,7 +55,9 @@ public class UpdatesDbHelper extends SQLiteOpenHelper {
UpdateEntry.COLUMN_NAME_TIMESTAMP + " INTEGER," +
UpdateEntry.COLUMN_NAME_TYPE + " TEXT," +
UpdateEntry.COLUMN_NAME_VERSION + " TEXT," +
- UpdateEntry.COLUMN_NAME_SIZE + " INTEGER)";
+ UpdateEntry.COLUMN_NAME_DISPLAY_VERSION + " TEXT," +
+ UpdateEntry.COLUMN_NAME_SIZE + " INTEGER," +
+ UpdateEntry.COLUMN_NAME_ANDROID_VERSION + " TEXT)";
private static final String SQL_DELETE_ENTRIES =
"DROP TABLE IF EXISTS " + UpdateEntry.TABLE_NAME;
@@ -89,7 +93,9 @@ public class UpdatesDbHelper extends SQLiteOpenHelper {
values.put(UpdateEntry.COLUMN_NAME_TIMESTAMP, update.getTimestamp());
values.put(UpdateEntry.COLUMN_NAME_TYPE, update.getType());
values.put(UpdateEntry.COLUMN_NAME_VERSION, update.getVersion());
+ values.put(UpdateEntry.COLUMN_NAME_DISPLAY_VERSION, update.getDisplayVersion());
values.put(UpdateEntry.COLUMN_NAME_SIZE, update.getFileSize());
+ values.put(UpdateEntry.COLUMN_NAME_ANDROID_VERSION, update.getAndroidVersion());
}
public void removeUpdate(String downloadId) {
@@ -125,8 +131,10 @@ public class UpdatesDbHelper extends SQLiteOpenHelper {
UpdateEntry.COLUMN_NAME_TIMESTAMP,
UpdateEntry.COLUMN_NAME_TYPE,
UpdateEntry.COLUMN_NAME_VERSION,
+ UpdateEntry.COLUMN_NAME_DISPLAY_VERSION,
UpdateEntry.COLUMN_NAME_STATUS,
UpdateEntry.COLUMN_NAME_SIZE,
+ UpdateEntry.COLUMN_NAME_ANDROID_VERSION,
};
String sort = UpdateEntry.COLUMN_NAME_TIMESTAMP + " DESC";
Cursor cursor = db.query(UpdateEntry.TABLE_NAME, projection, selection, selectionArgs,
@@ -146,10 +154,14 @@ public class UpdatesDbHelper extends SQLiteOpenHelper {
update.setType(cursor.getString(index));
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_VERSION);
update.setVersion(cursor.getString(index));
+ index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_DISPLAY_VERSION);
+ update.setDisplayVersion(cursor.getString(index));
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_STATUS);
update.setPersistentStatus(cursor.getInt(index));
index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_SIZE);
update.setFileSize(cursor.getLong(index));
+ index = cursor.getColumnIndex(UpdateEntry.COLUMN_NAME_ANDROID_VERSION);
+ update.setAndroidVersion(cursor.getString(index));
updates.add(update);
}
cursor.close();
diff --git a/app/src/main/java/org/lineageos/updater/UpdatesListAdapter.java b/app/src/main/java/org/lineageos/updater/UpdatesListAdapter.java
index 30144701df96ebea1a9f818200cea581aa763fff..c65fdf2e0a37d1c0cb4bb79450e62ebc80a06120 100644
--- a/app/src/main/java/org/lineageos/updater/UpdatesListAdapter.java
+++ b/app/src/main/java/org/lineageos/updater/UpdatesListAdapter.java
@@ -15,13 +15,10 @@
*/
package org.lineageos.updater;
-import android.app.Activity;
import android.content.Intent;
-import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.res.Resources;
-import android.net.Uri;
-import android.os.BatteryManager;
+import android.os.Build;
import android.os.PowerManager;
import android.text.SpannableString;
import android.text.format.Formatter;
@@ -40,8 +37,6 @@ import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
-import androidx.activity.result.ActivityResultLauncher;
-import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.view.ContextThemeWrapper;
@@ -55,17 +50,18 @@ import com.google.android.material.snackbar.Snackbar;
import org.lineageos.updater.controller.UpdaterController;
import org.lineageos.updater.controller.UpdaterService;
-import org.lineageos.updater.misc.BuildInfoUtils;
import org.lineageos.updater.misc.Constants;
import org.lineageos.updater.misc.StringGenerator;
import org.lineageos.updater.misc.Utils;
import org.lineageos.updater.model.UpdateInfo;
import org.lineageos.updater.model.UpdateStatus;
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.DateFormat;
+import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
@@ -75,10 +71,6 @@ public class UpdatesListAdapter extends RecyclerView.Adapter mDownloadIds;
@@ -103,6 +95,7 @@ public class UpdatesListAdapter extends RecyclerView.Adapter 0) {
+ requiredSpace -= updateFile.length();
+ }
+
+ if (availableFreeSpace < requiredSpace) {
+ // Not enough space to download file
+ double spaceNeeded = (requiredSpace - availableFreeSpace) / (1024.0 * 1024.0);
+ // Ignore if needed space is below 1 MB, like 0.25
+ // We only show integer part to the user
+ if (spaceNeeded >= 1) {
+ String message = resources.getString(R.string.e_dialog_free_space_low_message_pct,
+ new DecimalFormat("# MB").format(spaceNeeded));
+ return new AlertDialog.Builder(mActivity)
+ .setTitle(R.string.e_dialog_free_space_low_title)
+ .setMessage(message)
+ .setPositiveButton(android.R.string.ok, null);
+ }
+ }
+
+ return null;
+ }
+
private void startDownloadWithWarning(final String downloadId) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mActivity);
boolean warn = preferences.getBoolean(Constants.PREF_METERED_NETWORK_WARNING, true);
@@ -342,7 +373,8 @@ public class UpdatesListAdapter extends RecyclerView.Adapter {
if (checkbox.isChecked()) {
preferences.edit()
@@ -361,9 +393,18 @@ public class UpdatesListAdapter extends RecyclerView.Adapter startDownloadWithWarning(downloadId) : null;
+ clickListener = enabled ? view -> {
+ AlertDialog.Builder freeSpaceDialog = getSpaceDialog(
+ mUpdaterController.getUpdate(downloadId));
+ if (freeSpaceDialog != null) {
+ freeSpaceDialog.show();
+ } else {
+ startDownloadWithWarning(downloadId);
+ }
+ } : null;
break;
case PAUSE:
button.setText(R.string.action_pause);
@@ -379,7 +420,12 @@ public class UpdatesListAdapter extends RecyclerView.Adapter {
if (canInstall) {
- mUpdaterController.resumeDownload(downloadId);
+ AlertDialog.Builder freeSpaceDialog = getSpaceDialog(update);
+ if (freeSpaceDialog != null) {
+ freeSpaceDialog.show();
+ } else {
+ mUpdaterController.resumeDownload(downloadId);
+ }
} else {
mActivity.showSnackbar(R.string.snack_update_not_installable,
Snackbar.LENGTH_LONG);
@@ -395,7 +441,10 @@ public class UpdatesListAdapter extends RecyclerView.Adapter {
if (canInstall) {
AlertDialog.Builder installDialog = getInstallDialog(downloadId);
- if (installDialog != null) {
+ AlertDialog.Builder freeSpaceDialog = getSpaceDialog(update);
+ if (freeSpaceDialog != null) {
+ freeSpaceDialog.show();
+ } else if (installDialog != null) {
installDialog.show();
}
} else {
@@ -468,8 +517,8 @@ public class UpdatesListAdapter extends RecyclerView.Adapter {
- Utils.triggerUpdate(mActivity, downloadId);
- maybeShowInfoDialog();
- })
+ (dialog, which) -> Utils.triggerUpdate(mActivity, downloadId))
.setNegativeButton(android.R.string.cancel, null);
}
@@ -525,21 +603,6 @@ public class UpdatesListAdapter extends RecyclerView.Adapter preferences.edit()
- .putBoolean(Constants.HAS_SEEN_INFO_DIALOG, true)
- .apply())
- .show();
- }
-
private void startActionMode(final UpdateInfo update, final boolean canDelete, View anchor) {
mSelectedDownload = update.getDownloadId();
notifyItemChanged(update.getDownloadId());
@@ -604,21 +667,6 @@ public class UpdatesListAdapter extends RecyclerView.Adapter= required;
- }
-
private static boolean isScratchMounted() {
try (Stream lines = Files.lines(Path.of("/proc/mounts"))) {
return lines.anyMatch(x -> x.split(" ")[1].equals("/mnt/scratch"));
diff --git a/app/src/main/java/org/lineageos/updater/controller/UpdateInstaller.java b/app/src/main/java/org/lineageos/updater/controller/UpdateInstaller.java
index b111f9f33275d3810f6bd6a5f890f9d131591823..720d13ec5a5d45c17fb38b927ddbec20ff6f9326 100644
--- a/app/src/main/java/org/lineageos/updater/controller/UpdateInstaller.java
+++ b/app/src/main/java/org/lineageos/updater/controller/UpdateInstaller.java
@@ -31,8 +31,8 @@ import org.lineageos.updater.model.UpdateStatus;
import java.io.File;
import java.io.IOException;
-import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.Files;
+import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
diff --git a/app/src/main/java/org/lineageos/updater/controller/UpdaterService.java b/app/src/main/java/org/lineageos/updater/controller/UpdaterService.java
index e1e0c998a1a9eedaeafdc2697f218c240ab93f33..24236f6fef8d0b4685fb8eb03f734935b75be412 100644
--- a/app/src/main/java/org/lineageos/updater/controller/UpdaterService.java
+++ b/app/src/main/java/org/lineageos/updater/controller/UpdaterService.java
@@ -15,6 +15,10 @@
*/
package org.lineageos.updater.controller;
+import static android.os.SystemUpdateManager.STATUS_IN_PROGRESS;
+import static android.os.SystemUpdateManager.STATUS_WAITING_DOWNLOAD;
+import static android.os.SystemUpdateManager.STATUS_WAITING_REBOOT;
+
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -25,9 +29,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ServiceInfo;
+import android.net.ConnectivityManager;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
+import android.service.notification.StatusBarNotification;
import android.text.format.Formatter;
import android.util.Log;
@@ -38,7 +46,7 @@ import androidx.preference.PreferenceManager;
import org.lineageos.updater.R;
import org.lineageos.updater.UpdaterReceiver;
import org.lineageos.updater.UpdatesActivity;
-import org.lineageos.updater.misc.BuildInfoUtils;
+import org.lineageos.updater.misc.ConnectionStateMonitor;
import org.lineageos.updater.misc.Constants;
import org.lineageos.updater.misc.StringGenerator;
import org.lineageos.updater.misc.Utils;
@@ -46,7 +54,6 @@ import org.lineageos.updater.model.Update;
import org.lineageos.updater.model.UpdateInfo;
import org.lineageos.updater.model.UpdateStatus;
-import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.NumberFormat;
@@ -73,6 +80,7 @@ public class UpdaterService extends Service {
private static final int NOTIFICATION_ID = 10;
private final IBinder mBinder = new LocalBinder();
+ private static boolean isNetworkCallBackActive = false;
private boolean mHasClients;
private BroadcastReceiver mBroadcastReceiver;
@@ -82,11 +90,21 @@ public class UpdaterService extends Service {
private UpdaterController mUpdaterController;
+ private static final NetworkRequest networkRequest = new NetworkRequest.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .build();
+ private static ConnectivityManager.NetworkCallback mConnectionStateMonitor;
+ private static ConnectivityManager mConnectivityManager;
+
@Override
public void onCreate() {
super.onCreate();
mUpdaterController = UpdaterController.getInstance(this);
+ mConnectionStateMonitor = new ConnectionStateMonitor().getInstance(this);
+ mConnectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mNotificationManager = getSystemService(NotificationManager.class);
NotificationChannel notificationChannel = new NotificationChannel(
@@ -250,15 +268,34 @@ public class UpdaterService extends Service {
private void tryStopSelf() {
if (!mHasClients && !mUpdaterController.hasActiveDownloads() &&
- !mUpdaterController.isInstallingUpdate()) {
+ !mUpdaterController.isInstallingUpdate() && !isNetworkCallBackActive()
+ && !areNotificationsActive()) {
Log.d(TAG, "Service no longer needed, stopping");
stopSelf();
}
}
+ private boolean areNotificationsActive() {
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ StatusBarNotification[] notifications = notificationManager.getActiveNotifications();
+ if (notifications != null && notifications.length > 0) {
+ for (StatusBarNotification notification : notifications) {
+ if (notification.getId() == NOTIFICATION_ID &&
+ notification.getPackageName().equals(getPackageName())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private void handleUpdateStatusChange(UpdateInfo update) {
+ SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = pref.edit();
switch (update.getStatus()) {
case DELETED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
mNotificationBuilder.setOngoing(false);
mNotificationManager.cancel(NOTIFICATION_ID);
@@ -266,6 +303,7 @@ public class UpdaterService extends Service {
break;
}
case STARTING: {
+ notifySystemUpdaterService(STATUS_IN_PROGRESS, update);
mNotificationBuilder.mActions.clear();
mNotificationBuilder.setProgress(0, 0, true);
mNotificationStyle.setSummaryText(null);
@@ -282,6 +320,7 @@ public class UpdaterService extends Service {
break;
}
case DOWNLOADING: {
+ notifySystemUpdaterService(STATUS_IN_PROGRESS, update);
String text = getString(R.string.downloading_notification);
mNotificationStyle.bigText(text);
mNotificationBuilder.setStyle(mNotificationStyle);
@@ -289,6 +328,10 @@ public class UpdaterService extends Service {
mNotificationBuilder.addAction(android.R.drawable.ic_media_pause,
getString(R.string.pause_button),
getPausePendingIntent(update.getDownloadId()));
+ if (isNetworkCallBackActive) {
+ editor.putString(Constants.RESUME_DOWNLOAD_ID, "").apply();
+ setupNetworkCallback(false);
+ }
mNotificationBuilder.setTicker(text);
mNotificationBuilder.setOngoing(true);
mNotificationBuilder.setAutoCancel(false);
@@ -296,6 +339,7 @@ public class UpdaterService extends Service {
break;
}
case PAUSED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
// In case we pause before the first progress update
mNotificationBuilder.setProgress(100, update.getProgress(), false);
@@ -307,6 +351,10 @@ public class UpdaterService extends Service {
mNotificationBuilder.addAction(android.R.drawable.ic_media_play,
getString(R.string.resume_button),
getResumePendingIntent(update.getDownloadId()));
+ if (isNetworkCallBackActive) {
+ editor.putString(Constants.RESUME_DOWNLOAD_ID, "").apply();
+ setupNetworkCallback(false);
+ }
mNotificationBuilder.setTicker(text);
mNotificationBuilder.setOngoing(false);
mNotificationBuilder.setAutoCancel(false);
@@ -315,6 +363,7 @@ public class UpdaterService extends Service {
break;
}
case PAUSED_ERROR: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
int progress = update.getProgress();
// In case we pause before the first progress update
@@ -331,10 +380,15 @@ public class UpdaterService extends Service {
mNotificationBuilder.setOngoing(false);
mNotificationBuilder.setAutoCancel(false);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
+ if (!Utils.isNetworkAvailable(this)) {
+ editor.putString(Constants.RESUME_DOWNLOAD_ID, update.getDownloadId()).apply();
+ setupNetworkCallback(true);
+ }
tryStopSelf();
break;
}
case VERIFYING: {
+ notifySystemUpdaterService(STATUS_IN_PROGRESS, update);
mNotificationBuilder.setProgress(0, 0, true);
mNotificationStyle.setSummaryText(null);
mNotificationBuilder.setStyle(mNotificationStyle);
@@ -347,20 +401,55 @@ public class UpdaterService extends Service {
break;
}
case VERIFIED: {
+ notifySystemUpdaterService(STATUS_IN_PROGRESS, update);
stopForeground(STOP_FOREGROUND_DETACH);
mNotificationBuilder.setStyle(null);
mNotificationBuilder.setSmallIcon(R.drawable.ic_system_update);
mNotificationBuilder.setProgress(0, 0, false);
- String text = getString(R.string.download_completed_notification);
+ String text = getString(R.string.e_download_completed_notification);
+ boolean hasRequiredSpace = Utils.availableFreeSpace() > (update.getFileSize() * 2);
+
+ if (!Utils.canInstall(update) || !Utils.isBatteryLevelOk(this)
+ || !hasRequiredSpace) {
+ /* Show notification if any of the below condition didn't met. */
+ text = getString(R.string.blocked_update_dialog_title) + ". ";
+ if (!Utils.isBatteryLevelOk(this)) {
+ text = text + getString(R.string.dialog_battery_low_title);
+ } else if (!hasRequiredSpace) {
+ text = text + getString(R.string.e_dialog_free_space_low_title);
+ } else if (!Utils.canInstall(update)) {
+ text = text + getString(R.string.verification_failed_notification);
+ }
+ } else if (!Utils.isABDevice()) {
+ /* Add action to reboot and install for Non-A/B devices. */
+ mNotificationBuilder.mActions.clear();
+ mNotificationBuilder.addAction(R.drawable.ic_system_update,
+ getString(R.string.e_reboot_install),
+ getInstallationPendingIntent(update.getDownloadId()));
+ }
+
mNotificationBuilder.setContentText(text);
mNotificationBuilder.setTicker(text);
mNotificationBuilder.setOngoing(false);
mNotificationBuilder.setAutoCancel(true);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
- tryStopSelf();
+
+ /* Make sure these conditions are met before auto install
+ - Can install package (Is a newer build or downgrade allowed)
+ - Battery level (Above 30% if discharging or 20% if charging)
+ - Free space to install (Double the size of the ota zip)
+ */
+ if (Utils.isABDevice() && Utils.canInstall(update)
+ && Utils.isBatteryLevelOk(this) && hasRequiredSpace) {
+ Utils.triggerUpdate(this, update.getDownloadId());
+ } else {
+ tryStopSelf();
+ }
+
break;
}
case VERIFICATION_FAILED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
mNotificationBuilder.setStyle(null);
mNotificationBuilder.setSmallIcon(android.R.drawable.stat_sys_warning);
@@ -375,6 +464,7 @@ public class UpdaterService extends Service {
break;
}
case INSTALLING: {
+ notifySystemUpdaterService(STATUS_IN_PROGRESS, update);
mNotificationBuilder.mActions.clear();
mNotificationBuilder.setStyle(mNotificationStyle);
mNotificationBuilder.setSmallIcon(R.drawable.ic_system_update);
@@ -398,6 +488,7 @@ public class UpdaterService extends Service {
break;
}
case INSTALLED: {
+ notifySystemUpdaterService(STATUS_WAITING_REBOOT, update);
stopForeground(STOP_FOREGROUND_DETACH);
mNotificationBuilder.mActions.clear();
mNotificationBuilder.setStyle(null);
@@ -413,8 +504,7 @@ public class UpdaterService extends Service {
mNotificationBuilder.setAutoCancel(true);
mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());
- SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
- boolean deleteUpdate = pref.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
+ boolean deleteUpdate = pref.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, true);
boolean isLocal = Update.LOCAL_ID.equals(update.getDownloadId());
// Always delete local updates
if (deleteUpdate || isLocal) {
@@ -425,6 +515,7 @@ public class UpdaterService extends Service {
break;
}
case INSTALLATION_FAILED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
mNotificationBuilder.setStyle(null);
mNotificationBuilder.setSmallIcon(android.R.drawable.stat_sys_warning);
@@ -439,11 +530,13 @@ public class UpdaterService extends Service {
break;
}
case INSTALLATION_CANCELLED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(true);
tryStopSelf();
break;
}
case INSTALLATION_SUSPENDED: {
+ notifySystemUpdaterService(STATUS_WAITING_DOWNLOAD, update);
stopForeground(STOP_FOREGROUND_DETACH);
// In case we pause before the first progress update
mNotificationBuilder.setProgress(100, update.getProgress(), false);
@@ -465,6 +558,8 @@ public class UpdaterService extends Service {
}
}
+ public static boolean isNetworkCallBackActive() { return isNetworkCallBackActive; }
+
private void handleDownloadProgressChange(UpdateInfo update) {
int progress = update.getProgress();
mNotificationBuilder.setProgress(100, progress, false);
@@ -499,12 +594,31 @@ public class UpdaterService extends Service {
private void setNotificationTitle(UpdateInfo update) {
String buildDate = StringGenerator.getDateLocalizedUTC(this,
DateFormat.MEDIUM, update.getTimestamp());
- String buildInfo = getString(R.string.list_build_version_date,
+ String buildInfo = getString(R.string.e_list_build_version_date,
update.getVersion(), buildDate);
mNotificationStyle.setBigContentTitle(buildInfo);
mNotificationBuilder.setContentTitle(buildInfo);
}
+ public static void setupNetworkCallback(boolean shouldEnable) {
+ if (mConnectivityManager == null || mConnectionStateMonitor == null) {
+ Log.e(TAG, "Unable to set network callback");
+ return;
+ }
+ if (shouldEnable) {
+ mConnectivityManager.registerNetworkCallback(networkRequest, mConnectionStateMonitor);
+ } else {
+ try {
+ mConnectivityManager.unregisterNetworkCallback(mConnectionStateMonitor);
+ } catch (IllegalArgumentException | NullPointerException e) {
+ Log.e(TAG, "Network callback was not registered");
+ }
+ }
+
+ isNetworkCallBackActive = shouldEnable;
+ Log.d(TAG, "Network callback enabled: " + shouldEnable);
+ }
+
private PendingIntent getResumePendingIntent(String downloadId) {
final Intent intent = new Intent(this, UpdaterService.class);
intent.setAction(ACTION_DOWNLOAD_CONTROL);
@@ -537,10 +651,22 @@ public class UpdaterService extends Service {
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
+ private PendingIntent getInstallationPendingIntent(String downloadId) {
+ final Intent intent = new Intent(this, UpdaterService.class);
+ intent.setAction(ACTION_INSTALL_UPDATE);
+ intent.putExtra(UpdaterService.EXTRA_DOWNLOAD_ID, downloadId);
+ return PendingIntent.getService(this, 0, intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ }
+
private PendingIntent getResumeInstallationPendingIntent() {
final Intent intent = new Intent(this, UpdaterService.class);
intent.setAction(ACTION_INSTALL_RESUME);
return PendingIntent.getService(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
+
+ private void notifySystemUpdaterService(int status, UpdateInfo update) {
+ Utils.updateSystemUpdaterService(this, status, update.getVersion());
+ }
}
diff --git a/app/src/main/java/org/lineageos/updater/download/HttpURLConnectionClient.java b/app/src/main/java/org/lineageos/updater/download/HttpURLConnectionClient.java
index b9c4b5dc694562cc46c7ef0bd0bee114fc0e43ad..606bef16ccf08c3667eaea8bb5882966977953b3 100644
--- a/app/src/main/java/org/lineageos/updater/download/HttpURLConnectionClient.java
+++ b/app/src/main/java/org/lineageos/updater/download/HttpURLConnectionClient.java
@@ -16,8 +16,11 @@
package org.lineageos.updater.download;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
+import org.lineageos.updater.misc.Constants;
+
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -58,6 +61,11 @@ public class HttpURLConnectionClient implements DownloadClient {
DownloadClient.DownloadCallback callback,
boolean useDuplicateLinks) throws IOException {
mClient = (HttpURLConnection) new URL(url).openConnection();
+
+ String defaultUserAgent = mClient.getRequestProperty("User-Agent");
+ String newUserAgent = defaultUserAgent + " eOS v" + SystemProperties.get(Constants.PROP_BUILD_VERSION);
+ mClient.setRequestProperty("User-Agent", newUserAgent);
+
mDestination = destination;
mProgressListener = progressListener;
mCallback = callback;
diff --git a/app/src/main/java/org/lineageos/updater/misc/BuildInfoUtils.java b/app/src/main/java/org/lineageos/updater/misc/BuildInfoUtils.java
index 4b90a45e545419a51c727fd23581071fba205f54..6a647faf2517460c8cd0084a87e285dfddb68b1d 100644
--- a/app/src/main/java/org/lineageos/updater/misc/BuildInfoUtils.java
+++ b/app/src/main/java/org/lineageos/updater/misc/BuildInfoUtils.java
@@ -29,4 +29,8 @@ public final class BuildInfoUtils {
public static String getBuildVersion() {
return SystemProperties.get(Constants.PROP_BUILD_VERSION);
}
+
+ public static String getDisplayVersion() {
+ return SystemProperties.get(Constants.PROP_BUILD_DISPLAY_VERSION);
+ }
}
diff --git a/app/src/main/java/org/lineageos/updater/misc/ConnectionStateMonitor.kt b/app/src/main/java/org/lineageos/updater/misc/ConnectionStateMonitor.kt
new file mode 100644
index 0000000000000000000000000000000000000000..edb1b51835791a21231055187856c8fe3693b8b9
--- /dev/null
+++ b/app/src/main/java/org/lineageos/updater/misc/ConnectionStateMonitor.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 MURENA SAS
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.lineageos.updater.misc
+
+import android.content.Context
+import android.content.Intent
+import android.content.SharedPreferences
+import android.net.ConnectivityManager
+import android.net.Network
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.preference.PreferenceManager
+import org.lineageos.updater.UpdatesCheckReceiver
+import org.lineageos.updater.controller.UpdaterController
+
+class ConnectionStateMonitor {
+ companion object {
+ private var instance: ConnectivityManager.NetworkCallback? = null
+ }
+
+ fun getInstance(context: Context): ConnectivityManager.NetworkCallback {
+ if (instance == null) {
+ instance = networkCallback(context)
+ }
+
+ return instance!!
+ }
+
+ /**
+ * API callbacks to determine which status we currently in
+ * we need the below callbacks:
+ * - onAvailable: device connected to a network of course
+ * - onLost: when the connection completely lost
+ */
+ private fun networkCallback(context: Context) = object: ConnectivityManager.NetworkCallback() {
+ private val tag = "ConnectionStateMonitor"
+ private val pref: SharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
+ private val updaterController: UpdaterController = UpdaterController.getInstance(context)
+
+ private fun checkForUpdatesOrResume() {
+ val downloadId: String = pref.getString(Constants.RESUME_DOWNLOAD_ID, "")!!
+ if (downloadId.isNotEmpty()) {
+ Handler(Looper.getMainLooper()).postDelayed({
+ updaterController.resumeDownload(
+ downloadId
+ )
+ }, 2000L) // 2 seconds
+ }
+ if (pref.getBoolean(Constants.AUTO_UPDATE_CHECK_FAILED, false)) {
+ Handler(Looper.getMainLooper()).postDelayed({
+ val broadcastIntent = Intent()
+ broadcastIntent.setClassName(context, UpdatesCheckReceiver::class.java.name)
+ context.sendBroadcast(broadcastIntent)
+ }, 10000L) // 10 seconds
+ }
+
+ }
+
+ override fun onAvailable(network: Network) {
+ Log.d(tag, "Network available")
+ checkForUpdatesOrResume()
+ }
+
+ override fun onLost(network: Network) {
+ Log.d(tag, "Network not available")
+ checkForUpdatesOrResume()
+ }
+ }
+}
diff --git a/app/src/main/java/org/lineageos/updater/misc/Constants.java b/app/src/main/java/org/lineageos/updater/misc/Constants.java
index beb9423f1e7e50d7693314508ab462afb455c33d..b75b4c1bcf4b5eaa75ab037d8aef8267d16af4a8 100644
--- a/app/src/main/java/org/lineageos/updater/misc/Constants.java
+++ b/app/src/main/java/org/lineageos/updater/misc/Constants.java
@@ -27,26 +27,33 @@ public final class Constants {
public static final int AUTO_UPDATES_CHECK_INTERVAL_DAILY = 1;
public static final int AUTO_UPDATES_CHECK_INTERVAL_WEEKLY = 2;
public static final int AUTO_UPDATES_CHECK_INTERVAL_MONTHLY = 3;
+ public static final int AUTO_UPDATES_CHECK_INTERVAL_5_MINUTES = 4;
+ public static final int AUTO_UPDATES_CHECK_INTERVAL_10_MINUTES = 5;
+ public static final int AUTO_UPDATES_CHECK_INTERVAL_30_MINUTES = 6;
public static final String PREF_LAST_UPDATE_CHECK = "last_update_check";
public static final String PREF_AUTO_UPDATES_CHECK_INTERVAL = "auto_updates_check_interval";
public static final String PREF_AUTO_DELETE_UPDATES = "auto_delete_updates";
+ public static final String PREF_ALL_UPDATES = "all_updates";
public static final String PREF_AB_PERF_MODE = "ab_perf_mode";
public static final String PREF_METERED_NETWORK_WARNING = "pref_metered_network_warning";
public static final String PREF_MOBILE_DATA_WARNING = "pref_mobile_data_warning";
public static final String PREF_NEEDS_REBOOT_ID = "needs_reboot_id";
+ public static final String PREF_NETWORK_CALLBACK_ACTIVE = "pref_network_callback_active";
public static final String UNCRYPT_FILE_EXT = ".uncrypt";
public static final String PROP_AB_DEVICE = "ro.build.ab_update";
public static final String PROP_BUILD_DATE = "ro.build.date.utc";
public static final String PROP_BUILD_VERSION = "ro.lineage.build.version";
+ public static final String PROP_BUILD_DISPLAY_VERSION = "ro.lineage.display.version";
public static final String PROP_BUILD_VERSION_INCREMENTAL = "ro.build.version.incremental";
public static final String PROP_DEVICE = "ro.lineage.device";
public static final String PROP_NEXT_DEVICE = "ro.updater.next_device";
public static final String PROP_RELEASE_TYPE = "ro.lineage.releasetype";
public static final String PROP_UPDATER_ALLOW_DOWNGRADING = "lineage.updater.allow_downgrading";
public static final String PROP_UPDATER_URI = "lineage.updater.uri";
+ public static final String PROP_VERSION = "ro.lineage.version";
public static final String PREF_INSTALL_OLD_TIMESTAMP = "install_old_timestamp";
public static final String PREF_INSTALL_NEW_TIMESTAMP = "install_new_timestamp";
@@ -57,5 +64,6 @@ public final class Constants {
public static final String UPDATE_RECOVERY_EXEC = "/vendor/bin/install-recovery.sh";
public static final String UPDATE_RECOVERY_PROPERTY = "persist.vendor.recovery_update";
- public static final String HAS_SEEN_INFO_DIALOG = "has_seen_info_dialog";
+ public static final String RESUME_DOWNLOAD_ID = "resume_download_id";
+ public static final String AUTO_UPDATE_CHECK_FAILED = "auto_update_check_failed";
}
diff --git a/app/src/main/java/org/lineageos/updater/misc/JsonValidator.java b/app/src/main/java/org/lineageos/updater/misc/JsonValidator.java
new file mode 100644
index 0000000000000000000000000000000000000000..a1401c8e37c60de94f67c4e688bc2225088c081e
--- /dev/null
+++ b/app/src/main/java/org/lineageos/updater/misc/JsonValidator.java
@@ -0,0 +1,166 @@
+package org.lineageos.updater.misc;
+
+import android.util.Log;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class JsonValidator {
+
+ private static final String TAG = "JsonValidator" ;
+ private static final String[] jsonMainFields = {
+ "error",
+ "id",
+ "response"
+ };
+ private static final String[] requiredFields = {
+ "api_level",
+ "url",
+ "timestamp",
+ "md5sum",
+ "channel",
+ "filename",
+ "romtype",
+ "version",
+ "display_version",
+ "android_version",
+ "id"
+ };
+ private static final String[] optionalFields = {
+ "changes",
+ "pre_version"
+ };
+
+ public static boolean validateJsonFile(File jsonFile) {
+ try {
+ if (!jsonFile.exists()) {
+ Log.i(TAG, "Unable to locate json file");
+ return false;
+ }
+
+ // Read the JSON data from the file
+ BufferedReader reader = new BufferedReader(new FileReader(jsonFile));
+ StringBuilder jsonString = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ jsonString.append(line);
+ }
+ reader.close();
+
+ List missingRequiredFields = new ArrayList<>();
+ JSONObject jsonObject = new JSONObject(jsonString.toString());
+ for (String field : jsonMainFields) {
+ if (!isMainFieldValid(jsonObject, field)) {
+ missingRequiredFields.add(field);
+ }
+ }
+
+ if (!missingRequiredFields.isEmpty()) {
+ Log.i(TAG, "Missing or invalid required field in response object: "
+ + missingRequiredFields);
+ return false;
+ }
+ } catch (IOException | JSONException e) {
+ Log.i(TAG, "Unable to parse the json file:" + e);
+ return false;
+ }
+
+ return true;
+ }
+
+ public static boolean validateResponseObject(JSONObject responseObject) {
+ List missingRequiredFields = new ArrayList<>();
+ List missingOptionalFields = new ArrayList<>();
+
+ for (String field : requiredFields) {
+ if (!isObjectFieldValid(responseObject, field)) {
+ missingRequiredFields.add(field);
+ }
+ }
+
+ for (String field : optionalFields) {
+ if (!isObjectFieldValid(responseObject, field)) {
+ missingOptionalFields.add(field);
+ }
+ }
+
+ String filename = responseObject.optString("filename", "");
+ if (!missingRequiredFields.isEmpty()) {
+ if (!filename.isEmpty()) {
+ Log.i(TAG, "Missing or invalid required field in response object for "
+ + filename + ": " + missingRequiredFields);
+ }
+ return false;
+ } else if (!missingOptionalFields.isEmpty() && !filename.isEmpty()) {
+ Log.i(TAG, "Missing or invalid optional field in response object for "
+ + filename + ": " + missingOptionalFields);
+ }
+
+ return true; // All required fields are present and valid
+ }
+
+ private static boolean isMainFieldValid(JSONObject jsonObject, String field) {
+ if (!jsonObject.has(field)) {
+ return false;
+ }
+
+ String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist
+
+ switch (field) {
+ case "id":
+ case "error":
+ // Allow null, as it's a valid value
+ return true;
+ case "response":
+ // Check if it's an array with a length >= 0
+ return jsonObject.optJSONArray(field) != null
+ && jsonObject.optJSONArray(field).length() >= 0;
+ default:
+ return !value.isEmpty();
+ }
+ }
+
+ private static boolean isObjectFieldValid(JSONObject jsonObject, String field) {
+ if (!jsonObject.has(field) || jsonObject.isNull(field)) {
+ return false;
+ }
+
+ String value = jsonObject.optString(field, ""); // Return empty string if field doesn't exist
+ if (value.isEmpty()) {
+ return false;
+ }
+
+ switch (field) {
+ case "datetime":
+ case "timestamp":
+ case "size":
+ case "api_level":
+ return Utils.isInteger(value);
+ case "is_upgrade_supported":
+ return "true".equals(value) || "false".equals(value);
+ case "android_version":
+ // Check if it's a valid Android version number
+ return value.matches("^(\\d+)$|^(\\d+\\.\\d+)$|^(\\d+\\.\\d+\\.\\d+)$");
+ case "md5sum":
+ // Check if it's a valid MD5 checksum
+ return value.matches("^[a-fA-F0-9]{32}$");
+ case "filename":
+ return value.endsWith(".zip");
+ case "id":
+ // Check if it's a valid SHA-256 checksum
+ return value.matches("^[a-fA-F0-9]{64}$");
+ case "url":
+ // Check if it's a valid URL format
+ return value.startsWith("http://") || value.startsWith("https://");
+ default:
+ return true;
+ }
+ }
+}
diff --git a/app/src/main/java/org/lineageos/updater/misc/Utils.java b/app/src/main/java/org/lineageos/updater/misc/Utils.java
index b7ba720f5037735a890a07e90cb62990d42e5242..43b276d1a4afc1dc7244f817e3bad0df4e57673a 100644
--- a/app/src/main/java/org/lineageos/updater/misc/Utils.java
+++ b/app/src/main/java/org/lineageos/updater/misc/Utils.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2017-2023 The LineageOS Project
+ * Copyright (C) 2022 ECORP SAS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,18 +16,34 @@
*/
package org.lineageos.updater.misc;
+import static android.os.SystemUpdateManager.KEY_STATUS;
+import static android.os.SystemUpdateManager.KEY_TITLE;
+import static android.os.SystemUpdateManager.STATUS_IDLE;
+import static android.os.SystemUpdateManager.STATUS_WAITING_DOWNLOAD;
+
import android.app.AlarmManager;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
+import android.net.Uri;
+import android.os.BatteryManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.PersistableBundle;
+import android.os.StatFs;
import android.os.SystemProperties;
+import android.os.SystemUpdateManager;
import android.os.storage.StorageManager;
+import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
@@ -46,18 +63,26 @@ import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
+import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Enumeration;
-import java.util.HashSet;
import java.util.List;
import java.util.Locale;
-import java.util.Set;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class Utils {
+ private static final int BATTERY_PLUGGED_ANY = BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS;
+
private static final String TAG = "Utils";
+ private static final String CONTENT_URI_PATH = "content://custom.setting.Provider.OTA_SERVER/cte";
private Utils() {
}
@@ -81,6 +106,11 @@ public class Utils {
return new File(context.getCacheDir(), "updates.json");
}
+ public static String generateRandomID() {
+ String uuid = UUID.randomUUID().toString().replace("-", "");
+ return "anon" + uuid;
+ }
+
// This should really return an UpdateBaseInfo object, but currently this only
// used to initialize UpdateInfo objects
private static UpdateInfo parseJsonUpdate(JSONObject object) throws JSONException {
@@ -92,16 +122,48 @@ public class Utils {
update.setFileSize(object.getLong("size"));
update.setDownloadUrl(object.getString("url"));
update.setVersion(object.getString("version"));
+ update.setAndroidVersion(object.getString("android_version"));
+ if (object.has("pre_version") && !object.getString("pre_version").isEmpty()) {
+ update.setDisplayVersion(object.getString("version") + "-" + object.getString("pre_version"));
+ } else {
+ update.setDisplayVersion(object.getString("version"));
+ }
return update;
}
+ public static int parseAndroidVersion(String versionString) {
+ // Parse android versions such as 8.1.0.
+ // Older updates still shows in ota requests.
+ Pattern pattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)");
+ Matcher matcher = pattern.matcher(versionString);
+ return matcher.matches() ? Integer.parseInt(Objects.requireNonNull(matcher.group(1)))
+ : Float.valueOf(versionString).intValue();
+ }
+
public static boolean isCompatible(UpdateBaseInfo update) {
- if (update.getVersion().compareTo(SystemProperties.get(Constants.PROP_BUILD_VERSION)) < 0) {
- Log.d(TAG, update.getName() + " is older than current Android version");
- return false;
+ String updateAndroidVersion = update.getAndroidVersion();
+ if (!updateAndroidVersion.isEmpty()) {
+ final int updateOSVersion = parseAndroidVersion(updateAndroidVersion);
+ final int deviceOSVersion = parseAndroidVersion(Build.VERSION.RELEASE);
+ if (deviceOSVersion > updateOSVersion) {
+ Log.d(TAG, "Update : Skipping " + update.getName() + " since the installed version "
+ + deviceOSVersion + " is newer than update " + updateOSVersion);
+ return false;
+ }
}
+
+ int[] updateVersionParts = parseSemVer(update.getVersion());
+ int updateMajorVersion = updateVersionParts[0];
+ int updateMinorVersion = updateVersionParts[1];
+ Log.d(TAG, "Update : Major "+updateMajorVersion +" Minor "+ updateMinorVersion );
+
+ int[] deviceVersionParts = parseSemVer(SystemProperties.get(Constants.PROP_BUILD_VERSION));
+ int deviceMajorVersion = deviceVersionParts[0];
+ int deviceMinorVersion = deviceVersionParts[1];
+ Log.d(TAG, "Device : Major "+ deviceMajorVersion +" Minor "+ deviceMinorVersion );
+
if (!SystemProperties.getBoolean(Constants.PROP_UPDATER_ALLOW_DOWNGRADING, false) &&
- update.getTimestamp() <= SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0)) {
+ update.getTimestamp() <= SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0)) {
Log.d(TAG, update.getName() + " is older than/equal to the current build");
return false;
}
@@ -109,14 +171,46 @@ public class Utils {
Log.d(TAG, update.getName() + " has type " + update.getType());
return false;
}
+ if(updateMajorVersion > deviceMajorVersion){
+ Log.d(TAG, update.getName() + " is Newer to current Major version");
+ return true;
+ }
+ if(updateMajorVersion < deviceMajorVersion){
+ Log.d(TAG, update.getName() + " is Older to current Major version");
+ return false;
+ }
+ if(updateMinorVersion < deviceMinorVersion){
+ Log.d(TAG, update.getName() + " is Older to current Minor version");
+ return false;
+ }
+
return true;
}
+ public static int[] parseSemVer(String versionCode) {
+ String[] versionParts = versionCode.split(Pattern.quote("."));
+ int major = Integer.parseInt(versionParts[0]);
+ int minor = Integer.parseInt(versionParts[1]);
+ return new int[]{ major, minor };
+ }
+
+ public static long availableFreeSpace() {
+ StatFs stats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
+ return stats.getAvailableBlocksLong() * stats.getBlockSizeLong();
+ }
+
+ // https://stackoverflow.com/a/28527441
+ public static String getFileSize(long size) {
+ if (size <= 0)
+ return "0";
+ final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" };
+ int digitGroups = (int) (Math.log10(size) / Math.log10(1024));
+ return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups];
+ }
+
public static boolean canInstall(UpdateBaseInfo update) {
return (SystemProperties.getBoolean(Constants.PROP_UPDATER_ALLOW_DOWNGRADING, false) ||
- update.getTimestamp() > SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0)) &&
- update.getVersion().equalsIgnoreCase(
- SystemProperties.get(Constants.PROP_BUILD_VERSION));
+ update.getTimestamp() > SystemProperties.getLong(Constants.PROP_BUILD_DATE, 0));
}
public static List parseJson(File file, boolean compatibleOnly)
@@ -137,6 +231,12 @@ public class Utils {
continue;
}
try {
+ boolean isValidated = JsonValidator.validateResponseObject(
+ updatesList.getJSONObject(i));
+ if (!isValidated) {
+ Log.d(TAG, "Ignoring incompatible update");
+ continue;
+ }
UpdateInfo update = parseJsonUpdate(updatesList.getJSONObject(i));
if (!compatibleOnly || isCompatible(update)) {
updates.add(update);
@@ -156,17 +256,48 @@ public class Utils {
String device = SystemProperties.get(Constants.PROP_NEXT_DEVICE,
SystemProperties.get(Constants.PROP_DEVICE));
String type = SystemProperties.get(Constants.PROP_RELEASE_TYPE).toLowerCase(Locale.ROOT);
+ String anonHash = Settings.Secure.getString(context.getContentResolver(),
+ Settings.Secure.OTA_ANON_HASH);
String serverUrl = SystemProperties.get(Constants.PROP_UPDATER_URI);
+ if (retrieveStatus(context) != null && retrieveStatus(context).equals("true")
+ && isDevModeOn(context)) {
+ serverUrl = context.getString(R.string.ota_staging_server_url);
+ }
+
if (serverUrl.trim().isEmpty()) {
serverUrl = context.getString(R.string.updater_server_url);
}
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean allUpdates = preferences.getBoolean(Constants.PREF_ALL_UPDATES, false);
+ if (Utils.isDevModeOn(context)) {
+ allUpdates = true;
+ }
+
+ if (anonHash != null && !anonHash.isEmpty()) {
+ serverUrl += "?ota_anon_hash=" + anonHash + "&strict=" + !allUpdates;
+ } else {
+ serverUrl += "?strict=" + !allUpdates;
+ }
+
return serverUrl.replace("{device}", device)
.replace("{type}", type)
.replace("{incr}", incrementalVersion);
}
+ /*get the status from database that ota option is on or off*/
+ public static String retrieveStatus(Context context) {
+ String status = null;
+ Cursor cursor = context.getContentResolver().query(Uri.parse(CONTENT_URI_PATH), null, "id=?", new String[]{"1"}, "Status");
+ if (cursor.moveToFirst()) {
+ do {
+ status = cursor.getString(cursor.getColumnIndex("Status"));
+ } while (cursor.moveToNext());
+ }
+ return status;
+ }
+
public static String getUpgradeBlockedURL(Context context) {
String device = SystemProperties.get(Constants.PROP_NEXT_DEVICE,
SystemProperties.get(Constants.PROP_DEVICE));
@@ -174,9 +305,8 @@ public class Utils {
}
public static String getChangelogURL(Context context) {
- String device = SystemProperties.get(Constants.PROP_NEXT_DEVICE,
- SystemProperties.get(Constants.PROP_DEVICE));
- return context.getString(R.string.menu_changelog_url, device);
+ String buildVersion = SystemProperties.get(Constants.PROP_BUILD_VERSION);
+ return context.getString(R.string.menu_changelog_url, buildVersion);
}
public static void triggerUpdate(Context context, String downloadId) {
@@ -210,26 +340,47 @@ public class Utils {
/**
* Compares two json formatted updates list files
*
- * @param oldJson old update list
* @param newJson new update list
- * @return true if newJson has at least a compatible update not available in oldJson
+ * @return true if newJson has an update with higher version than the installed system
*/
- public static boolean checkForNewUpdates(File oldJson, File newJson)
+ public static boolean checkForNewUpdates(File newJson, Context context)
throws IOException, JSONException {
- List oldList = parseJson(oldJson, true);
List newList = parseJson(newJson, true);
- Set oldIds = new HashSet<>();
- for (UpdateInfo update : oldList) {
- oldIds.add(update.getDownloadId());
- }
- // In case of no new updates, the old list should
- // have all (if not more) the updates
+ int[] deviceVersionParts = parseSemVer(SystemProperties.get(Constants.PROP_BUILD_VERSION));
+ int deviceMajorVersion = deviceVersionParts[0];
+ int deviceMinorVersion = deviceVersionParts[1];
+ int deviceMaintenanceVersion = deviceVersionParts.length > 2 ? deviceVersionParts[2] : 0;
+ int highestMajorVersion = deviceMajorVersion;
+ int highestMinorVersion = deviceMinorVersion;
+ int highestMaintenanceVersion = deviceMaintenanceVersion;
+ boolean hasUpdate = false;
for (UpdateInfo update : newList) {
- if (!oldIds.contains(update.getDownloadId())) {
- return true;
+ if (isCompatible(update)) {
+ Log.d(TAG, "New compatible update available");
+ int[] updateVersionParts = parseSemVer(update.getVersion());
+ int updateMajorVersion = updateVersionParts[0];
+ int updateMinorVersion = updateVersionParts[1];
+ int updateMaintenanceVersion = updateVersionParts.length > 2
+ ? updateVersionParts[2] : 0;
+ if (updateMajorVersion * 10000 + updateMinorVersion * 100 + updateMaintenanceVersion
+ >= highestMajorVersion * 10000 + highestMinorVersion * 100
+ + highestMaintenanceVersion) {
+ highestMajorVersion = updateMajorVersion;
+ highestMinorVersion = updateMinorVersion;
+ highestMaintenanceVersion = updateMaintenanceVersion;
+ }
+ hasUpdate = true;
}
}
- return false;
+ String updateVersion = highestMajorVersion + "." + highestMinorVersion +
+ (highestMaintenanceVersion > 0 ? "." + highestMaintenanceVersion : "");
+ if (hasUpdate) {
+ updateSystemUpdaterService(context, STATUS_WAITING_DOWNLOAD, updateVersion);
+ return true;
+ } else {
+ updateSystemUpdaterService(context, STATUS_IDLE, updateVersion);
+ return false;
+ }
}
/**
@@ -290,7 +441,7 @@ public class Utils {
long prevTimestamp = preferences.getLong(Constants.PREF_INSTALL_OLD_TIMESTAMP, 0);
String lastUpdatePath = preferences.getString(Constants.PREF_INSTALL_PACKAGE_PATH, null);
boolean reinstalling = preferences.getBoolean(Constants.PREF_INSTALL_AGAIN, false);
- boolean deleteUpdates = preferences.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, false);
+ boolean deleteUpdates = preferences.getBoolean(Constants.PREF_AUTO_DELETE_UPDATES, true);
if ((buildTimestamp != prevTimestamp || reinstalling) && deleteUpdates &&
lastUpdatePath != null) {
File lastUpdate = new File(lastUpdatePath);
@@ -370,6 +521,11 @@ public class Utils {
return isAB;
}
+ public static boolean isDevModeOn(Context context) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED , 0) == 1;
+ }
+
public static boolean hasTouchscreen(Context context) {
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);
}
@@ -391,7 +547,7 @@ public class Utils {
public static int getUpdateCheckSetting(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getInt(Constants.PREF_AUTO_UPDATES_CHECK_INTERVAL,
- Constants.AUTO_UPDATES_CHECK_INTERVAL_WEEKLY);
+ Constants.AUTO_UPDATES_CHECK_INTERVAL_DAILY);
}
public static boolean isUpdateCheckEnabled(Context context) {
@@ -400,6 +556,12 @@ public class Utils {
public static long getUpdateCheckInterval(Context context) {
switch (Utils.getUpdateCheckSetting(context)) {
+ case Constants.AUTO_UPDATES_CHECK_INTERVAL_5_MINUTES:
+ return AlarmManager.INTERVAL_FIFTEEN_MINUTES / 3;
+ case Constants.AUTO_UPDATES_CHECK_INTERVAL_10_MINUTES:
+ return AlarmManager.INTERVAL_HALF_HOUR / 3;
+ case Constants.AUTO_UPDATES_CHECK_INTERVAL_30_MINUTES:
+ return AlarmManager.INTERVAL_HALF_HOUR;
case Constants.AUTO_UPDATES_CHECK_INTERVAL_DAILY:
return AlarmManager.INTERVAL_DAY;
case Constants.AUTO_UPDATES_CHECK_INTERVAL_WEEKLY:
@@ -414,14 +576,41 @@ public class Utils {
return new File(Constants.UPDATE_RECOVERY_EXEC).exists();
}
- public static String getDisplayVersion(String version) {
- float floatVersion = 0;
+ public static boolean isBatteryLevelOk(Context context) {
+ Intent intent = context.registerReceiver(null,
+ new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+ if (!intent.getBooleanExtra(BatteryManager.EXTRA_PRESENT, false)) {
+ return true;
+ }
+ int percent = Math.round(100.f * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100) /
+ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
+ int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ int required = (plugged & BATTERY_PLUGGED_ANY) != 0 ?
+ context.getResources().getInteger(R.integer.battery_ok_percentage_charging) :
+ context.getResources().getInteger(R.integer.battery_ok_percentage_discharging);
+ return percent >= required;
+ }
+
+ public static void updateSystemUpdaterService(Context context, int status, String version) {
+ final SystemUpdateManager updateManager = context.getSystemService(SystemUpdateManager.class);
+
+ final Bundle oldInfo = updateManager.retrieveSystemUpdateInfo();
+ final int oldStatus = oldInfo.getInt(SystemUpdateManager.KEY_STATUS);
+
+ if (status != oldStatus) {
+ PersistableBundle infoBundle = new PersistableBundle();
+ infoBundle.putInt(KEY_STATUS, status);
+ infoBundle.putString(KEY_TITLE, version);
+ updateManager.updateSystemUpdateInfo(infoBundle);
+ }
+ }
+
+ public static boolean isInteger(String value) {
try {
- floatVersion = Float.parseFloat(version);
- } catch (NumberFormatException ignored) {
- // ignore
+ Integer.parseInt(value);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
}
- // Lineage 20 and up should only be integer values (we don't have minor versions anymore)
- return (floatVersion >= 20) ? String.valueOf((int)floatVersion) : version;
}
}
diff --git a/app/src/main/java/org/lineageos/updater/model/UpdateBase.java b/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
index 8fcf09c8f962ec56a7787e42ae0f9754325fb521..167e36295c6a0435e77746ade0d0ddd63dfebdf0 100644
--- a/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
+++ b/app/src/main/java/org/lineageos/updater/model/UpdateBase.java
@@ -23,6 +23,8 @@ public class UpdateBase implements UpdateBaseInfo {
private long mTimestamp;
private String mType;
private String mVersion;
+ private String mDisplayVersion;
+ private String mAndroidVersion;
private long mFileSize;
public UpdateBase() {
@@ -35,6 +37,8 @@ public class UpdateBase implements UpdateBaseInfo {
mTimestamp = update.getTimestamp();
mType = update.getType();
mVersion = update.getVersion();
+ mDisplayVersion = update.getDisplayVersion();
+ mAndroidVersion = update.getAndroidVersion();
mFileSize = update.getFileSize();
}
@@ -83,6 +87,15 @@ public class UpdateBase implements UpdateBaseInfo {
mVersion = version;
}
+ @Override
+ public String getDisplayVersion() {
+ return mDisplayVersion;
+ }
+
+ public void setDisplayVersion(String displayVersion) {
+ mDisplayVersion = displayVersion;
+ }
+
@Override
public String getDownloadUrl() {
return mDownloadUrl;
@@ -100,4 +113,13 @@ public class UpdateBase implements UpdateBaseInfo {
public void setFileSize(long fileSize) {
mFileSize = fileSize;
}
+
+ @Override
+ public String getAndroidVersion() {
+ return mAndroidVersion;
+ }
+
+ public void setAndroidVersion(String androidVersion) {
+ mAndroidVersion = androidVersion;
+ }
}
diff --git a/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java b/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
index 2041582ad0c613431559f54bb4a04fc6d8aae5ba..aea17bd80661f3ce8cd99919c17cad79490d4a80 100644
--- a/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
+++ b/app/src/main/java/org/lineageos/updater/model/UpdateBaseInfo.java
@@ -26,7 +26,11 @@ public interface UpdateBaseInfo {
String getVersion();
+ String getDisplayVersion();
+
String getDownloadUrl();
long getFileSize();
+
+ String getAndroidVersion();
}
diff --git a/app/src/main/res/layout-television/activity_updates.xml b/app/src/main/res/layout-television/activity_updates.xml
index efabfea41ce8a68136e2d69d0c1c982004b3a79a..df63d6c806cb2da120ddd8a5f9d8e2cbd16a6880 100644
--- a/app/src/main/res/layout-television/activity_updates.xml
+++ b/app/src/main/res/layout-television/activity_updates.xml
@@ -105,7 +105,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
- android:text="@string/list_no_updates"
+ android:text="@string/e_list_no_updates"
android:textColor="?android:textColorSecondary" />
diff --git a/app/src/main/res/layout/activity_updates.xml b/app/src/main/res/layout/activity_updates.xml
index adfe1a27147de46995d227924a449f07be561cfa..677282f4b8c22ef694d4f4083e2e7f8bdee578d6 100644
--- a/app/src/main/res/layout/activity_updates.xml
+++ b/app/src/main/res/layout/activity_updates.xml
@@ -48,14 +48,24 @@
android:paddingStart="16dp"
app:layout_collapseMode="parallax">
+
+
@@ -108,17 +117,36 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
- android:text="@string/list_no_updates"
+ android:text="@string/e_list_no_updates"
android:textColor="?android:textColorSecondary" />
-
+ android:orientation="vertical"
+ android:visibility="gone"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+
+
+
+
diff --git a/app/src/main/res/layout/preferences_dialog.xml b/app/src/main/res/layout/preferences_dialog.xml
index c1b435605c5ca5c3cabe089f1b3524cac3d6f1e6..bad8f44a5412f030088f80c35b087709917353d8 100644
--- a/app/src/main/res/layout/preferences_dialog.xml
+++ b/app/src/main/res/layout/preferences_dialog.xml
@@ -25,10 +25,17 @@
android:id="@+id/preferences_auto_updates_check_interval"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:entries="@array/menu_auto_updates_check_interval_entries" />
+ android:layout_weight="1" />
+
+
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
-
-
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:weightSum="1"
+ android:baselineAligned="false">
-
+ android:layout_weight="1"
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
+ android:alpha="0.87"
+ android:drawablePadding="8dp"
+ android:maxLines="1"
+ android:paddingBottom="8sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ tools:text="LineageOS 20" />
-
-
-
-
+ android:maxLines="1"
+ android:textSize="14sp"
+ tools:text="18 June 2023" />
+
-
+ android:layout_below="@id/progress"
+ android:ellipsize="marquee"
+ android:singleLine="true"
+ android:visibility="invisible"
+ tools:text="162 of 1.1 GB (3 minutes left)"
+ tools:visibility="visible" />
+
+
+
+
diff --git a/app/src/main/res/menu/menu_toolbar.xml b/app/src/main/res/menu/menu_toolbar.xml
index dbca3d03319d6c82c543d64f914cc914672e4ffd..9b97c5f96bf144dd866e41fbb677789656a90d91 100644
--- a/app/src/main/res/menu/menu_toolbar.xml
+++ b/app/src/main/res/menu/menu_toolbar.xml
@@ -16,6 +16,6 @@
app:showAsAction="never" />
diff --git a/app/src/main/res/values-de/e_strings.xml b/app/src/main/res/values-de/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..735dd3d999b373cc675282e2c542c8de70f0b0e1
--- /dev/null
+++ b/app/src/main/res/values-de/e_strings.xml
@@ -0,0 +1,55 @@
+
+
+ Auf deinem Telefon läuft die neueste /e/OS-Version. Um manuell nach neuen Aktualisierungen zu suchen, verwende die Schaltfläche „Aktualisieren“.
+ /e/OS
+\n%1$s
+ Versionshinweise anzeigen
+ /e/OS %1$s - %2$s
+ /e/OS %1$s
+ Verfügbare Aktualisierungen:
+ Aktuelle Version:
+ Alle 30 Minuten
+ Alle 10 Minuten
+ Alle 5 Minuten
+ Die Aktualisierung kann nicht abgeschlossen werden, weil nicht genug Speicher frei ist. Mindestens %1$s freier interner Speicher ist nötig, bevor du weitermachst.
+ Platz schaffen
+ Aktualisierung installieren
+ Neustarten und Installieren
+ Aktualisierung ist bereit zur Installation
+ Sie stehen kurz davor, die höhere Version %1$s zu installieren, die auf Android %2$s aufbaut.
+\n
+\nHinweis: Es ist immer eine gute Idee, Ihre Daten vor Versionsänderungen zu sichern.
+\n
+\nWenn Sie %3$s antippen, wird das Gerät in den Wiederherstellungs-Modus neu starten, um die neue Version zu installieren.
+ Sie stehen kurz davor, die Aktualisierung %1$s zu installieren.
+\n
+\nWenn Sie %2$s antippen, wird das Gerät die Aktualisierung im Hintergrund installieren.
+\n
+\nWenn sie abgeschlossen ist, werden Sie zum Neustart aufgefordert.
+ Sie stehen kurz davor, die höhere Version %1$s zu installieren, die auf Android %2$s aufbaut.
+\n
+\nHinweis: Es ist immer eine gute Idee, Ihre Daten vor Versionsänderungen zu sichern.
+\n
+\nWenn Sie %3$s antippen, wird das Gerät die neue Version im Hintergrund installieren.
+\n
+\nWenn sie abgeschlossen ist, werden Sie zum Neustart aufgefordert.
+ Sie stehen kurz davor, die Aktualisierung %1$s zu installieren.
+\n
+\nWenn Sie %2$s antippen, wird das Gerät in den Wiederherstellungs-Modus neu starten, um die Aktualisierung zu installieren.
+ Versionsaufstieg!
+ Alle verfügbaren Aktualisierungen anzeigen
+
diff --git a/app/src/main/res/values-es/e_strings.xml b/app/src/main/res/values-es/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f95bf53a470bfdbcd0272c24840fb853bede2510
--- /dev/null
+++ b/app/src/main/res/values-es/e_strings.xml
@@ -0,0 +1,55 @@
+
+
+ Versión actual:
+ Actualizaciones disponibles:
+ La actualización no puede completarse porque no hay suficiente espacio libre. Debes tener al menos %1$s de espacio libre.
+ Liberar espacio
+ Reiniciar e instalar
+ Cada 5 minutos
+ Cada 10 minutos
+ Cada 30 minutos
+ Estás usando la última versión de /e/OS. Para comprobar manualmente si hay actualizaciones, usa el botón Actualizar.
+ Aplicar actualización
+ Estás a punto de actualizar a %1$s.
+\n
+\nSi pulsas %2$s, el dispositivo se reiniciará en modo de recuperación para instalar la actualización.
+ ¡Actualización disponible!
+ /e/OS %1$s
+ /e/OS %1$s - %2$s
+ Mostrar notas de publicación
+ /e/OS
+\n%1$s
+ Estás a punto de actualizar a %1$s.
+\n
+\nSi pulsas %2$s, el dispositivo comenzará la instalación en segundo plano.
+\n
+\nUna vez completada, se te pedirá que reinicies.
+ Ver todas las actualizaciones disponibles
+ Actualización lista para instalar
+ Estás a punto de actualizar a %1$s, basado en Android %2$s.
+\n
+\nRecuerda: siempre es recomendable hacer una copia de seguridad de tus datos antes de actualizar.
+\n
+\nSi pulsas %3$s, el dispositivo comenzará la instalación en segundo plano.
+\n
+\nUna vez completada, se te pedirá que reinicies.
+ Estás a punto de actualizar a %1$s, basado en Android %2$s.
+\n
+\nRecuerda: siempre es recomendable hacer una copia de seguridad de tus datos antes de actualizar.
+\n
+\nSi pulsas %3$s, el dispositivo se reiniciará en modo de recuperación para instalar la actualización.
+
diff --git a/app/src/main/res/values-fr/e_strings.xml b/app/src/main/res/values-fr/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4732b7a3d149eb3cf1bc5b78696a54743b7061b5
--- /dev/null
+++ b/app/src/main/res/values-fr/e_strings.xml
@@ -0,0 +1,55 @@
+
+
+ Vous utilisez actuellement la dernière version de /e/OS. Pour vérifier manuellement la présence de mises à jour, utilisez le bouton Actualiser.
+ Version actuelle :
+ /e/OS
+\n%1$s
+ /e/OS %1$s - %2$s
+ /e/OS %1$s
+ Toutes les 30 minutes
+ Toutes les 10 minutes
+ Toutes les 5 minutes
+ L\'espace disponible n\'est pas suffisant pour installer la mise à jour : merci de libérer au moins %1$s de mémoire interne pour pouvoir continuer.
+ Libérez de l\'espace
+ Afficher les notes de version
+ Mises à jour disponibles :
+ La mise à jour est prête à être installée
+ Vous êtes sur le point d\'effectuer une montée de version vers %1$s, basée sur Android %2$s
+\n
+\nRappel : il est recommandé de toujours effectuer une sauvegarde de vos données avant d\'effectuer une montée de version.
+\n
+\nQuand vous appuierez sur %3$s, l\'appareil redémarrera en mode récupération (recovery) pour installer la mise à jour.
+ Vous êtes sur le point de mettre à jour votre appareil vers %1$s.
+\n
+\nQuand vous appuierez sur %2$s, l\'appareil commencera l\'installation en arrière plan.
+\n
+\nUne fois terminée, il vous sera demandé de redémarrer.
+ Vous êtes sur le point d\'effectuer une montée de version vers %1$s, basée sur Android %2$s
+\n
+\nRappel : il est recommandé de toujours effectuer une sauvegarde de vos données avant d\'effectuer une montée de version.
+\n
+\nQuand vous appuierez sur %3$s, l\'appareil commencera l\'installation en arrière plan.
+\n
+\nUne fois terminée, il vous sera demandé de redémarrer.
+ Redémarrer et installer
+ Vous êtes sur le point de mettre à jour votre appareil vers %1$s.
+\n
+\nQuand vous appuierez sur %2$s, l\'appareil redémarrera en mode récupération (recovery) pour installer la mise à jour.
+ Appliquer la mise à jour
+ Montée de version !
+ Voir toutes les mises à jour disponibles
+
diff --git a/app/src/main/res/values-is/e_strings.xml b/app/src/main/res/values-is/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2ff02c4e7dbb4e9c8b05b61b6354c1e693187770
--- /dev/null
+++ b/app/src/main/res/values-is/e_strings.xml
@@ -0,0 +1,55 @@
+
+
+ Uppfærsla er tilbúin til uppsetningar
+ Endurræsa og setja upp
+ Losa geymslupláss
+ Það er ekki nægilegt pláss eftir til að halda áfram með uppfærsluna: þú þarft fyrst að losa um %1$s í innri gagnageymslunni.
+ Á 5 mínútna fresti
+ Á 10 mínútna fresti
+ Á 30 mínútna fresti
+ Þú ert þegar að keyra nýjustu útgáfu /e/OS stýrikerfisins. Til að athuga handvirkt með nýjar uppfærslur, skaltu nota Endurlesa-hnappinn.
+ Setja inn uppfærslu
+ Þú ert í þann mund að setja upp %1$s.
+\n
+\nEf þú ýtir á %2$s, mun tækið endurræsa sig í endurheimtuham til að setja inn uppfærsluna.
+ Þú ert í þann mund að setja upp %1$s.
+\n
+\nEf þú ýtir á %2$s, mun tækið hefja uppsetninguna í bakgrunnsferli.
+\n
+\nÞegar henni er lokið færðu beiðni um að endurræsa.
+ /e/OS %1$s
+ /e/OS %1$s - %2$s
+ Birta útgáfuupplýsingar
+ /e/OS
+\n%1$s
+ Tiltækar uppfærslur:
+ Núverandi útgáfa:
+ Uppfærsla útgáfu!
+ Þú ert í þann mund að uppfæra kerfið í %1$s, byggt á Android %2$s
+\n
+\nMundu: alltaf er mælt með því að þú takir öryggisafrit af gögnunum þínum áður en uppfærsla fer fram.
+\n
+\nEf þú ýtir á %3$s, mun tækið endurræsast í endurheimtuham til að setja inn uppfærsluna.
+ Þú ert í þann mund að uppfæra kerfið í %1$s, byggt á Android %2$s
+\n
+\nMundu: alltaf er mælt með því að þú takir öryggisafrit af gögnunum þínum áður en uppfærsla fer fram.
+\n
+\nEf þú ýtir á %3$s, mun tækið byrja að setja inn uppfærsluna í bakgrunni.
+\n
+\nÞegar því er lokið, muntu fá boð um að endurræsa.
+ Sjá allar tiltækar uppfærslur
+
diff --git a/app/src/main/res/values-it/e_strings.xml b/app/src/main/res/values-it/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4a202100d0efc7823b64835a9f57c262e6e27996
--- /dev/null
+++ b/app/src/main/res/values-it/e_strings.xml
@@ -0,0 +1,37 @@
+
+
+ Stai usando l\'ultima versione di /e/OS. Per verificare manualmente la presenza di aggiornamenti, utilizza il pulsante Aggiorna.
+ /e/OS
+\n%1$s
+ Mostra note di rilascio
+ /e/OS %1$s - %2$s
+ /e/OS %1$s
+ Ogni 30 minuti
+ Ogni 10 minuti
+ Ogni 5 minuti
+ Versione attuale:
+ Agiornamenti disponibili:
+ Non c\'è spazio sufficiente per procedere con l\'aggiornamento; devi liberare almeno altri %1$s dalla memoria interna.
+ Libera spazio
+ Riavvia e installa
+ Applica aggiornamento
+ Vedere tutti gli aggiornamenti disponibili
+ Stai per aggiornare a %1$s.
+\n
+\nPigiando %2$s, il dispositivo si riavvierà nella modalità ripristino per installare l\'aggiornamento.
+ L\'aggiornamento è pronto per essere installato
+
diff --git a/app/src/main/res/values-nl/e_strings.xml b/app/src/main/res/values-nl/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fd7af226c903a236f1f2aebe6adbd3ff91110c0d
--- /dev/null
+++ b/app/src/main/res/values-nl/e_strings.xml
@@ -0,0 +1,55 @@
+
+
+ Je gebruikt de laatste versie van /e/OS. Gebruik de knop Vernieuwen om handmatig te controleren op nieuwe updates.
+ Beschikbare updates:
+ /e/OS
+\n%1$s
+ Toon release notes
+ /e/OS %1$s - %2$s
+ /e/OS %1$s
+ Huidige versie:
+ Elke 30 minuten
+ Elke 10 minuten
+ Elke 5 minuten
+ Er is niet genoeg geheugen vrij om deze update uit te voeren: je moet eerst nog %1$s intern geheugen vrijmaken.
+ Geheugen vrij maken
+ Update uitvoeren
+ Herstarten en installeren
+ Update is klaar om geïnstalleerd te worden
+ Je gaat upgraden naar %1$s, gebaseerd op Android %2$s
+\n
+\nOpmerking: het wordt altijd aangeraden een back-up te nemen van je data voor dat je een upgrade start.
+\n
+\nWanneer je tikt op %3$s, zal het toestel herstarten in \'recovery modus\' om de installatie uit te voeren.
+ Je gaat updaten naar %1$s.
+\n
+\nAls je tikt op %2$s, zal het toestel de installatie starten op de achtergrond.
+\n
+\nWanneer dit voltooid is, zal je gevraagd worden om te herstarten.
+ Je gaat upgraden naar %1$s, gebaseerd op Android %2$s
+\n
+\nOpmerking: het wordt altijd aangeraden een back-up te nemen van je data voor dat je een upgrade start.
+\n
+\nWanneer je tikt op %3$s, zal het toestel de upgrade installeren op de achtergrond.
+\n
+\nWanneer dit voltooid is, zal je gevraagd worden om te herstarten.
+ Je gaat updaten naar %1$s.
+\n
+\nAls je tikt op %2$s, zal het toestel herstarten in \'recovery mode\' om de update te installeren.
+ Versie Upgrade!
+ Alle beschikbare updates weergeven
+
diff --git a/app/src/main/res/values-ru/e_strings.xml b/app/src/main/res/values-ru/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d07607a17dfd0b5239e2ded85cd126ca35cfd6f
--- /dev/null
+++ b/app/src/main/res/values-ru/e_strings.xml
@@ -0,0 +1,33 @@
+
+
+
+ Вы используете последнюю версию /e/OS. Чтобы проверить наличие обновлений вручную, используйте кнопку Обновить.
+ /e/OS
+\n%1$s
+ Показать примечания к выпуску
+ /e/OS %1$s - %2$s
+ /e/OS %1$s
+ Каждые 30 минут
+ Каждые 10 минут
+ Каждые 5 минут
+ Текущая версия:
+ Доступные обновления:
+ Для продолжения обновления недостаточно места: сначала необходимо освободить еще %1$s во внутреннем хранилище.
+ Освободить место
+ Применить обновление
+ Перезагрузиться и установить
+
diff --git a/app/src/main/res/values-sv/e_strings.xml b/app/src/main/res/values-sv/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2881f06239a07bb6653725216b828046d1715ebe
--- /dev/null
+++ b/app/src/main/res/values-sv/e_strings.xml
@@ -0,0 +1,41 @@
+
+
+ Var 5:e minut
+ Uppdatering är redo att installeras
+ Var 10:e minut
+ Du är på väg att uppgradera till %1$s, baserad på Android %2$s
+\n
+\nKom ihåg: det rekommenderas alltid att göra en säkerhetskopiering av din data innan uppgraderingar.
+\n
+\nOm du trycker på %3$s, kommer din enhet att starta om i återställningsläge och installera uppgraderingen.
+ Du är på väg att uppdatera %1$s.
+\n
+\nOm du trycker på %2$s så kommer enheten att påbörja installationen i bakgrunden.
+\n
+\nNär den är slutförd kommer du bli ombedd att starta om.
+ /e/OS
+\n%1$s
+ Du är på väg att uppgradera till %1$s, baserad på Android %2$s.
+\n
+\nKom ihåg: det är alltid rekommenderat att göra en säkerhetskopia av din data innan uppgraderingar.
+\n
+\nOm du trycker på %3$s, kommer enheten att påbörja installation i bakgrunden.
+\n
+\nNär det är färdigt kommer du bli uppmanad att starta om.
+ Var 30:e minut
+ Starta om och installera
+ Tillgängliga uppdateringar:
+ Du är på väg att uppdatera till %1$s.
+\n
+\nOm du trycker på %2$s, kommer enheten att starta om i återställningsläge och installera uppdateringen.
+ Visa utgåvonoteringar
+ Aktuell version:
+ Tillämpa uppdatering
+ Versionsuppgradering!
+ Du kör den senaste versionen av /e/OS. För att söka efter uppdateringar manuellt, använd Uppdateringsknappen.
+ /e/OS %1$s
+ Frigör utrymme
+ /e/OS %1$s - %2$s
+ Det finns inte tillräckligt med ledigt utrymme för att fortsätta med uppdateringen. Du måste frigöra ytterligare %1$s i din interna lagring först.
+ Visa alla tillgängliga uppdateringar
+
diff --git a/app/src/main/res/values-tr/e_strings.xml b/app/src/main/res/values-tr/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d3652624ff9ad426ed1a8daa8a63c4393d6eb2cf
--- /dev/null
+++ b/app/src/main/res/values-tr/e_strings.xml
@@ -0,0 +1,41 @@
+
+
+ Güncelleme yükleme için hazır
+ Yeniden başlat ve yükle
+ Cihazda yer açın
+ 5 dakikada bir
+ 10 dakikada bir
+ Yarım saatte bir
+ En son /e/OS sürümünü çalıştırıyorsunuz. Güncellemeleri manuel olarak kontrol etmek için Yenile düğmesini kullanın.
+ Güncellemeyi yükle
+ %1$s güncellemesini yapmak üzeresiniz.
+\n
+\n%2$s tuşuna basarsanız, cihaz arka planda yüklemeye başlayacaktır.
+\n
+\nTamamlandığında, yeniden başlatmanız istenecektir.
+ Sürüm Güncellemesi!
+ Android %1$s sürümüne yükseltmek üzeresiniz %2$s.
+\n
+\nUnutmayın: yükseltmelerden önce her zaman verilerinizi yedeklemeniz önerilir.
+\n
+\n%3$s tuşuna basarsanız, cihaz arka planda yüklemeye başlayacaktır.
+\n
+\nTamamlandığında, yeniden başlatmanız istenecektir.
+ /e/OS %1$s
+ /e/OS %1$s - %2$s
+ Güncelleme notlarını göster
+ /e/OS
+\n%1$s
+ Android %1$s sürümüne yükseltmek üzeresiniz %2$s
+\n
+\nUnutmayın: yükseltmelerden önce her zaman verilerinizi yedeklemeniz önerilir.
+\n
+\n%3$s tuşuna basarsanız, cihaz güncellemeyi yüklemek için kendini kurtarma modunda yeniden başlatacaktır.
+ %1$s güncellemesini yapmak üzeresiniz.
+\n
+\n%2$s tuşuna basarsanız, cihaz güncellemeyi yüklemek için kendini kurtarma modunda yeniden başlatacaktır.
+ Güncellemeye devam etmek için yeterli alan kalmadı: önce dahili depolama alanında başka bir %1$s boşaltmalısınız.
+ Mevcut güncellemeler:
+ Mevcut sürüm:
+ Mevcut tüm güncellemeleri görün
+
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/e_strings.xml b/app/src/main/res/values-uk/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..083c905f23c8b35e1d9c594b0d946aebeece4d93
--- /dev/null
+++ b/app/src/main/res/values-uk/e_strings.xml
@@ -0,0 +1,40 @@
+
+
+ Ви збираєтеся оновитися до %1$s.
+\n
+\nЯкщо ви натиснете %2$s, пристрій перезавантажиться у режим Recovery для встановлення оновлення.
+ Кожні 5 хвилин
+ Оновлення готове до встановлення
+ Кожні 10 хвилин
+ Ви збираєтеся оновитися до %1$s, на базі Android %2$s
+\n
+\nПам\'ятайте: завжди рекомендується створювати резервні копії даних перед оновленням.
+\n
+\nЯкщо ви натиснете %3$s, пристрій перезапуститься у режим Recovery для встановлення оновлення.
+ Ви збираєтеся оновитися до %1$s.
+\n
+\nЯкщо ви натиснете %2$s, пристрій почне встановлення у фоновому режимі.
+\n
+\nПісля завершення вам буде запропоновано перезавантажитися.
+ /e/OS
+\n%1$s
+ Ви збираєтеся оновитися до %1$s, на базі Android %2$s.
+\n
+\nПам\'ятайте: завжди рекомендується створювати резервні копії даних перед оновленням.
+\n
+\nЯкщо ви натиснете %3$s, пристрій почне встановлення у фоновому режимі.
+\n
+\nПісля завершення вам буде запропоновано перезавантажитися.
+ Кожні 30 хвилин
+ Перезавантажити та встановити
+ Доступні оновлення:
+ Показати примітки до випуску
+ Поточна версія:
+ Застосувати оновлення
+ Оновлення версії!
+ Ви використовуєте останню версію /e/OS. Щоб вручну перевірити наявність оновлень, скористайтеся кнопкою Оновити.
+ /e/OS %1$s
+ Звільніть місце
+ /e/OS %1$s - %2$s
+ Для продовження оновлення залишилося недостатньо місця: спочатку потрібно звільнити ще %1$s у внутрішньому сховищі.
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 287c221c07d0919b297bbcbd3c2c778ef220b492..3374dccf6168beae200dde3b215f169b6b7ef063 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -21,4 +21,10 @@
@string/menu_auto_updates_check_interval_weekly@string/menu_auto_updates_check_interval_monthly
+
+
+ @string/e_menu_auto_updates_check_interval_5_minutes
+ @string/e_menu_auto_updates_check_interval_10_minutes
+ @string/e_menu_auto_updates_check_interval_30_minutes
+
diff --git a/app/src/main/res/values/config.xml b/app/src/main/res/values/config.xml
index 76ab68622f0142b433f48c2e117175ef348f70ec..16ab670ff81a5f35f992790d5f80524ca7c86bd2 100644
--- a/app/src/main/res/values/config.xml
+++ b/app/src/main/res/values/config.xml
@@ -16,4 +16,5 @@
-->
false
+ false
diff --git a/app/src/main/res/values/e_strings.xml b/app/src/main/res/values/e_strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2b605729a611b1d58a0a492023afefb5de5360a7
--- /dev/null
+++ b/app/src/main/res/values/e_strings.xml
@@ -0,0 +1,39 @@
+
+
+
+ Update is ready to be installed
+ Reboot and install
+ Free up space
+ There is not enough space left to proceed with the update: you must free up another %1$s in the internal storage first.
+ Every 5 minutes
+ Every 10 minutes
+ Every 30 minutes
+ You are running the latest /e/OS version. To manually check for updates, use the Refresh button.
+ Apply update
+ You are about to update to %1$s.\n\nIf you press %2$s, the device will restart itself in recovery mode to install the update.
+ You are about to update to %1$s.\n\nIf you press %2$s, the device will begin installing in the background.\n\nOnce completed, you will be prompted to reboot.
+ Version Upgrade!
+ You are about to upgrade to %1$s, based on Android %2$s\n\nRemember: it is always recommended to backup your data before upgrades.\n\nIf you press %3$s, the device will restart itself into recovery mode to install the update.
+ You are about to upgrade to %1$s, based on Android %2$s.\n\nRemember: it is always recommended to backup your data before upgrades.\n\nIf you press %3$s, the device will begin installing in the background.\n\nOnce completed, you will be prompted to reboot.
+ /e/OS %1$s
+ /e/OS %1$s - %2$s
+ Show release notes
+ /e/OS\n%1$s
+ Available updates:
+ Current version:
+ See all available updates
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 744d46f15a0c718cff786a86f9513937dede35a5..85695efefbe064136fe914a8ff5316e79f91e65b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,7 +32,8 @@
{type} - Build type
{incr} - Incremental version
-->
- https://download.lineageos.org/api/v1/{device}/{type}/{incr}
+ https://ota.ecloud.global/api/v1/{device}/{type}/{incr}
+ https://test.ota.ecloud.global/api/v1/{device}/{type}/{incr}Verification failedVerifying update
@@ -69,7 +70,6 @@
]]>
Cannot install update with OverlayFS mounted
-
RebootRefresh
@@ -84,7 +84,7 @@
Copy URLExport updateShow changelog
- https://download.lineageos.org/%1$s/changes
+ https://gitlab.e.foundation/e/os/releases/-/releases/v%1$s-sPrioritize update processUpdate recoveryIt is impossible to disable Lineage Recovery updates on this device.
diff --git a/app/src/main/res/values/symbols.xml b/app/src/main/res/values/symbols.xml
index f3dc0ba394f8ae3026399bde98b72d154829af7a..992614c0cba99474236ec7ff0838753e84f53e0f 100644
--- a/app/src/main/res/values/symbols.xml
+++ b/app/src/main/res/values/symbols.xml
@@ -16,4 +16,5 @@
-->
+