Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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.100"
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")
```
8 changes: 8 additions & 0 deletions okhttp-android/api/okhttp-android.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
public final class okhttp3/android/AndroidAsyncDns : okhttp3/AsyncDns {
public fun <init> ()V
public fun <init> (Landroid/net/Network;Ljava/util/List;)V
public synthetic fun <init> (Landroid/net/Network;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun asDns ()Lokhttp3/Dns;
public fun query (Ljava/lang/String;Lokhttp3/AsyncDns$Callback;)V
}

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,189 @@
/*
* 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.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 dns = AndroidAsyncDns()

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(dns.asDns())
.sslSocketFactory(localhost.sslSocketFactory(), localhost.trustManager)
.build()

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

@Test
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
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)

dns.query(hostname, object : AsyncDns.Callback {
override fun onAddressResults(dnsClass: AsyncDns.DnsClass, addresses: List<InetAddress>) {
allAddresses.addAll(addresses)
}

override fun onComplete() {
latch.countDown()
}

override fun onError(dnsClass: AsyncDns.DnsClass, 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(AndroidAsyncDns(network = network, dnsClasses = listOf(AsyncDns.DnsClass.IPV4)).asDns())
.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