chromium/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabNavigationEventObserver.java

// Copyright 2018 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.chrome.browser.customtabs;

import androidx.browser.customtabs.CustomTabsCallback;
import androidx.browser.customtabs.CustomTabsSessionToken;

import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.dependency_injection.ActivityScope;
import org.chromium.chrome.browser.tab.EmptyTabObserver;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabHidingType;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.net.NetError;
import org.chromium.url.GURL;

import java.util.Optional;

import javax.inject.Inject;

/** An observer for firing navigation events on {@link CustomTabsCallback}. */
@ActivityScope
public class CustomTabNavigationEventObserver extends EmptyTabObserver {
    // An operation was aborted (due to user action). Should match the value in net_error_list.h.
    private static final int NET_ERROR_ABORTED = -3;

    private final CustomTabsSessionToken mSessionToken;
    private final CustomTabsConnection mConnection;

    @Inject
    public CustomTabNavigationEventObserver(
            BrowserServicesIntentDataProvider intentDataProvider, CustomTabsConnection connection) {
        mSessionToken = intentDataProvider.getSession();
        mConnection = connection;
    }

    @Override
    public void onPageLoadStarted(Tab tab, GURL url) {
        mConnection.notifyNavigationEvent(mSessionToken, CustomTabsCallback.NAVIGATION_STARTED);
    }

    @Override
    public void onPageLoadFinished(Tab tab, GURL url) {
        mConnection.notifyNavigationEvent(mSessionToken, CustomTabsCallback.NAVIGATION_FINISHED);
    }

    @Override
    @SuppressWarnings("TraditionalSwitchExpression")
    public void onPageLoadFailed(Tab tab, int errorCode) {
        int navigationEvent =
                errorCode == NET_ERROR_ABORTED
                        ? CustomTabsCallback.NAVIGATION_ABORTED
                        : CustomTabsCallback.NAVIGATION_FAILED;

        // For privacy reason, we do not pass all the error codes but choose a few safe ones.
        // See crbug/1501085 for more details.
        Optional<Integer> code =
                switch (errorCode) {
                    case NetError.ERR_INTERNET_DISCONNECTED:
                    case NetError.ERR_CONNECTION_TIMED_OUT:
                    case NetError.ERR_NAME_RESOLUTION_FAILED:
                        yield Optional.of(getReportErrorCode(errorCode));
                    default:
                        yield Optional.empty();
                };

        mConnection.notifyNavigationEvent(mSessionToken, navigationEvent, code);
        RecordHistogram.recordSparseHistogram("CustomTabs.PageNavigation.ErrorCode", errorCode);
    }

    static int getReportErrorCode(int code) {
        return -code + 100;
    }

    @Override
    public void onShown(Tab tab, @TabSelectionType int type) {
        mConnection.notifyNavigationEvent(mSessionToken, CustomTabsCallback.TAB_SHOWN);
    }

    @Override
    public void onHidden(Tab tab, @TabHidingType int type) {
        mConnection.notifyNavigationEvent(mSessionToken, CustomTabsCallback.TAB_HIDDEN);
    }
}