Skip to content

Commit

Permalink
Support HttpEngine openConnection.
Browse files Browse the repository at this point in the history
Use the JavaCronetProvider from cronet-fallback.
Implement just enough to support URLConnection.
  • Loading branch information
yschimke committed Apr 6, 2024
1 parent f173bcf commit 943976c
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 0 deletions.
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
@@ -1,4 +1,5 @@
[versions]
cronet-fallback = "119.6045.31"
robolectric-nativeruntime-dist-compat = "1.0.9"

# https://developer.android.com/studio/releases
Expand Down Expand Up @@ -128,6 +129,7 @@ play-services-basement = "18.0.1"

[libraries]
android-gradle = { module = "com.android.tools.build:gradle", version.ref = "android-gradle" }
cronet-fallback = { module = "org.chromium.net:cronet-fallback", version.ref = "cronet-fallback" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
spotless-gradle = { module = "com.diffplug.spotless:spotless-plugin-gradle", version.ref = "spotless-gradle" }
detekt-gradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt-gradle" }
Expand Down
3 changes: 3 additions & 0 deletions integration_tests/ctesque/src/main/AndroidManifest.xml
Expand Up @@ -3,6 +3,9 @@
Manifest for ctesque tests
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required for HttpEngineTest -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application />
</manifest>
@@ -0,0 +1,32 @@
package android.net.http;

import android.content.Context;
import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import java.net.URL;
import java.net.URLConnection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.internal.DoNotInstrument;

@DoNotInstrument
@RunWith(AndroidJUnit4.class)
@Config(minSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public class HttpEngineTest {
@Test
public void shouldSupportHttpClient() throws Exception {
Context context = ApplicationProvider.getApplicationContext();

HttpEngine engine = new HttpEngine.Builder(context).build();

URLConnection con = engine.openConnection(new URL("https://google.com/robots.txt"));

// Check how to test network in robolectric builds
// String response = new String(con.getInputStream().readAllBytes());
// assertThat(response).contains("Disallow");
}
}
2 changes: 2 additions & 0 deletions shadows/framework/build.gradle
Expand Up @@ -57,6 +57,8 @@ dependencies {

api "androidx.test:monitor:$axtMonitorVersion@aar"

implementation(libs.cronet.fallback)

implementation libs.error.prone.annotations
compileOnly libs.findbugs.jsr305
api libs.sqlite4java
Expand Down
@@ -0,0 +1,194 @@
package org.robolectric.shadows;

import android.annotation.NonNull;
import android.content.Context;
import android.net.Network;
import android.net.http.ExperimentalBidirectionalStream;
import android.net.http.ExperimentalHttpEngine;
import android.net.http.ExperimentalUrlRequest;
import android.net.http.HttpEngine;
import android.net.http.IHttpEngineBuilder;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandlerFactory;
import java.time.Instant;
import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.Executor;
import org.chromium.net.ExperimentalCronetEngine;
import org.chromium.net.impl.JavaCronetProvider;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.versioning.AndroidVersions.U;

@SuppressWarnings({"NewApi", "deprecation"})
@Implements(value = HttpEngine.Builder.class, minSdk = U.SDK_INT)
public class ShadowHttpEngine {

@Implementation
protected static IHttpEngineBuilder createBuilderDelegate(Context context)
throws ClassNotFoundException {
return new CronetEngineBuilderWrapper(context);
}

static class CronetEngineBuilderWrapper extends IHttpEngineBuilder {
private final ExperimentalCronetEngine.Builder backend;

public CronetEngineBuilderWrapper(Context context) {
this.backend =
(ExperimentalCronetEngine.Builder) new JavaCronetProvider(context).createBuilder();
}

@Override
public String getDefaultUserAgent() {
return backend.getDefaultUserAgent();
}

@Override
public IHttpEngineBuilder setUserAgent(String userAgent) {
backend.setUserAgent(userAgent);
return this;
}

@Override
public IHttpEngineBuilder setStoragePath(String value) {
backend.setStoragePath(value);
return this;
}

@Override
public IHttpEngineBuilder addQuicHint(String host, int port, int alternatePort) {
backend.addQuicHint(host, port, alternatePort);
return this;
}

@Override
public IHttpEngineBuilder addPublicKeyPins(
String hostName,
Set<byte[]> pinsSha256,
boolean includeSubdomains,
Instant expirationInstant) {
backend.addPublicKeyPins(
hostName, pinsSha256, includeSubdomains, Date.from(expirationInstant));
return this;
}

@Override
public IHttpEngineBuilder setQuicOptions(@NonNull android.net.http.QuicOptions options) {
return this;
}

@Override
public IHttpEngineBuilder setDnsOptions(@NonNull android.net.http.DnsOptions options) {
return this;
}

@Override
public IHttpEngineBuilder setConnectionMigrationOptions(
@NonNull android.net.http.ConnectionMigrationOptions options) {
return this;
}

@Override
public IHttpEngineBuilder enableBrotli(boolean value) {
return this;
}

@Override
protected Set<Integer> getSupportedConfigOptions() {
return Collections.emptySet();
}

@Override
public IHttpEngineBuilder enableNetworkQualityEstimator(boolean value) {
return this;
}

@Override
public IHttpEngineBuilder setThreadPriority(int priority) {
return this;
}

@Override
public IHttpEngineBuilder enableHttp2(boolean b) {
return this;
}

@Override
public IHttpEngineBuilder enableHttpCache(int i, long l) {
return this;
}

@Override
public IHttpEngineBuilder enablePublicKeyPinningBypassForLocalTrustAnchors(boolean b) {
return this;
}

@Override
public IHttpEngineBuilder enableQuic(boolean b) {
return this;
}

@Override
public IHttpEngineBuilder enableSdch(boolean b) {
return this;
}

@Override
public IHttpEngineBuilder setExperimentalOptions(String s) {
return this;
}

public ExperimentalHttpEngine build() {
return new CronetEngineWrapper(backend.build());
}
}

static class CronetEngineWrapper extends ExperimentalHttpEngine {

private final ExperimentalCronetEngine backend;

public CronetEngineWrapper(ExperimentalCronetEngine backend) {
this.backend = backend;
}

@Override
public void shutdown() {
backend.shutdown();
}

@Override
public URLConnection openConnection(URL url) throws IOException {
return backend.openConnection(url);
}

@Override
public URLStreamHandlerFactory createUrlStreamHandlerFactory() {
return backend.createURLStreamHandlerFactory();
}

@Override
public void bindToNetwork(Network network) {
long networkHandle = backend.UNBIND_NETWORK_HANDLE;
if (network != null) {
networkHandle = network.getNetworkHandle();
}
backend.bindToNetwork(networkHandle);
}

@Override
public ExperimentalBidirectionalStream.Builder newBidirectionalStreamBuilder(
String url, Executor executor, android.net.http.BidirectionalStream.Callback callback) {
throw new UnsupportedOperationException();
}

@Override
public ExperimentalUrlRequest.Builder newUrlRequestBuilder(
String url, Executor executor, android.net.http.UrlRequest.Callback callback) {
// TODO
throw new UnsupportedOperationException();
}
}
}

0 comments on commit 943976c

Please sign in to comment.