diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index ac21c24dd..0231ad224 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 21) + - name: Run instrumented tests (API 23) uses: ReactiveCircus/android-emulator-runner@v2 with: - api-level: 21 + api-level: 23 arch: x86_64 script: ./gradlew connected${{ matrix.flavor }}DebugAndroidTest - name: Run instrumented tests (API 35) diff --git a/CHANGELOG.md b/CHANGELOG.md index dadaf554d..14b5b91a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## 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 ## v2.41.5 - 162 (2026-01-15) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 96adcfeb3..b35b615ee 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -17,7 +17,7 @@ android { defaultConfig { applicationId = "me.hackerchick.catima" - minSdk = 21 + minSdk = 23 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 c44a19fc4..50f5dd852 100644 --- a/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java +++ b/app/src/main/java/protect/card_locker/CatimaAppCompatActivity.java @@ -38,15 +38,10 @@ public class CatimaAppCompatActivity extends AppCompatActivity { Window window = getWindow(); if (window != null) { boolean darkMode = Utils.isDarkModeEnabled(this); - 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)); - } + View decorView = window.getDecorView(); + WindowInsetsControllerCompat wic = new WindowInsetsControllerCompat(window, decorView); + wic.setAppearanceLightStatusBars(!darkMode); + window.setStatusBarColor(Color.TRANSPARENT); } // XXX android 9 and below has a nasty rendering bug if the theme was patched earlier Utils.postPatchColors(this); @@ -66,7 +61,4 @@ 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 6d509e65c..be5ac4886 100644 --- a/app/src/main/java/protect/card_locker/ListWidget.kt +++ b/app/src/main/java/protect/card_locker/ListWidget.kt @@ -102,8 +102,7 @@ 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) - // 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 + // FIXME: The icon flow causes a crash up to Android 12L, so force anything below 33 down this path 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 71cf75f52..583da8e14 100644 --- a/app/src/main/java/protect/card_locker/PermissionUtils.java +++ b/app/src/main/java/protect/card_locker/PermissionUtils.java @@ -34,11 +34,6 @@ 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; } @@ -49,21 +44,14 @@ public class PermissionUtils { * @param activity * @param requestCode */ - public static void requestStorageReadPermission(CatimaAppCompatActivity activity, int requestCode) { + public static void requestStorageReadPermission(Activity 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 { - // 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); + activity.onRequestPermissionsResult(requestCode, permissions, mockedResults); } } @@ -74,21 +62,14 @@ public class PermissionUtils { * @param activity * @param requestCode */ - public static void requestCameraPermission(CatimaAppCompatActivity activity, int requestCode) { + public static void requestCameraPermission(Activity 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 { - // 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); + activity.onRequestPermissionsResult(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 52beb68ea..952dac3c3 100644 --- a/app/src/main/java/protect/card_locker/ScanActivity.kt +++ b/app/src/main/java/protect/card_locker/ScanActivity.kt @@ -543,14 +543,6 @@ 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 caa326c76..352fb5266 100644 --- a/app/src/main/java/protect/card_locker/UCropWrapper.kt +++ b/app/src/main/java/protect/card_locker/UCropWrapper.kt @@ -40,16 +40,9 @@ class UCropWrapper : UCropActivity() { return } - 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 - ) - } + val decorView = window.decorView + val wic = WindowInsetsControllerCompat(window, decorView) + wic.isAppearanceLightStatusBars = !darkMode } 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 54667e2fe..1f83b90a7 100644 --- a/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java +++ b/app/src/main/java/protect/card_locker/contentprovider/CardsContentProvider.java @@ -77,13 +77,6 @@ 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 06b7377ec..8d48320d2 100644 --- a/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt +++ b/app/src/main/java/protect/card_locker/preferences/SettingsActivity.kt @@ -157,12 +157,6 @@ 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 3c0ad6d38..5792aad3c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # AndroidX -compose = "2025.11.01" +compose = "2025.12.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.10.1" } +androidx-activity-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.12.2" } 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"}