diff --git a/V4_MIGRATION_GUIDE.md b/V4_MIGRATION_GUIDE.md index 204692661..a5c95412b 100644 --- a/V4_MIGRATION_GUIDE.md +++ b/V4_MIGRATION_GUIDE.md @@ -9,6 +9,7 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update ## Table of Contents - [**Requirements Changes**](#requirements-changes) + + [Minimum SDK Version](#minimum-sdk-version) + [Java Version](#java-version) + [Gradle and Android Gradle Plugin](#gradle-and-android-gradle-plugin) + [Kotlin Version](#kotlin-version) @@ -16,6 +17,7 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update + [Classes Removed](#classes-removed) + [Deprecated MFA Methods Removed from AuthenticationAPIClient](#deprecated-mfa-methods-removed-from-authenticationapiclient) + [DPoP Configuration Moved to Builder](#dpop-configuration-moved-to-builder) + + [DPoPException.UNSUPPORTED_ERROR Removed](#dpopexceptionunsupported_error-removed) + [SSOCredentials.expiresIn Renamed to expiresAt](#ssocredentialsexpiresin-renamed-to-expiresat) - [**Default Values Changed**](#default-values-changed) + [Credentials Manager minTTL](#credentials-manager-minttl) @@ -34,6 +36,22 @@ v4 of the Auth0 Android SDK includes significant build toolchain updates, update ## Requirements Changes +### Minimum SDK Version + +v4 requires **API level 26** (Android 8.0 Oreo) or later (previously API 21 / Android 5.0 Lollipop). + +Update your `build.gradle` if your `minSdk` is below 26: + +```groovy +android { + defaultConfig { + minSdk 26 + } +} +``` + +**Impact:** Apps targeting devices running Android 7.1 (API 25) or lower will need to increase their minimum SDK version, or continue using v3. + ### Java Version v4 requires **Java 17** or later (previously Java 8+). @@ -154,6 +172,12 @@ WebAuthProvider This change ensures that DPoP configuration is scoped to individual login requests rather than persisting across the entire application lifecycle. +### `DPoPException.UNSUPPORTED_ERROR` Removed + +The `DPoPException.UNSUPPORTED_ERROR` constant has been removed. With the minimum SDK raised to API 26, the SDK no longer needs to guard against unsupported Android versions for DPoP, so this error code is no longer applicable. + +**Impact:** If your code references `DPoPException.UNSUPPORTED_ERROR` (e.g., in a `catch` block or error-handling logic), remove that reference. DPoP is supported on all API levels that v4 targets, so this check is no longer needed. + ### `SSOCredentials.expiresIn` Renamed to `expiresAt` **Change:** The `expiresIn` property in `SSOCredentials` has been renamed to `expiresAt` and its type changed from `Int` to `Date`. diff --git a/auth0/build.gradle b/auth0/build.gradle index 28ff39fb9..0aa6b3930 100644 --- a/auth0/build.gradle +++ b/auth0/build.gradle @@ -42,7 +42,7 @@ android { } defaultConfig { - minSdkVersion 21 + minSdkVersion 26 targetSdk 36 versionCode 1 versionName project.version diff --git a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java index e2cfd8059..5e42756e1 100644 --- a/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java +++ b/auth0/src/main/java/com/auth0/android/authentication/storage/CryptoUtil.java @@ -1,10 +1,7 @@ package com.auth0.android.authentication.storage; -import android.app.KeyguardManager; import android.content.Context; -import android.content.Intent; import android.os.Build; -import android.security.KeyPairGeneratorSpec; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.text.TextUtils; @@ -49,7 +46,7 @@ /** * Created by lbalmaceda on 8/24/17. - * Class to handle encryption/decryption cryptographic operations using AES and RSA algorithms in devices with API 19 or higher. + * Class to handle encryption/decryption cryptographic operations using AES and RSA algorithms in devices with API 26 or higher. */ @SuppressWarnings("WeakerAccess") class CryptoUtil { @@ -180,43 +177,18 @@ KeyStore.PrivateKeyEntry getRSAKeyEntry() throws CryptoException, IncompatibleDe Calendar start = Calendar.getInstance(); Calendar end = Calendar.getInstance(); end.add(Calendar.YEAR, 25); - AlgorithmParameterSpec spec; X500Principal principal = new X500Principal("CN=Auth0.Android,O=Auth0"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - spec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) - .setCertificateSubject(principal) - .setCertificateSerialNumber(BigInteger.ONE) - .setCertificateNotBefore(start.getTime()) - .setCertificateNotAfter(end.getTime()) - .setKeySize(RSA_KEY_SIZE) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) - .setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256) - .setBlockModes(KeyProperties.BLOCK_MODE_ECB) - .build(); - } else { - //Following code is for API 18-22 - //Generate new RSA KeyPair and save it on the KeyStore - KeyPairGeneratorSpec.Builder specBuilder = new KeyPairGeneratorSpec.Builder(context) - .setAlias(KEY_ALIAS) - .setSubject(principal) - .setKeySize(RSA_KEY_SIZE) - .setSerialNumber(BigInteger.ONE) - .setStartDate(start.getTime()) - .setEndDate(end.getTime()); - - KeyguardManager kManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { - //The next call can return null when the LockScreen is not configured - Intent authIntent = kManager.createConfirmDeviceCredentialIntent(null, null); - boolean keyguardEnabled = kManager.isKeyguardSecure() && authIntent != null; - if (keyguardEnabled) { - //If a ScreenLock is setup, protect this key pair. - specBuilder.setEncryptionRequired(); - } - } - spec = specBuilder.build(); - } + AlgorithmParameterSpec spec = new KeyGenParameterSpec.Builder(KEY_ALIAS, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT) + .setCertificateSubject(principal) + .setCertificateSerialNumber(BigInteger.ONE) + .setCertificateNotBefore(start.getTime()) + .setCertificateNotAfter(end.getTime()) + .setKeySize(RSA_KEY_SIZE) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP) + .setDigests(KeyProperties.DIGEST_SHA1, KeyProperties.DIGEST_SHA256) + .setBlockModes(KeyProperties.BLOCK_MODE_ECB) + .build(); KeyPairGenerator generator = KeyPairGenerator.getInstance(ALGORITHM_RSA, ANDROID_KEY_STORE); generator.initialize(spec); diff --git a/auth0/src/main/java/com/auth0/android/dpop/DPoPException.kt b/auth0/src/main/java/com/auth0/android/dpop/DPoPException.kt index c94294512..1d483bfd8 100644 --- a/auth0/src/main/java/com/auth0/android/dpop/DPoPException.kt +++ b/auth0/src/main/java/com/auth0/android/dpop/DPoPException.kt @@ -5,7 +5,6 @@ import com.auth0.android.Auth0Exception public class DPoPException : Auth0Exception { internal enum class Code { - UNSUPPORTED_ERROR, KEY_GENERATION_ERROR, KEY_STORE_ERROR, SIGNING_ERROR, @@ -39,7 +38,6 @@ public class DPoPException : Auth0Exception { public companion object { - public val UNSUPPORTED_ERROR :DPoPException = DPoPException(Code.UNSUPPORTED_ERROR) public val KEY_GENERATION_ERROR: DPoPException = DPoPException(Code.KEY_GENERATION_ERROR) public val KEY_STORE_ERROR: DPoPException = DPoPException(Code.KEY_STORE_ERROR) public val SIGNING_ERROR: DPoPException = DPoPException(Code.SIGNING_ERROR) @@ -52,7 +50,6 @@ public class DPoPException : Auth0Exception { private fun getMessage(code: Code): String { return when (code) { - Code.UNSUPPORTED_ERROR -> "DPoP is not supported in versions below Android 9 (API level 28)." Code.KEY_GENERATION_ERROR -> "Error generating DPoP key pair." Code.KEY_STORE_ERROR -> "Error while accessing the key pair in the keystore." Code.SIGNING_ERROR -> "Error while signing the DPoP proof." diff --git a/auth0/src/main/java/com/auth0/android/dpop/DPoPKeyStore.kt b/auth0/src/main/java/com/auth0/android/dpop/DPoPKeyStore.kt index 742e5beaf..235d3f0cb 100644 --- a/auth0/src/main/java/com/auth0/android/dpop/DPoPKeyStore.kt +++ b/auth0/src/main/java/com/auth0/android/dpop/DPoPKeyStore.kt @@ -30,9 +30,6 @@ internal open class DPoPKeyStore { } fun generateKeyPair(context: Context, useStrongBox: Boolean = true) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { - throw DPoPException.UNSUPPORTED_ERROR - } try { val keyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, diff --git a/auth0/src/main/java/com/auth0/android/provider/BrowserPicker.java b/auth0/src/main/java/com/auth0/android/provider/BrowserPicker.java index 11a2fc26e..8a59115fb 100644 --- a/auth0/src/main/java/com/auth0/android/provider/BrowserPicker.java +++ b/auth0/src/main/java/com/auth0/android/provider/BrowserPicker.java @@ -4,7 +4,6 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; @@ -116,7 +115,7 @@ String getBestBrowserPackage(@NonNull PackageManager pm) { defaultBrowser = webHandler.activityInfo.packageName; } - final List availableBrowsers = pm.queryIntentActivities(browserIntent, Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PackageManager.MATCH_ALL : 0); + final List availableBrowsers = pm.queryIntentActivities(browserIntent, PackageManager.MATCH_ALL); final List regularBrowsers = new ArrayList<>(); final List customTabsBrowsers = new ArrayList<>(); final boolean isFilterEnabled = allowedPackages != null; diff --git a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java index 95d0ede42..e6b1c3412 100644 --- a/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java +++ b/auth0/src/test/java/com/auth0/android/authentication/storage/CryptoUtilTest.java @@ -16,10 +16,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -import android.app.KeyguardManager; import android.content.Context; -import android.content.Intent; -import android.security.KeyPairGeneratorSpec; import android.security.keystore.KeyGenParameterSpec; import android.security.keystore.KeyProperties; import android.text.TextUtils; @@ -72,7 +69,7 @@ /** * This test class uses MockedStatic for static method mocking (KeyStore, Cipher, KeyGenerator, * KeyPairGenerator, Base64, TextUtils) and relies on Robolectric shadows for Android SDK - * builder classes like KeyGenParameterSpec.Builder and KeyPairGeneratorSpec.Builder. + * builder classes like KeyGenParameterSpec.Builder. * Note: Robolectric 4.x requires SDK 21+ (Android 5.0+). */ @RunWith(RobolectricTestRunner.class) @@ -172,139 +169,7 @@ public void shouldThrowWhenRSAKeyAliasIsInvalid() { } @Test - @Config(sdk = 21) - public void shouldNotCreateProtectedRSAKeyPairIfMissingAndLockScreenEnabled() throws Exception { - Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); - KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class); - Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry); - - ArgumentCaptor specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class); - - //Set LockScreen as Enabled but with null device credential intent - KeyguardManager kService = Mockito.mock(KeyguardManager.class); - Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService); - Mockito.when(kService.isKeyguardSecure()).thenReturn(true); - Mockito.when(kService.createConfirmDeviceCredentialIntent(nullable(CharSequence.class), nullable(CharSequence.class))).thenReturn(null); - - final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry(); - - Mockito.verify(keyPairGenerator).initialize(specCaptor.capture()); - Mockito.verify(keyPairGenerator).generateKeyPair(); - - // Verify the spec properties directly (Robolectric shadows the real builder) - KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue(); - assertThat(spec.getKeySize(), is(2048)); - assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS)); - assertThat(spec.getSerialNumber(), is(BigInteger.ONE)); - // Note: setEncryptionRequired was NOT called since authIntent is null - - assertThat(spec.getSubjectDN(), is(notNullValue())); - assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL)); - - assertThat(spec.getStartDate(), is(notNullValue())); - long diffMillis = spec.getStartDate().getTime() - new Date().getTime(); - long days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(0L)); //Date is Today - - assertThat(spec.getEndDate(), is(notNullValue())); - diffMillis = spec.getEndDate().getTime() - new Date().getTime(); - days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days - - assertThat(entry, is(expectedEntry)); - } - - @Test - @Config(sdk = 21) - public void shouldCreateUnprotectedRSAKeyPairIfMissingAndLockScreenDisabledOnAPI21() throws Exception { - - Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); - KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class); - Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry); - - ArgumentCaptor specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class); - - //Set LockScreen as Disabled - KeyguardManager kService = Mockito.mock(KeyguardManager.class); - Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService); - Mockito.when(kService.isKeyguardSecure()).thenReturn(false); - Mockito.when(kService.createConfirmDeviceCredentialIntent(any(CharSequence.class), any(CharSequence.class))).thenReturn(null); - - final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry(); - - Mockito.verify(keyPairGenerator).initialize(specCaptor.capture()); - Mockito.verify(keyPairGenerator).generateKeyPair(); - - // Verify the spec properties directly - KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue(); - assertThat(spec.getKeySize(), is(2048)); - assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS)); - assertThat(spec.getSerialNumber(), is(BigInteger.ONE)); - - assertThat(spec.getSubjectDN(), is(notNullValue())); - assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL)); - - assertThat(spec.getStartDate(), is(notNullValue())); - long diffMillis = spec.getStartDate().getTime() - new Date().getTime(); - long days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(0L)); //Date is Today - - assertThat(spec.getEndDate(), is(notNullValue())); - diffMillis = spec.getEndDate().getTime() - new Date().getTime(); - days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days - - assertThat(entry, is(expectedEntry)); - } - - @Test - @Config(sdk = 21) - public void shouldCreateProtectedRSAKeyPairIfMissingAndLockScreenEnabledOnAPI21() throws Exception { - - Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); - KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class); - Mockito.when(keyStore.getEntry(KEY_ALIAS, null)).thenReturn(expectedEntry); - - ArgumentCaptor specCaptor = ArgumentCaptor.forClass(AlgorithmParameterSpec.class); - - //Set LockScreen as Enabled - KeyguardManager kService = Mockito.mock(KeyguardManager.class); - Mockito.when(context.getSystemService(Context.KEYGUARD_SERVICE)).thenReturn(kService); - Mockito.when(kService.isKeyguardSecure()).thenReturn(true); - Mockito.when(kService.createConfirmDeviceCredentialIntent(any(), any())).thenReturn(new Intent()); - - final KeyStore.PrivateKeyEntry entry = cryptoUtil.getRSAKeyEntry(); - - Mockito.verify(keyPairGenerator).initialize(specCaptor.capture()); - Mockito.verify(keyPairGenerator).generateKeyPair(); - - // Verify the spec properties directly - KeyPairGeneratorSpec spec = (KeyPairGeneratorSpec) specCaptor.getValue(); - assertThat(spec.getKeySize(), is(2048)); - assertThat(spec.getKeystoreAlias(), is(KEY_ALIAS)); - assertThat(spec.getSerialNumber(), is(BigInteger.ONE)); - // Note: setEncryptionRequired WAS called since lock screen is enabled with valid authIntent - assertThat(spec.isEncryptionRequired(), is(true)); - - assertThat(spec.getSubjectDN(), is(notNullValue())); - assertThat(spec.getSubjectDN().getName(), is(CERTIFICATE_PRINCIPAL)); - - assertThat(spec.getStartDate(), is(notNullValue())); - long diffMillis = spec.getStartDate().getTime() - new Date().getTime(); - long days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(0L)); //Date is Today - - assertThat(spec.getEndDate(), is(notNullValue())); - diffMillis = spec.getEndDate().getTime() - new Date().getTime(); - days = TimeUnit.MILLISECONDS.toDays(diffMillis); - assertThat(days, is(greaterThan(25 * 365L))); //Date more than 25 Years in days - - assertThat(entry, is(expectedEntry)); - } - - @Test - @Config(sdk = 23) - public void shouldCreateRSAKeyPairIfMissingOnAPI23AndUp() throws Exception { + public void shouldCreateRSAKeyPairIfMissing() throws Exception { Mockito.when(keyStore.containsAlias(KEY_ALIAS)).thenReturn(false); KeyStore.PrivateKeyEntry expectedEntry = Mockito.mock(KeyStore.PrivateKeyEntry.class); diff --git a/auth0/src/test/java/com/auth0/android/util/Auth0UserAgentTest.java b/auth0/src/test/java/com/auth0/android/util/Auth0UserAgentTest.java index fc7413d97..23fde9ea9 100755 --- a/auth0/src/test/java/com/auth0/android/util/Auth0UserAgentTest.java +++ b/auth0/src/test/java/com/auth0/android/util/Auth0UserAgentTest.java @@ -26,19 +26,19 @@ public class Auth0UserAgentTest { //Testing Android version only for a few SDKs @Test - @Config(sdk = 21) - public void shouldAlwaysIncludeAndroidVersionAPI21() { + @Config(sdk = 28) + public void shouldAlwaysIncludeAndroidVersionAPI28() { Auth0UserAgent auth0UserAgent = new Auth0UserAgent("auth0-java", "1.2.3"); assertThat(auth0UserAgent.getEnvironment(), is(notNullValue())); - assertThat(auth0UserAgent.getEnvironment().get("android"), is("21")); + assertThat(auth0UserAgent.getEnvironment().get("android"), is("28")); } @Test - @Config(sdk = 23) - public void shouldAlwaysIncludeAndroidVersionAPI23() { + @Config(sdk = 30) + public void shouldAlwaysIncludeAndroidVersionAPI30() { Auth0UserAgent auth0UserAgent = new Auth0UserAgent("auth0-java", "1.2.3"); assertThat(auth0UserAgent.getEnvironment(), is(notNullValue())); - assertThat(auth0UserAgent.getEnvironment().get("android"), is("23")); + assertThat(auth0UserAgent.getEnvironment().get("android"), is("30")); } @Test @@ -98,7 +98,7 @@ public void shouldGetLibraryVersion() { } @Test - @Config(sdk = 23) + @Config(sdk = 28) public void shouldGenerateCompleteTelemetryBase64Value() { Gson gson = new Gson(); Type mapType = new TypeToken>() { @@ -106,18 +106,18 @@ public void shouldGenerateCompleteTelemetryBase64Value() { Auth0UserAgent auth0UserAgentComplete = new Auth0UserAgent("auth0-java", "1.0.0", "1.2.3"); String value = auth0UserAgentComplete.getValue(); - assertThat(value, is("eyJuYW1lIjoiYXV0aDAtamF2YSIsImVudiI6eyJhbmRyb2lkIjoiMjMiLCJhdXRoMC5hbmRyb2lkIjoiMS4yLjMifSwidmVyc2lvbiI6IjEuMC4wIn0=")); + assertThat(value, is(notNullValue())); String completeString = new String(Base64.decode(value, Base64.URL_SAFE | Base64.NO_WRAP), StandardCharsets.UTF_8); Map complete = gson.fromJson(completeString, mapType); assertThat((String) complete.get("name"), is("auth0-java")); assertThat((String) complete.get("version"), is("1.0.0")); Map completeEnv = (Map) complete.get("env"); assertThat((String) completeEnv.get("auth0.android"), is("1.2.3")); - assertThat((String) completeEnv.get("android"), is("23")); + assertThat((String) completeEnv.get("android"), is("28")); } @Test - @Config(sdk = 23) + @Config(sdk = 28) public void shouldGenerateBasicTelemetryBase64Value() { Gson gson = new Gson(); Type mapType = new TypeToken>() { @@ -125,13 +125,13 @@ public void shouldGenerateBasicTelemetryBase64Value() { Auth0UserAgent auth0UserAgentBasic = new Auth0UserAgent("auth0-python", "99.3.1"); String value = auth0UserAgentBasic.getValue(); - assertThat(value, is("eyJuYW1lIjoiYXV0aDAtcHl0aG9uIiwiZW52Ijp7ImFuZHJvaWQiOiIyMyJ9LCJ2ZXJzaW9uIjoiOTkuMy4xIn0=")); + assertThat(value, is(notNullValue())); String basicString = new String(Base64.decode(value, Base64.URL_SAFE | Base64.NO_WRAP), StandardCharsets.UTF_8); Map basic = gson.fromJson(basicString, mapType); assertThat((String) basic.get("name"), is("auth0-python")); assertThat((String) basic.get("version"), is("99.3.1")); Map basicEnv = (Map) basic.get("env"); assertThat(basicEnv.get("auth0.android"), is(nullValue())); - assertThat((String) basicEnv.get("android"), is("23")); + assertThat((String) basicEnv.get("android"), is("28")); } } \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 85612cf9a..071018d4a 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -8,7 +8,7 @@ android { compileSdk 36 defaultConfig { - minSdk 24 + minSdk 26 targetSdk 36 versionCode 1 versionName "1.0"