Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@
build/
docs/
docs-store/
auth-lib/stdout
auth-sample/stdout
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
52 changes: 27 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,32 @@

[![Maven Central](https://img.shields.io/maven-central/v/com.spotify.android/auth.svg)](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
<activity
android:name="com.spotify.sdk.android.auth.browser.RedirectUriReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="your-redirect-scheme"
android:host="your-redirect-host"
android:pathPattern="your-path-pattern"/>
</intent-filter>
</activity>
```

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 `<intent-filter>` 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.
Expand All @@ -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).
Expand Down
34 changes: 15 additions & 19 deletions auth-lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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}\"")
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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")
}

/*
Expand Down
12 changes: 1 addition & 11 deletions auth-lib/src/auth/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,7 @@
<activity
android:name="com.spotify.sdk.android.auth.browser.RedirectUriReceiverActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="${redirectHostName}"
android:scheme="${redirectSchemeName}"
android:pathPattern="${redirectPathPattern}"/>
</intent-filter>
</activity>
android:exported="true"/>

</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
}
}
6 changes: 6 additions & 0 deletions auth-lib/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
<action android:name="com.spotify.sso.action.START_AUTH_FLOW" />
<data android:mimeType="text/plain" />
</intent>

<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
</queries>

<application>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ class AuthorizationClient(
if (authorizationPending) return
authorizationPending = true

checkRedirectUriConfiguration(loginActivity.packageManager, request.redirectUri)

val processedRequest = validateAndConvertTokenRequest(request)

for (authHandler in authorizationHandlers) {
Expand All @@ -229,6 +231,7 @@ class AuthorizationClient(
}
}


fun cancel() {
if (!authorizationPending) {
return
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
36 changes: 36 additions & 0 deletions auth-lib/src/test/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="com.spotify.sdk.android.auth.browser.RedirectUriReceiverActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="test"
android:host="test"
android:pathPattern=".*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="redirect"
android:host="uri"
android:pathPattern=".*"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:scheme="to"
android:host="me"
android:pathPattern=".*"/>
</intent-filter>
</activity>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Intent> 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)
Expand Down
21 changes: 13 additions & 8 deletions auth-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ plugins {
}

android {
compileSdk = 33
buildToolsVersion = "33.0.0"
compileSdk = 35
buildToolsVersion = "35.0.0"

signingConfigs {
getByName("debug") {
Expand All @@ -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
Expand All @@ -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
Expand Down
Loading
Loading