diff --git a/CHANGELOG.md b/CHANGELOG.md index 620d6db..5e12d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.2.12] - 2026-09-30 + +### Fixed + +- When PackageInstaller sessions cannot start on the head unit, OTA now opens Android's + interactive APK installer through a cache-scoped FileProvider, matching DriveHub_Dort. + ## [2.2.11] - 2026-09-30 ### Changed diff --git a/FatihsMG4-unstable-2.2.12.apk.sha256 b/FatihsMG4-unstable-2.2.12.apk.sha256 new file mode 100644 index 0000000..e09e3a8 --- /dev/null +++ b/FatihsMG4-unstable-2.2.12.apk.sha256 @@ -0,0 +1 @@ +8320dad875986da41696291d106d568686d0b6175fa0a0f87add4d4b8c56201b FatihsMG4-unstable-2.2.12.apk diff --git a/app/build.gradle b/app/build.gradle index 6be8fad..271d059 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -34,8 +34,8 @@ android { applicationId "com.evsuite.abrp" minSdk 27 targetSdk 36 - versionCode 28 - versionName "2.2.11" + versionCode 29 + versionName "2.2.12" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } diff --git a/app/src/unstable/AndroidManifest.xml b/app/src/unstable/AndroidManifest.xml index 4238ec2..67402de 100644 --- a/app/src/unstable/AndroidManifest.xml +++ b/app/src/unstable/AndroidManifest.xml @@ -10,6 +10,16 @@ + + + + diff --git a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java index f1bd7a5..94fdf08 100644 --- a/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java +++ b/app/src/unstable/java/com/evsuite/abrp/OtaUpdater.java @@ -4,8 +4,13 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInstaller; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; import android.util.Log; +import androidx.core.content.FileProvider; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -414,6 +419,8 @@ final class OtaUpdater { UNREADABLE_ARCHIVE, /** Handed to the platform. The outcome arrives at {@link OtaInstallResultReceiver}. */ SESSION_STARTED, + /** Session install was unavailable; the system's interactive installer was opened. */ + INSTALL_UI_STARTED, /** The session could not be created, written or committed. */ SESSION_FAILED } @@ -437,8 +444,14 @@ final class OtaUpdater { commitSession(context, apk); return InstallResult.SESSION_STARTED; } catch (Throwable t) { - Log.w(TAG, "OTA session install failed", t); - return InstallResult.SESSION_FAILED; + Log.w(TAG, "OTA session install failed; trying system install UI", t); + try { + launchInstallerActivity(context, apk); + return InstallResult.INSTALL_UI_STARTED; + } catch (Throwable fallback) { + Log.w(TAG, "OTA install UI failed", fallback); + return InstallResult.SESSION_FAILED; + } } } @@ -511,4 +524,41 @@ final class OtaUpdater { } } + /** DriveHub_Dort-compatible fallback for head units that refuse installer sessions. */ + private static void launchInstallerActivity(Context context, File apk) { + if (Build.VERSION.SDK_INT >= 26 + && !context.getPackageManager().canRequestPackageInstalls()) { + Intent settings = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) + .setData(Uri.parse("package:" + context.getPackageName())) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(settings); + throw new IllegalStateException("unknown sources permission required"); + } + + Uri contentUri = FileProvider.getUriForFile(context, + context.getPackageName() + ".fileprovider", apk); + Intent install = new Intent(Intent.ACTION_VIEW) + .setDataAndType(contentUri, "application/vnd.android.package-archive") + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION); + grantToInstallers(context, contentUri); + if (install.resolveActivity(context.getPackageManager()) == null) { + throw new IllegalStateException("No package installer activity"); + } + context.startActivity(install); + Log.i(TAG, "System install UI opened for " + apk.getName()); + } + + private static void grantToInstallers(Context context, Uri uri) { + String[] packages = { + "com.android.packageinstaller", + "com.google.android.packageinstaller", + "com.samsung.android.packageinstaller" + }; + for (String packageName : packages) { + try { + context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (Throwable ignored) { } + } + } + } diff --git a/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java b/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java index 837617b..fc8e595 100644 --- a/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java +++ b/app/src/unstable/java/com/evsuite/abrp/UpdateHook.java @@ -117,10 +117,12 @@ final class UpdateHook { if (apk == null) return "İndirme veya SHA-256 doğrulaması başarısız (" + update.versionName + ")"; - // The cached APK is cleared whichever way install goes: a rejected archive must not - // linger, and an accepted one has already been streamed into the installer session. OtaUpdater.InstallResult result = OtaUpdater.install(app, apk); - if (!apk.delete()) Log.w(TAG, "Could not remove cached OTA APK"); + // FileProvider's interactive installer opens asynchronously and still needs this file. + // Other outcomes have either streamed the bytes already or rejected the archive. + if (result != OtaUpdater.InstallResult.INSTALL_UI_STARTED && !apk.delete()) { + Log.w(TAG, "Could not remove cached OTA APK"); + } switch (result) { case SESSION_STARTED: @@ -128,6 +130,8 @@ final class UpdateHook { // platform may still want the driver to confirm, and either way the verdict // arrives at OtaInstallResultReceiver, not here. return "Kuruluyor: " + update.versionName + " — ekrandaki onayı bekleyin"; + case INSTALL_UI_STARTED: + return "Kurulum ekranı açıldı: " + update.versionName; case UNREADABLE_ARCHIVE: return "Kurulum reddedildi: APK okunamadı"; default: diff --git a/app/src/unstable/res/xml/ota_file_paths.xml b/app/src/unstable/res/xml/ota_file_paths.xml new file mode 100644 index 0000000..ea61649 --- /dev/null +++ b/app/src/unstable/res/xml/ota_file_paths.xml @@ -0,0 +1,4 @@ + + + +