chromium/components/cronet/android/java/src/org/chromium/net/impl/AndroidUrlRequestCallbackWrapper.java

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net.impl;

import static org.chromium.net.impl.HttpEngineNativeProvider.EXT_API_LEVEL;
import static org.chromium.net.impl.HttpEngineNativeProvider.EXT_VERSION;

import android.net.http.HttpException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresExtension;

import org.chromium.net.CronetException;
import org.chromium.net.RequestFinishedInfo;

import java.nio.ByteBuffer;
import java.util.Objects;

@RequiresExtension(extension = EXT_API_LEVEL, version = EXT_VERSION)
@SuppressWarnings("Override")
class AndroidUrlRequestCallbackWrapper implements android.net.http.UrlRequest.Callback {
    private final org.chromium.net.UrlRequest.Callback mBackend;
    private AndroidUrlRequestWrapper mWrappedRequest;

    public AndroidUrlRequestCallbackWrapper(org.chromium.net.UrlRequest.Callback backend) {
        this.mBackend = Objects.requireNonNull(backend, "Callback is required.");
    }

    /**
     * @see <a
     *     href="https://developer.android.com/training/basics/network-ops/reading-network-state#listening-events">Foo
     *     Bar</a>
     */
    @Override
    public void onRedirectReceived(
            android.net.http.UrlRequest request,
            android.net.http.UrlResponseInfo info,
            String newLocationUrl)
            throws Exception {
        CronetExceptionTranslationUtils.executeTranslatingCronetExceptions(
                () -> {
                    AndroidUrlResponseInfoWrapper specializedResponseInfo =
                            AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
                    mBackend.onRedirectReceived(
                            mWrappedRequest, specializedResponseInfo, newLocationUrl);
                    return null;
                },
                Exception.class);
    }

    @Override
    public void onResponseStarted(
            android.net.http.UrlRequest request, android.net.http.UrlResponseInfo info)
            throws Exception {
        CronetExceptionTranslationUtils.executeTranslatingCronetExceptions(
                () -> {
                    AndroidUrlResponseInfoWrapper specializedResponseInfo =
                            AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
                    mBackend.onResponseStarted(mWrappedRequest, specializedResponseInfo);
                    return null;
                },
                Exception.class);
    }

    @Override
    public void onReadCompleted(
            android.net.http.UrlRequest request,
            android.net.http.UrlResponseInfo info,
            ByteBuffer byteBuffer)
            throws Exception {
        CronetExceptionTranslationUtils.executeTranslatingCronetExceptions(
                () -> {
                    AndroidUrlResponseInfoWrapper specializedResponseInfo =
                            AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
                    mBackend.onReadCompleted(mWrappedRequest, specializedResponseInfo, byteBuffer);
                    return null;
                },
                Exception.class);
    }

    @Override
    public void onSucceeded(
            android.net.http.UrlRequest request, android.net.http.UrlResponseInfo info) {
        AndroidUrlResponseInfoWrapper specializedResponseInfo =
                AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
        try {
            mBackend.onSucceeded(mWrappedRequest, specializedResponseInfo);
        } finally {
            // In a scenario where this throws, the side effect is that it will be propagated to
            // CronetUrlRequest as an error in the callback and mess with the FinalUserCallbackThrew
            // metrics. Because we catch most the exceptions, this side effect is negligible enough
            // to
            // not try to figure out a workaround.
            mWrappedRequest.maybeReportMetrics(
                    RequestFinishedInfo.SUCCEEDED, specializedResponseInfo, null);
        }
    }

    @Override
    public void onFailed(
            android.net.http.UrlRequest request,
            android.net.http.UrlResponseInfo info,
            HttpException error) {
        AndroidUrlResponseInfoWrapper specializedResponseInfo =
                AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
        CronetException translatedException =
                CronetExceptionTranslationUtils.translateCheckedAndroidCronetException(error);
        try {
            mBackend.onFailed(mWrappedRequest, specializedResponseInfo, translatedException);
        } finally {
            // See comment in onSucceeded.
            mWrappedRequest.maybeReportMetrics(
                    RequestFinishedInfo.FAILED, specializedResponseInfo, translatedException);
        }
    }

    @Override
    public void onCanceled(
            @NonNull android.net.http.UrlRequest request,
            @Nullable android.net.http.UrlResponseInfo info) {
        AndroidUrlResponseInfoWrapper specializedResponseInfo =
                AndroidUrlResponseInfoWrapper.createForUrlRequest(info);
        try {
            mBackend.onCanceled(mWrappedRequest, specializedResponseInfo);
        } finally {
            // See comment in onSucceeded.
            mWrappedRequest.maybeReportMetrics(
                    RequestFinishedInfo.CANCELED, specializedResponseInfo, null);
        }
    }

    void setRequest(AndroidUrlRequestWrapper request) {
        mWrappedRequest = request;
    }
}