diff --git a/.gitignore b/.gitignore
index c9cccbed..3aa09dae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@
build/
docs/
docs-store/
+auth-lib/stdout
+auth-sample/stdout
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5bb6ec76..f056fac1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
Change Log
==========
+## Version 5.0.0
+- Bumped targetSdkVersion to 35
+- Bumped minSdkVersion to 21
+- Bumped AGP to 8.7.3 and Gradle to 8.11.1
+- Bumped Robolectric to 4.14.1
+- **Breaking Change**: The `RedirectUriReceiverActivity` intent-filter must now be declared in the consuming app's `AndroidManifest.xml`. The library no longer provides it automatically. See the [README](README.md#breaking-changes-in-spotify-auth-library-version-500) for migration instructions.
## Version 4.0.1
- Fix: Ensure auth progress is preserved across configuration changes, preventing cases where a headless activity remains open and blocks user input.
diff --git a/README.md b/README.md
index dbbe2907..b195817c 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,32 @@
[](https://search.maven.org/search?q=g:com.spotify.android)
+# Breaking changes in Spotify Auth library version 5.0.0
+
+The `RedirectUriReceiverActivity` intent-filter is no longer provided by the library. You must declare it in your app's `AndroidManifest.xml` with your redirect URI values:
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+If you were previously using `manifestPlaceholders` for `redirectSchemeName`, `redirectHostName`, and `redirectPathPattern`, replace them with the values directly in the manifest. The `manifestPlaceholders` configuration in `build.gradle` is no longer needed.
+
+If your redirect URI uses `https://`, add `android:autoVerify="true"` to the `` to enable [Android App Links](https://developer.android.com/training/app-links) verification.
+
+If your app is missing this intent-filter, the library will crash at runtime with an `IllegalStateException` when the auth flow starts.
+
# Breaking changes in Spotify Auth library version 2.0.0
In this version we replaced use of WebView with [Custom Tabs](https://developer.chrome.com/docs/android/custom-tabs/) since Google and Facebook Login no longer support WebViews for authenticating users.
@@ -24,31 +50,7 @@ repositories {
}
```
-Since Spotify Auth library version 2.0.0 you also need to provide the scheme and host of the redirect URI that your app is using for authorizing in your app `build.gradle` file.
-Below is an example of how this looks for [the auth sample project](auth-sample) using `spotify-sdk://auth` redirect URI:
-
-```gradle
- defaultConfig {
- manifestPlaceholders = [redirectSchemeName: "spotify-sdk", redirectHostName: "auth"]
- ...
- }
-```
-
-Since Spotify Auth library version 3.0.0 you also need to provide the path pattern of your redirect URI
-
-```gradle
-defaultConfig {
- manifestPlaceholders = [
- redirectSchemeName: "your-redirect-scheme",
- redirectHostName: "your-redirect-host",
- redirectPathPattern: "your/redirect/path/pattern" // New mandatory field
- ]
- ...
-}
-```
-
-If you want to retain the previous behavior (accepting any path), use .* as the path pattern.
-For more details, see the [Google documentation](https://developer.android.com/guide/topics/manifest/data-element#path).
+You must also declare the `RedirectUriReceiverActivity` intent-filter in your app's `AndroidManifest.xml` (see [breaking changes in version 5.0.0](#breaking-changes-in-spotify-auth-library-version-500) above).
To learn more see the [Authentication Guide](https://developer.spotify.com/technologies/spotify-android-sdk/android-sdk-authentication-guide/)
and the [API reference](https://spotify.github.io/android-auth/auth-lib/docs/index.html).
diff --git a/auth-lib/build.gradle.kts b/auth-lib/build.gradle.kts
index 3a0842b6..40f314ba 100644
--- a/auth-lib/build.gradle.kts
+++ b/auth-lib/build.gradle.kts
@@ -35,21 +35,21 @@ plugins {
}
group = "com.spotify.android"
-version = "4.0.1"
+version = "5.0.0"
val archivesBaseName = "auth"
android {
- compileSdk = 33
- buildToolsVersion = "33.0.0"
+ compileSdk = 35
+ buildToolsVersion = "35.0.0"
buildFeatures {
buildConfig = true
}
defaultConfig {
- minSdk = 16
- targetSdk = 33
+ minSdk = 21
+ targetSdk = 35
buildConfigField("String", "LIB_VERSION_NAME", "\"${project.version}\"")
}
@@ -82,6 +82,15 @@ android {
}
}
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
lint {
lintConfig = file("${project.rootDir}/config/lint.xml")
quiet = false
@@ -97,20 +106,7 @@ android {
}
}
- val manifestPlaceholdersForTests = mapOf(
- "redirectSchemeName" to "spotify-sdk",
- "redirectHostName" to "auth",
- "redirectPathPattern" to "/.*"
- )
-
namespace = "com.spotify.sdk.android.auth"
-
- unitTestVariants.configureEach {
- mergedFlavor.manifestPlaceholders.putAll(manifestPlaceholdersForTests)
- }
- testVariants.configureEach {
- mergedFlavor.manifestPlaceholders.putAll(manifestPlaceholdersForTests)
- }
}
val kotlinVersion = rootProject.extra["kotlin_version"] as String
@@ -121,7 +117,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:2.28.2")
- testImplementation("org.robolectric:robolectric:4.11.1")
+ testImplementation("org.robolectric:robolectric:4.14.1")
}
/*
diff --git a/auth-lib/src/auth/AndroidManifest.xml b/auth-lib/src/auth/AndroidManifest.xml
index 80833331..f17c8f62 100644
--- a/auth-lib/src/auth/AndroidManifest.xml
+++ b/auth-lib/src/auth/AndroidManifest.xml
@@ -34,17 +34,7 @@
-
-
-
-
-
-
-
+ android:exported="true"/>
diff --git a/auth-lib/src/auth/kotlin/com/spotify/sdk/android/auth/browser/CustomTabsSupportChecker.kt b/auth-lib/src/auth/kotlin/com/spotify/sdk/android/auth/browser/CustomTabsSupportChecker.kt
index 8ca40890..dc1e3df1 100644
--- a/auth-lib/src/auth/kotlin/com/spotify/sdk/android/auth/browser/CustomTabsSupportChecker.kt
+++ b/auth-lib/src/auth/kotlin/com/spotify/sdk/android/auth/browser/CustomTabsSupportChecker.kt
@@ -3,9 +3,7 @@ package com.spotify.sdk.android.auth.browser
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.content.pm.ResolveInfo
import android.net.Uri
-import android.text.TextUtils
import android.util.Log
import androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION
import com.spotify.sdk.android.auth.AuthorizationRequest
@@ -26,8 +24,7 @@ internal object CustomTabsSupportChecker {
)
// CustomTabs seems to have problem with redirecting back the app after auth when URI has http/https scheme
return if (!redirectUri.startsWith("http") && !redirectUri.startsWith("https") &&
- hasBrowserSupportForCustomTabs(packageSupportingCustomTabs) &&
- hasRedirectUriActivity(context.packageManager, redirectUri)
+ hasBrowserSupportForCustomTabs(packageSupportingCustomTabs)
) {
packageSupportingCustomTabs
} else {
@@ -83,20 +80,4 @@ internal object CustomTabsSupportChecker {
}
}
- private fun hasRedirectUriActivity(pm: PackageManager?, redirectUri: String): Boolean {
- if (pm == null) {
- return false
- }
- val intent = Intent().apply {
- action = Intent.ACTION_VIEW
- addCategory(Intent.CATEGORY_DEFAULT)
- addCategory(Intent.CATEGORY_BROWSABLE)
- data = Uri.parse(redirectUri)
- }
- val infoList = pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER)
-
- return infoList.any { info ->
- info.activityInfo.name == RedirectUriReceiverActivity::class.java.name
- }
- }
}
diff --git a/auth-lib/src/main/AndroidManifest.xml b/auth-lib/src/main/AndroidManifest.xml
index 22db0699..d7e7f530 100644
--- a/auth-lib/src/main/AndroidManifest.xml
+++ b/auth-lib/src/main/AndroidManifest.xml
@@ -35,6 +35,12 @@
+
+
+
+
+
+
diff --git a/auth-lib/src/main/kotlin/com/spotify/sdk/android/auth/AuthorizationClient.kt b/auth-lib/src/main/kotlin/com/spotify/sdk/android/auth/AuthorizationClient.kt
index 56bd94aa..b7ef8fe3 100644
--- a/auth-lib/src/main/kotlin/com/spotify/sdk/android/auth/AuthorizationClient.kt
+++ b/auth-lib/src/main/kotlin/com/spotify/sdk/android/auth/AuthorizationClient.kt
@@ -219,6 +219,8 @@ class AuthorizationClient(
if (authorizationPending) return
authorizationPending = true
+ checkRedirectUriConfiguration(loginActivity.packageManager, request.redirectUri)
+
val processedRequest = validateAndConvertTokenRequest(request)
for (authHandler in authorizationHandlers) {
@@ -229,6 +231,7 @@ class AuthorizationClient(
}
}
+
fun cancel() {
if (!authorizationPending) {
return
@@ -348,6 +351,25 @@ class AuthorizationClient(
companion object {
private const val TAG = "Spotify Auth Client"
+ // Hardcoded because RedirectUriReceiverActivity is in the auth flavor source set
+ private const val REDIRECT_ACTIVITY_NAME =
+ "com.spotify.sdk.android.auth.browser.RedirectUriReceiverActivity"
+
+ private fun checkRedirectUriConfiguration(pm: PackageManager, redirectUri: String) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(redirectUri)).apply {
+ addCategory(Intent.CATEGORY_DEFAULT)
+ addCategory(Intent.CATEGORY_BROWSABLE)
+ }
+ val hasRedirectActivity = pm
+ .queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER)
+ .any { it.activityInfo.name == REDIRECT_ACTIVITY_NAME }
+ check(hasRedirectActivity) {
+ "No intent-filter found for RedirectUriReceiverActivity matching redirect URI " +
+ "'$redirectUri'. Add an intent-filter for RedirectUriReceiverActivity " +
+ "to your AndroidManifest.xml. See https://github.com/spotify/android-auth#breaking-changes-in-spotify-auth-library-version-500"
+ }
+ }
+
const val MARKET_VIEW_PATH = "market://"
const val MARKET_SCHEME = "market"
const val MARKET_PATH = "details"
@@ -391,6 +413,7 @@ class AuthorizationClient(
*/
@JvmStatic
fun openLoginInBrowser(contextActivity: Activity, request: AuthorizationRequest) {
+ checkRedirectUriConfiguration(contextActivity.packageManager, request.redirectUri)
val launchBrowser = Intent(Intent.ACTION_VIEW, request.toUri())
contextActivity.startActivity(launchBrowser)
}
diff --git a/auth-lib/src/test/AndroidManifest.xml b/auth-lib/src/test/AndroidManifest.xml
new file mode 100644
index 00000000..d8c6e748
--- /dev/null
+++ b/auth-lib/src/test/AndroidManifest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/auth-lib/src/test/java/com/spotify/sdk/android/auth/AuthorizationClientTest.java b/auth-lib/src/test/java/com/spotify/sdk/android/auth/AuthorizationClientTest.java
index 0c95c1c5..cfd57c2f 100644
--- a/auth-lib/src/test/java/com/spotify/sdk/android/auth/AuthorizationClientTest.java
+++ b/auth-lib/src/test/java/com/spotify/sdk/android/auth/AuthorizationClientTest.java
@@ -32,6 +32,7 @@
import org.mockito.Mockito;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
+import static org.robolectric.Shadows.shadowOf;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -53,16 +54,13 @@ public void shouldLaunchIntentForPassedActivity() {
AuthorizationResponse.Type.TOKEN,
"to://me"
).build();
- Activity activity = mock(Activity.class);
-
- Mockito.doNothing().when(activity).startActivity(any(Intent.class));
- ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class);
+ Activity activity = Robolectric.buildActivity(Activity.class).create().get();
AuthorizationClient.openLoginInBrowser(activity, authorizationRequest);
- verify(activity, times(1)).startActivity(captor.capture());
- assertEquals(Intent.ACTION_VIEW, captor.getValue().getAction());
- assertEquals(authorizationRequest.toUri().toString(), captor.getValue().getData().toString());
+ Intent startedIntent = shadowOf(activity).getNextStartedActivity();
+ assertEquals(Intent.ACTION_VIEW, startedIntent.getAction());
+ assertEquals(authorizationRequest.toUri().toString(), startedIntent.getData().toString());
}
@Test(expected = NullPointerException.class)
diff --git a/auth-sample/build.gradle.kts b/auth-sample/build.gradle.kts
index 86d48675..14ce1170 100644
--- a/auth-sample/build.gradle.kts
+++ b/auth-sample/build.gradle.kts
@@ -26,8 +26,8 @@ plugins {
}
android {
- compileSdk = 33
- buildToolsVersion = "33.0.0"
+ compileSdk = 35
+ buildToolsVersion = "35.0.0"
signingConfigs {
getByName("debug") {
@@ -40,15 +40,11 @@ android {
defaultConfig {
applicationId = "com.spotify.sdk.android.authentication.sample"
- minSdk = 16
- targetSdk = 33
+ minSdk = 21
+ targetSdk = 35
versionCode = 1
versionName = "1.0"
- manifestPlaceholders["redirectSchemeName"] = "spotify-sdk"
- manifestPlaceholders["redirectHostName"] = "auth"
- manifestPlaceholders["redirectPathPattern"] = ".*"
-
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
// Specify which auth-lib flavor to use
@@ -66,6 +62,15 @@ android {
}
}
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
lint {
lintConfig = file("${project.rootDir}/config/lint.xml")
quiet = false
diff --git a/auth-sample/src/main/AndroidManifest.xml b/auth-sample/src/main/AndroidManifest.xml
index 36fc44bd..56202d5c 100644
--- a/auth-sample/src/main/AndroidManifest.xml
+++ b/auth-sample/src/main/AndroidManifest.xml
@@ -36,6 +36,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle.kts b/build.gradle.kts
index 0e81e505..435a28ca 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,7 +27,7 @@ buildscript {
google()
}
dependencies {
- classpath("com.android.tools.build:gradle:7.4.2")
+ classpath("com.android.tools.build:gradle:8.7.3")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("org.jetbrains.dokka:dokka-gradle-plugin:1.9.10")
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8049c684..81aa1c04 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists