From 93d259b1d8faea3d056d78a8881409741708dc8e Mon Sep 17 00:00:00 2001 From: Sylvia van Os Date: Fri, 23 Jan 2026 18:28:30 +0100 Subject: [PATCH] Reapply "Temporarily re-add Android 5 support (#2925)" This reverts commit 5ac24058345d5fe99e44bb0cb79621e5cb1cf22a. --- .github/workflows/android.yml | 4 +-- CHANGELOG.md | 2 -- app/build.gradle.kts | 2 +- .../card_locker/CatimaAppCompatActivity.java | 16 ++++++++--- .../java/protect/card_locker/ListWidget.kt | 3 ++- .../protect/card_locker/PermissionUtils.java | 27 ++++++++++++++++--- .../java/protect/card_locker/ScanActivity.kt | 8 ++++++ .../java/protect/card_locker/UCropWrapper.kt | 13 ++++++--- .../contentprovider/CardsContentProvider.java | 7 +++++ .../preferences/SettingsActivity.kt | 6 +++++ gradle/libs.versions.toml | 4 +-- 11 files changed, 73 insertions(+), 19 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 0231ad224..ac21c24dd 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -50,10 +50,10 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: Run instrumented tests (API 23) + - name: Run instrumented tests (API 21) uses: ReactiveCircus/android-emulator-runner@v2 with: - api-level: 23 + api-level: 21 arch: x86_64 script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest - name: Run instrumented tests (API 35) diff --git a/CHANGELOG.md b/CHANGELOG.md index 318c02b1f..57d09aa98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Unreleased - 163 -Android 5.0 and 5.1 are no longer supported starting with this release. If you want to use Catima on these versions, please use version 2.41.5. - - Auto-detect URLs in card ID and make the clickable in card ID details pop-up - Fix barcode selector activity showing white squares instead of barcodes diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b35b615ee..96adcfeb3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { applicationId = "me.hackerchick.catima" - minSdk = 23 + minSdk = 21 targetSdk = 36 versionCode = 162 versionName = "2.41.5" diff --git a/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java b/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java index 50f5dd852..c44a19fc4 100644 --- a/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java +++ b/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java @@ -38,10 +38,15 @@ public class CatimaAppCompatActivity extends AppCompatActivity { Window window = getWindow(); if (window != null) { boolean darkMode = Utils.isDarkModeEnabled(this); - View decorView = window.getDecorView(); - WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView); - wic.setAppearanceLightStatusBars(!darkMode); - window.setStatusBarColor(Color.TRANSPARENT); + if (Build.VERSION.SDK_INT >= 23) { + View decorView = window.getDecorView(); + WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView); + wic.setAppearanceLightStatusBars(!darkMode); + window.setStatusBarColor(Color.TRANSPARENT); + } else { + // icons are always white back then + window.setStatusBarColor(darkMode ? Color.TRANSPARENT : Color.argb(127, 0, 0, 0)); + } } // XXX android 9 and below has a nasty rendering bug if the theme was patched earlier Utils.postPatchColors(this); @@ -61,4 +66,7 @@ public class CatimaAppCompatActivity extends AppCompatActivity { actionBar.setDisplayHomeAsUpEnabled(true); } } + + public void onMockedRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + } } diff --git a/app/src/main/java/protect/card_locker/ListWidget.kt b/app/src/main/java/protect/card_locker/ListWidget.kt index be5ac4886..6d509e65c 100644 --- a/app/src/main/java/protect/card_locker/ListWidget.kt +++ b/app/src/main/java/protect/card_locker/ListWidget.kt @@ -102,7 +102,8 @@ class ListWidget : AppWidgetProvider() { val foreground = if (Utils.needsDarkForeground(headerColor)) Color.BLACK else Color.WHITE setInt(R.id.item_container_foreground, "setBackgroundColor", headerColor) val icon = loyaltyCard.getImageThumbnail(context) - // FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path + // setImageViewIcon is not supported on Android 5, so force Android 5 down the text path + // FIXME: The icon flow causes a crash up to Android 12L, so SDK_INT is forced up from 23 to 33 if (icon != null && Build.VERSION.SDK_INT >= 32) { setInt(R.id.item_container_foreground, "setBackgroundColor", foreground) setImageViewIcon(R.id.item_image, Icon.createWithBitmap(icon)) diff --git a/app/src/main/java/protect/card_locker/PermissionUtils.java b/app/src/main/java/protect/card_locker/PermissionUtils.java index 583da8e14..71cf75f52 100644 --- a/app/src/main/java/protect/card_locker/PermissionUtils.java +++ b/app/src/main/java/protect/card_locker/PermissionUtils.java @@ -34,6 +34,11 @@ public class PermissionUtils { * @return */ public static boolean needsCameraPermission(Activity activity) { + // Android only introduced the runtime permission system in Marshmallow (Android 6.0) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return false; + } + return ContextCompat.checkSelfPermission(activity, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED; } @@ -44,14 +49,21 @@ public class PermissionUtils { * @param activity * @param requestCode */ - public static void requestStorageReadPermission(Activity activity, int requestCode) { + public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) { String[] permissions = new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE }; int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED }; if (needsStorageReadPermission(activity)) { ActivityCompat.requestPermissions(activity, permissions, requestCode); } else { - activity.onRequestPermissionsResult(requestCode, permissions, mockedResults); + // FIXME: This points to onMockedRequestPermissionResult instead of to + // onRequestPermissionResult because onRequestPermissionResult was only introduced in + // Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too. + // + // When minSdk becomes 23, this should point to onRequestPermissionResult directly and + // the activity input variable should be changed from CatimaAppCompatActivity to + // Activity. + activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults); } } @@ -62,14 +74,21 @@ public class PermissionUtils { * @param activity * @param requestCode */ - public static void requestCameraPermission(Activity activity, int requestCode) { + public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) { String[] permissions = new String[]{ Manifest.permission.CAMERA }; int[] mockedResults = new int[]{ PackageManager.PERMISSION_GRANTED }; if (needsCameraPermission(activity)) { ActivityCompat.requestPermissions(activity, permissions, requestCode); } else { - activity.onRequestPermissionsResult(requestCode, permissions, mockedResults); + // FIXME: This points to onMockedRequestPermissionResult instead of to + // onRequestPermissionResult because onRequestPermissionResult was only introduced in + // Android 6.0 (SDK 23) and we and to support Android 5.0 (SDK 21) too. + // + // When minSdk becomes 23, this should point to onRequestPermissionResult directly and + // the activity input variable should be changed from CatimaAppCompatActivity to + // Activity. + activity.onMockedRequestPermissionsResult(requestCode, permissions, mockedResults); } } } \ No newline at end of file diff --git a/app/src/main/java/protect/card_locker/ScanActivity.kt b/app/src/main/java/protect/card_locker/ScanActivity.kt index 952dac3c3..52beb68ea 100644 --- a/app/src/main/java/protect/card_locker/ScanActivity.kt +++ b/app/src/main/java/protect/card_locker/ScanActivity.kt @@ -543,6 +543,14 @@ class ScanActivity : CatimaAppCompatActivity() { ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) + onMockedRequestPermissionsResult(requestCode, permissions, grantResults) + } + + override fun onMockedRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { val granted = grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED diff --git a/app/src/main/java/protect/card_locker/UCropWrapper.kt b/app/src/main/java/protect/card_locker/UCropWrapper.kt index 352fb5266..caa326c76 100644 --- a/app/src/main/java/protect/card_locker/UCropWrapper.kt +++ b/app/src/main/java/protect/card_locker/UCropWrapper.kt @@ -40,9 +40,16 @@ class UCropWrapper : UCropActivity() { return } - val decorView = window.decorView - val wic = WindowInsetsControllerCompat(window, decorView) - wic.isAppearanceLightStatusBars = !darkMode + if (Build.VERSION.SDK_INT >= 23) { + val decorView = window.decorView + val wic = WindowInsetsControllerCompat(window, decorView) + wic.isAppearanceLightStatusBars = !darkMode + } else if (!darkMode) { + window.statusBarColor = ColorUtils.compositeColors( + Color.argb(127, 0, 0, 0), + window.statusBarColor + ) + } } private fun checkViews(darkMode: Boolean) { diff --git a/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java b/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java index 1f83b90a7..54667e2fe 100644 --- a/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java +++ b/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java @@ -77,6 +77,13 @@ public class CardsContentProvider extends ContentProvider { @Nullable final String selection, @Nullable final String[] selectionArgs, @Nullable final String sortOrder) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // Disable the content provider on SDK < 23 since it grants dangerous + // permissions at install-time + Log.w(TAG, "Content provider read is only available for SDK >= 23"); + return null; + } + final Settings settings = new Settings(getContext()); if (!settings.getAllowContentProviderRead()) { Log.w(TAG, "Content provider read is disabled"); diff --git a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt index 8d48320d2..06b7377ec 100644 --- a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt +++ b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt @@ -157,6 +157,12 @@ class SettingsActivity : CatimaAppCompatActivity() { true } + // Disable content provider on SDK < 23 since dangerous permissions + // are granted at install-time + val contentProviderReadPreference = findPreference(getString(R.string.settings_key_allow_content_provider_read)) + contentProviderReadPreference!!.isVisible = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + // Hide crash reporter settings on builds it's not enabled on val crashReporterPreference = findPreference("acra.enable") crashReporterPreference!!.isVisible = BuildConfig.useAcraCrashReporter diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8cf2dc344..3c0ad6d38 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # AndroidX -compose = "2026.01.00" +compose = "2025.11.01" # Third-party acra = "5.13.1" @@ -22,7 +22,7 @@ com-google-android-material-material = { group = "com.google.android.material", com-android-tools-desugar_jdk_libs = { group = "com.android.tools", name = "desugar_jdk_libs", version = "2.1.5" } # Compose -androidx-activity-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.12.2" } +androidx-activity-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.10.1" } androidx-compose-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose" } androidx-compose-foundation-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-compose-material3-material3 = { group = "androidx.compose.material3", name = "material3"}