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: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ jobs:
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
script: ./gradlew -PandroidBuild :android-test:connectedCheck
script: ./gradlew -PandroidBuild :okhttp-android:connectedCheck :android-test:connectedCheck
env:
API_LEVEL: ${{ matrix.api-level }}

Expand Down
2 changes: 2 additions & 0 deletions android-test/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ dependencies {
implementation(libs.kotlin.reflect)
implementation(libs.playservices.safetynet)
implementation(projects.okhttp)
implementation(projects.okhttpAndroid)

androidTestImplementation(projects.okhttpTestingSupport) {
exclude("org.openjsse", "openjsse")
Expand All @@ -64,6 +65,7 @@ dependencies {
androidTestImplementation(projects.loggingInterceptor)
androidTestImplementation(projects.okhttpSse)
androidTestImplementation(projects.okhttpTls)
androidTestImplementation(projects.okhttpAndroid)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.httpClient5)
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ subprojects {
val project = this@subprojects
if (project.name == "okhttp-bom") return@subprojects

if (project.name == "okhttp-android") return@subprojects
if (project.name == "android-test") return@subprojects
if (project.name == "regression-test") return@subprojects

Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ picocli = "4.6.3"

[libraries]
amazonCorretto = "software.amazon.cryptools:AmazonCorrettoCryptoProvider:1.6.1"
androidx-annotation = "androidx.annotation:annotation:1.3.0"
androidx-espresso-core = "androidx.test.espresso:espresso-core:3.4.0"
androidx-junit = "androidx.test.ext:junit:1.1.3"
androidx-test-runner = "androidx.test:runner:1.4.0"
Expand All @@ -31,7 +32,7 @@ conscrypt-android = { module = "org.conscrypt:conscrypt-android", version.ref =
conscrypt-openjdk = { module = "org.conscrypt:conscrypt-openjdk-uber", version.ref = "org-conscrypt" }
eclipseOsgi = "org.eclipse.platform:org.eclipse.osgi:3.17.200"
findbugs-jsr305 = "com.google.code.findbugs:jsr305:3.0.2"
gradlePlugin-android = "com.android.tools.build:gradle:7.1.2"
gradlePlugin-android = "com.android.tools.build:gradle:7.0.4"
gradlePlugin-androidJunit5 = "de.mannodermaus.gradle.plugins:android-junit5:1.8.2.0"
gradlePlugin-animalsniffer = "ru.vyarus:gradle-animalsniffer-plugin:1.5.4"
gradlePlugin-binaryCompatibilityValidator = "org.jetbrains.kotlinx.binary-compatibility-validator:org.jetbrains.kotlinx.binary-compatibility-validator.gradle.plugin:0.8.0"
Expand Down
3 changes: 3 additions & 0 deletions okhttp-android/Module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Module okhttp-android

OkHttp Android library.
13 changes: 13 additions & 0 deletions okhttp-android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
OkHttp Android
==============

Enhanced APIs for using OkHttp on Android.

At the moment, this will mostly likely only be useful for fastFallback.

Download
--------

```kotlin
implementation("com.squareup.okhttp3:okhttp-android:4.9.3")
```
12 changes: 12 additions & 0 deletions okhttp-android/api/okhttp-android.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
public final class okhttp3/android/AndroidAsyncDns : okhttp3/AsyncDns {
public static final field Companion Lokhttp3/android/AndroidAsyncDns$Companion;
public fun <init> (Lokhttp3/AsyncDns$DnsClass;Landroid/net/Network;)V
public synthetic fun <init> (Lokhttp3/AsyncDns$DnsClass;Landroid/net/Network;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun query (Ljava/lang/String;Lokhttp3/AsyncDns$Callback;)V
}

public final class okhttp3/android/AndroidAsyncDns$Companion {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not nice from java, but as these are Android specific, I think Kotlin is a good assumption.

public final fun getIPv4 ()Lokhttp3/android/AndroidAsyncDns;
public final fun getIPv6 ()Lokhttp3/android/AndroidAsyncDns;
}

66 changes: 66 additions & 0 deletions okhttp-android/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import com.vanniktech.maven.publish.JavadocJar

plugins {
id("com.android.library")
kotlin("android")
id("de.mannodermaus.android-junit5")
id("org.jetbrains.dokka")
id("com.vanniktech.maven.publish.base")
id("binary-compatibility-validator")
}

android {
compileSdk = 31

defaultConfig {
minSdk = 21
targetSdk = 31

// Make sure to use the AndroidJUnitRunner (or a sub-class) in order to hook in the JUnit 5 Test Builder
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
testInstrumentationRunnerArguments += mapOf(
"runnerBuilder" to "de.mannodermaus.junit5.AndroidJUnit5Builder",
"notPackage" to "org.bouncycastle"
)

buildFeatures {
buildConfig = false
}
}

compileOptions {
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_11)
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
}

dependencies {
api(libs.squareup.okio)
api(projects.okhttp)
compileOnly(libs.androidx.annotation)
compileOnly(libs.findbugs.jsr305)
compileOnly(libs.animalsniffer.annotations)
compileOnly(libs.robolectric.android)

testImplementation(projects.okhttpTestingSupport)
testImplementation(projects.mockwebserver3Junit5)
testImplementation(libs.junit)
testImplementation(libs.assertj.core)

androidTestImplementation(projects.okhttpTls)
androidTestImplementation(libs.assertj.core)
androidTestImplementation(projects.mockwebserver3Junit5)
androidTestImplementation(libs.androidx.test.runner)
androidTestImplementation(libs.junit.jupiter.api)
androidTestImplementation(libs.junit5android.core)
androidTestRuntimeOnly(libs.junit5android.runner)
}

mavenPublishing {
@Suppress("DEPRECATION") // Requires AGP 7.1.x
configure(com.vanniktech.maven.publish.AndroidLibrary(javadocJar = JavadocJar.Dokka("dokkaGfm")))
}
6 changes: 6 additions & 0 deletions okhttp-android/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="okhttp.android.test">

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package okhttp3.android

import android.content.Context
import android.net.ConnectivityManager
import androidx.test.platform.app.InstrumentationRegistry
import java.net.InetAddress
import java.net.UnknownHostException
import java.util.concurrent.CountDownLatch
import mockwebserver3.MockResponse
import mockwebserver3.MockWebServer
import mockwebserver3.junit5.internal.MockWebServerExtension
import okhttp3.AsyncDns
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.tls.HandshakeCertificates
import okhttp3.tls.HeldCertificate
import okio.IOException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.fail
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.opentest4j.TestAbortedException

/**
* Run with "./gradlew :android-test:connectedCheck" and make sure ANDROID_SDK_ROOT is set.
*/
@ExtendWith(MockWebServerExtension::class)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay!

class AndroidAsyncDnsTest(val server: MockWebServer) {
private val localhostName: String = InetAddress.getByName("localhost").canonicalHostName

private val localhost: HandshakeCertificates by lazy {
// Generate a self-signed cert for the server to serve and the client to trust.
val heldCertificate = HeldCertificate.Builder()
.commonName("localhost")
.addSubjectAlternativeName(localhostName)
.build()
return@lazy HandshakeCertificates.Builder()
.addPlatformTrustedCertificates()
.heldCertificate(heldCertificate)
.addTrustedCertificate(heldCertificate.certificate)
.build()
}

private val client = OkHttpClient.Builder()
.dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
.sslSocketFactory(localhost.sslSocketFactory(), localhost.trustManager)
.build()

@BeforeEach
fun init() {
server.useHttps(localhost.sslSocketFactory(), false)
}

@Test
@Disabled("java.net.UnknownHostException: No results for localhost, in CI.")
fun testRequest() {
server.enqueue(MockResponse())

val call = client.newCall(Request.Builder().url(server.url("/")).build())

call.execute().use { response ->
assertThat(response.code).isEqualTo(200)
}
}

@Test
fun testRequestExternal() {
assumeNetwork()

val call = client.newCall(Request.Builder().url("https://google.com/robots.txt").build())

call.execute().use { response ->
assertThat(response.code).isEqualTo(200)
}
}

@Test
fun testRequestInvalid() {
val call = client.newCall(Request.Builder().url("https://google.invalid/").build())

try {
call.execute()
fail("Request can't succeed")
} catch (ioe: IOException) {
assertThat(ioe).hasMessage("No results for google.invalid")
}
}

@Test
@Disabled("No results on CI for localhost")
fun testDnsRequest() {
val (allAddresses, exception) = dnsQuery(localhostName)

assertThat(exception).isNull()
assertThat(allAddresses).isNotEmpty
}

private fun dnsQuery(hostname: String): Pair<List<InetAddress>, Exception?> {
val allAddresses = mutableListOf<InetAddress>()
var exception: Exception? = null
val latch = CountDownLatch(1)

// assumes an IPv4 address
AndroidAsyncDns.IPv4.query(hostname, object : AsyncDns.Callback {
override fun onResponse(hostname: String, addresses: List<InetAddress>) {
allAddresses.addAll(addresses)
latch.countDown()
}

override fun onFailure(hostname: String, e: IOException) {
exception = e
latch.countDown()
}
})

latch.await()

return Pair(allAddresses, exception)
}

@Test
fun testDnsRequestExternal() {
assumeNetwork()

val (allAddresses, exception) = dnsQuery("google.com")

assertThat(exception).isNull()
assertThat(allAddresses).isNotEmpty
}

@Test
fun testDnsRequestInvalid() {
val (allAddresses, exception) = dnsQuery("google.invalid")

assertThat(exception).isNull()
assertThat(allAddresses).isEmpty()
}

@Test
fun testRequestOnNetwork() {
assumeNetwork()

val context = InstrumentationRegistry.getInstrumentation().context
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

val network =
connectivityManager.activeNetwork ?: throw TestAbortedException("No active network")

val client = OkHttpClient.Builder()
.dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
.socketFactory(network.socketFactory)
.build()

val call =
client.newCall(Request.Builder().url("https://google.com/robots.txt").build())

call.execute().use { response ->
assertThat(response.code).isEqualTo(200)
}
}

private fun assumeNetwork() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

try {
InetAddress.getByName("www.google.com")
} catch (uhe: UnknownHostException) {
throw TestAbortedException(uhe.message, uhe)
}
}
}
6 changes: 6 additions & 0 deletions okhttp-android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="okhttp.android.test">

<uses-permission android:name="android.permission.INTERNET" />

</manifest>
Loading