fix: use DownloadManager for OTA packages

This commit is contained in:
TCFDONMEZ
2026-09-30 11:02:08 +03:00
parent e091704990
commit e8c9122fdf
4 changed files with 61 additions and 55 deletions
+7
View File
@@ -4,6 +4,13 @@ All notable changes to this project are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.17] - 2026-09-30
### Fixed
- Replaced the custom OTA downloader with DriveHub_Dort's Android DownloadManager flow,
then stages and verifies the downloaded bytes before installation.
## [2.2.16] - 2026-09-30 ## [2.2.16] - 2026-09-30
### Added ### Added
+1
View File
@@ -0,0 +1 @@
fa9649f4559b590f4514556805d95e2accc9f95298e8933c2a4f7e7af1ad075d FatihsMG4-unstable-2.2.17.apk
+2 -2
View File
@@ -34,8 +34,8 @@ android {
applicationId "com.evsuite.abrp" applicationId "com.evsuite.abrp"
minSdk 27 minSdk 27
targetSdk 36 targetSdk 36
versionCode 33 versionCode 34
versionName "2.2.16" versionName "2.2.17"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@@ -1,14 +1,17 @@
package com.evsuite.abrp; package com.evsuite.abrp;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.DownloadManager;
import android.content.ClipData; import android.content.ClipData;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInstaller; import android.content.pm.PackageInstaller;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Environment; import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.Settings; import android.provider.Settings;
import android.util.Log; import android.util.Log;
@@ -346,64 +349,59 @@ final class OtaUpdater {
Log.w(TAG, "Refusing to download from " + update.apkUrl); Log.w(TAG, "Refusing to download from " + update.apkUrl);
return null; return null;
} }
File target = new File(context.getCacheDir(), CACHE_PREFIX DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
+ java.util.UUID.randomUUID() + ".apk"); if (manager == null) return null;
File temporary = null; String publicName = downloadFileName(update.versionName);
File publicFile = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DOWNLOADS), publicName);
if (publicFile.exists() && !publicFile.delete()) return null;
long downloadId = -1L;
try { try {
temporary = File.createTempFile(CACHE_PREFIX, ".apk", context.getCacheDir()); DownloadManager.Request request = new DownloadManager.Request(Uri.parse(update.apkUrl))
URL current = new URL(update.apkUrl); .setTitle("EVABRPUploader " + update.versionName)
for (int redirects = 0; redirects <= 5; redirects++) { .setDescription("Güncelleme indiriliyor")
if (!isAllowedUrl(current.toString())) return null; .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
HttpURLConnection connection = (HttpURLConnection) current.openConnection(); .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, publicName)
connection.setInstanceFollowRedirects(false); .setAllowedOverMetered(true)
connection.setConnectTimeout(TIMEOUT_MS); .setAllowedOverRoaming(true);
connection.setReadTimeout(TIMEOUT_MS); downloadId = manager.enqueue(request);
try { while (true) {
int status = connection.getResponseCode(); try (Cursor cursor = manager.query(new DownloadManager.Query().setFilterById(downloadId))) {
if (status >= 300 && status <= 399) { if (cursor == null || !cursor.moveToFirst()) return null;
String location = connection.getHeaderField("Location"); int status = cursor.getInt(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS));
if (location == null) return null; long received = cursor.getLong(cursor.getColumnIndexOrThrow(
current = current.toURI().resolve(location).toURL(); DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
if (!isAllowedUrl(current.toString())) return null; long total = cursor.getLong(cursor.getColumnIndexOrThrow(
continue; DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
if (listener != null) listener.onProgress(total > 0
? (int) Math.min(100, received * 100 / total) : -1);
if (status == DownloadManager.STATUS_SUCCESSFUL) break;
if (status == DownloadManager.STATUS_FAILED) return null;
} }
if (status != HttpURLConnection.HTTP_OK) return null; Thread.sleep(250L);
}
File staged = new File(context.getCacheDir(), CACHE_PREFIX
+ java.util.UUID.randomUUID() + ".apk");
MessageDigest digest = MessageDigest.getInstance("SHA-256"); MessageDigest digest = MessageDigest.getInstance("SHA-256");
long total = connection.getContentLengthLong(); try (ParcelFileDescriptor descriptor = manager.openDownloadedFile(downloadId);
long received = 0; java.io.InputStream input = new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
int lastPercent = -1; FileOutputStream output = new FileOutputStream(staged)) {
try (FileOutputStream output = new FileOutputStream(temporary)) { byte[] buffer = new byte[64 * 1024];
try (java.io.InputStream input = connection.getInputStream()) {
byte[] buffer = new byte[32 * 1024];
int count; int count;
while ((count = input.read(buffer)) != -1) { while ((count = input.read(buffer)) != -1) {
output.write(buffer, 0, count); output.write(buffer, 0, count);
digest.update(buffer, 0, count); digest.update(buffer, 0, count);
received += count;
int percent = total > 0 ? (int) Math.min(100, received * 100 / total) : -1;
if (listener != null && percent != lastPercent) {
listener.onProgress(percent);
lastPercent = percent;
}
}
} }
output.getFD().sync(); output.getFD().sync();
} }
String actual = hex(digest.digest()); if (!hex(digest.digest()).equalsIgnoreCase(expectedSha256)) {
if (!actual.equalsIgnoreCase(expectedSha256)) { staged.delete();
Log.w(TAG, "Downloaded APK SHA-256 mismatch");
return null; return null;
} }
if (!temporary.renameTo(target)) return null; return staged;
return target;
} finally {
connection.disconnect();
}
}
} catch (Exception e) { } catch (Exception e) {
Log.w(TAG, "Update download failed", e); Log.w(TAG, "Update download failed", e);
} finally {
if (temporary != null && temporary.exists()) temporary.delete();
} }
return null; return null;
} }