chromium/android_webview/java/src/org/chromium/android_webview/AwSettings.java

// Copyright 2012 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.android_webview;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Message;
import android.os.Process;
import android.provider.Settings;
import android.webkit.WebSettings;

import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;
import org.jni_zero.NativeMethods;

import org.chromium.android_webview.client_hints.AwUserAgentMetadata;
import org.chromium.android_webview.common.AwFeatures;
import org.chromium.android_webview.common.AwSwitches;
import org.chromium.android_webview.common.Lifetime;
import org.chromium.android_webview.common.MediaIntegrityApiStatus;
import org.chromium.android_webview.metrics.BackForwardCacheNotRestoredReason;
import org.chromium.android_webview.safe_browsing.AwSafeBrowsingConfigHelper;
import org.chromium.android_webview.settings.AttributionBehavior;
import org.chromium.android_webview.settings.ForceDarkBehavior;
import org.chromium.android_webview.settings.ForceDarkMode;
import org.chromium.android_webview.settings.SpeculativeLoadingAllowedFlags;
import org.chromium.base.CommandLine;
import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.components.webauthn.WebauthnMode;
import org.chromium.components.webauthn.WebauthnModeProvider;
import org.chromium.content_public.browser.WebContents;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * Stores Android WebView specific settings that does not need to be synced to WebKit.
 *
 * <p>Methods in this class can be called from any thread, including threads created by the client
 * of WebView.
 *
 * <p>Flushing the BFCache is required if a settings property is changed.
 */
@Lifetime.WebView
@JNINamespace("android_webview")
public class AwSettings {
    private static final String TAG = "AwSettings";
    private static final boolean TRACE = false;

    /* See {@link android.webkit.WebSettings}. */
    @Retention(RetentionPolicy.SOURCE)
    @IntDef({
        LAYOUT_ALGORITHM_NORMAL,
        /* See {@link android.webkit.WebSettings}. */
        LAYOUT_ALGORITHM_SINGLE_COLUMN,
        /* See {@link android.webkit.WebSettings}. */
        LAYOUT_ALGORITHM_NARROW_COLUMNS,
        LAYOUT_ALGORITHM_TEXT_AUTOSIZING
    })
    public @interface LayoutAlgorithm {}

    public static final int LAYOUT_ALGORITHM_NORMAL = 0;
    /* See {@link android.webkit.WebSettings}. */
    public static final int LAYOUT_ALGORITHM_SINGLE_COLUMN = 1;
    /* See {@link android.webkit.WebSettings}. */
    public static final int LAYOUT_ALGORITHM_NARROW_COLUMNS = 2;
    public static final int LAYOUT_ALGORITHM_TEXT_AUTOSIZING = 3;

    public static final int FORCE_DARK_OFF = ForceDarkMode.FORCE_DARK_OFF;
    public static final int FORCE_DARK_AUTO = ForceDarkMode.FORCE_DARK_AUTO;
    public static final int FORCE_DARK_ON = ForceDarkMode.FORCE_DARK_ON;
    public static final int FORCE_DARK_MODES_COUNT = 3;

    @ForceDarkMode private int mForceDarkMode = ForceDarkMode.FORCE_DARK_AUTO;

    private boolean mAlgorithmicDarkeningAllowed;

    public static final int FORCE_DARK_ONLY = ForceDarkBehavior.FORCE_DARK_ONLY;
    public static final int MEDIA_QUERY_ONLY = ForceDarkBehavior.MEDIA_QUERY_ONLY;
    // This option requires RuntimeEnabledFeatures::MetaColorSchemeEnabled()
    public static final int PREFER_MEDIA_QUERY_OVER_FORCE_DARK =
            ForceDarkBehavior.PREFER_MEDIA_QUERY_OVER_FORCE_DARK;
    public static final int FORCE_DARK_STRATEGY_COUNT = 3;

    @ForceDarkBehavior
    private int mForceDarkBehavior = ForceDarkBehavior.PREFER_MEDIA_QUERY_OVER_FORCE_DARK;

    @AttributionBehavior
    public static final int ATTRIBUTION_DISABLED = AttributionBehavior.DISABLED;

    @AttributionBehavior
    public static final int ATTRIBUTION_APP_SOURCE_AND_WEB_TRIGGER =
            AttributionBehavior.APP_SOURCE_AND_WEB_TRIGGER;

    @AttributionBehavior
    public static final int ATTRIBUTION_WEB_SOURCE_AND_WEB_TRIGGER =
            AttributionBehavior.WEB_SOURCE_AND_WEB_TRIGGER;

    @AttributionBehavior
    public static final int ATTRIBUTION_APP_SOURCE_AND_APP_TRIGGER =
            AttributionBehavior.APP_SOURCE_AND_APP_TRIGGER;

    private Set<String> mRequestedWithHeaderAllowedOriginRules;

    private Context mContext;
    private WebContents mWebContents;

    // This class must be created on the UI thread. Afterwards, it can be
    // used from any thread. Internally, the class uses a message queue
    // to call native code on the UI thread only.

    // Values passed in on construction.
    private final boolean mHasInternetPermission;

    private ZoomSupportChangeListener mZoomChangeListener;
    private double mDIPScale = 1.0;

    // Lock to protect all settings.
    private final Object mAwSettingsLock = new Object();

    @LayoutAlgorithm private int mLayoutAlgorithm = LAYOUT_ALGORITHM_NARROW_COLUMNS;
    private int mTextSizePercent = 100;
    private String mStandardFontFamily = "sans-serif";
    private String mFixedFontFamily = "monospace";
    private String mSansSerifFontFamily = "sans-serif";
    private String mSerifFontFamily = "serif";
    private String mCursiveFontFamily = "cursive";
    private String mFantasyFontFamily = "fantasy";
    private String mDefaultTextEncoding = "UTF-8";
    private String mUserAgent;
    private AwUserAgentMetadata mAwUserAgentMetadata;
    private boolean mHasUserAgentMetadataOverrides;
    private int mMinimumFontSize = 8;
    private int mMinimumLogicalFontSize = 8;
    private int mDefaultFontSize = 16;
    private int mDefaultFixedFontSize = 13;
    private boolean mLoadsImagesAutomatically = true;
    private boolean mImagesEnabled = true;
    private boolean mJavaScriptEnabled;
    private boolean mAllowUniversalAccessFromFileURLs;
    private boolean mAllowFileAccessFromFileURLs;
    private boolean mJavaScriptCanOpenWindowsAutomatically;
    private boolean mSupportMultipleWindows;
    private boolean mDomStorageEnabled;
    private boolean mDatabaseEnabled;
    private boolean mUseWideViewport;
    private boolean mZeroLayoutHeightDisablesViewportQuirk;
    private boolean mForceZeroLayoutHeight;
    private boolean mLoadWithOverviewMode;
    private boolean mMediaPlaybackRequiresUserGesture = true;
    private String mDefaultVideoPosterURL;
    private float mInitialPageScalePercent;
    private boolean mSpatialNavigationEnabled; // Default depends on device features.
    private boolean mEnableSupportedHardwareAcceleratedFeatures;
    private int mMixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW;
    private int mAttributionBehavior = AttributionBehavior.APP_SOURCE_AND_WEB_TRIGGER;

    @SpeculativeLoadingAllowedFlags
    private int mSpeculativeLoadingAllowedFlags =
            SpeculativeLoadingAllowedFlags.SPECULATIVE_LOADING_DISABLED;

    private boolean mHasCalledSetSpeculativeLoadingAllowedBefore;

    // Enabling this setting or the kWebViewBackForwardCache feature will enable BFCache
    // in WebView.
    private boolean mBackForwardCacheEnabled;
    private boolean mHasCalledSetBackForwardCacheEnabledBefore;

    private boolean mCSSHexAlphaColorEnabled;
    private boolean mScrollTopLeftInteropEnabled;
    private boolean mWillSuppressErrorPage;

    private boolean mOffscreenPreRaster;
    private int mDisabledMenuItems = WebSettings.MENU_ITEM_NONE;

    // Although this bit is stored on AwSettings it is actually controlled via the CookieManager.
    private boolean mAcceptThirdPartyCookies;

    // if null, default to AwSafeBrowsingConfigHelper.getSafeBrowsingEnabledByManifest()
    private Boolean mSafeBrowsingEnabled;

    private final boolean mSupportLegacyQuirks;
    private final boolean mAllowEmptyDocumentPersistence;
    private final boolean mAllowGeolocationOnInsecureOrigins;
    private final boolean mDoNotUpdateSelectionOnMutatingSelectionRange;

    private final boolean mPasswordEchoEnabled;

    // Not accessed by the native side.
    private boolean mBlockSpecialFileUrls;
    private boolean mBlockNetworkLoads; // Default depends on permission of embedding APK.
    private boolean mAllowContentUrlAccess = true;
    private boolean mAllowFileUrlAccess;
    private int mCacheMode = WebSettings.LOAD_DEFAULT;
    private boolean mShouldFocusFirstNode = true;
    private boolean mGeolocationEnabled = true;
    private boolean mFullscreenSupported;
    private boolean mSupportZoom = true;
    private boolean mBuiltInZoomControls;
    private boolean mDisplayZoomControls = true;
    private final AwMediaIntegrityApiStatusConfig mIntegrityApiStatusConfig;

    private @WebauthnMode int mWebauthnMode = WebauthnMode.NONE;

    // Cache default user agent string obtained through JNI, since it will not change during the
    // process lifetime. This saves a JNI call when creating new AwSettings objects after the first
    // one in the process, and when client code asks for the default UA.
    static class LazyDefaultUserAgent {
        // Lazy Holder pattern
        private static final String sInstance = AwSettingsJni.get().getDefaultUserAgent();
    }

    // Cache default user agent metadata obtained through JNI.
    static class LazyDefaultUserAgentMetadata {
        // Lazy Holder pattern
        private static final AwUserAgentMetadata sInstance =
                AwSettingsJni.get().getDefaultUserAgentMetadata();
    }

    // Protects access to settings global fields.
    private static final Object sGlobalContentSettingsLock = new Object();

    // The native side of this object. It's lifetime is bounded by the WebContent it is attached to.
    private long mNativeAwSettings;

    // Custom handler that queues messages to call native code on the UI thread.
    private final EventHandler mEventHandler;

    private static final int MINIMUM_FONT_SIZE = 1;
    private static final int MAXIMUM_FONT_SIZE = 72;

    // Class to handle messages to be processed on the UI thread.
    private class EventHandler {
        // Message id for running a Runnable with mAwSettingsLock held.
        private static final int RUN_RUNNABLE_BLOCKING = 0;
        // Actual UI thread handler
        private Handler mHandler;
        // Synchronization flag.
        private boolean mSynchronizationPending;

        EventHandler() {}

        @SuppressLint("HandlerLeak")
        void bindUiThread() {
            if (mHandler != null) return;
            mHandler =
                    new Handler(ThreadUtils.getUiThreadLooper()) {
                        @Override
                        public void handleMessage(Message msg) {
                            switch (msg.what) {
                                case RUN_RUNNABLE_BLOCKING:
                                    synchronized (mAwSettingsLock) {
                                        if (mNativeAwSettings != 0) {
                                            ((Runnable) msg.obj).run();
                                        }
                                        mSynchronizationPending = false;
                                        mAwSettingsLock.notifyAll();
                                    }
                                    break;
                            }
                        }
                    };
        }

        void runOnUiThreadBlockingAndLocked(Runnable r) {
            assert Thread.holdsLock(mAwSettingsLock);
            if (mHandler == null) return;
            if (ThreadUtils.runningOnUiThread()) {
                r.run();
            } else {
                assert !mSynchronizationPending;
                mSynchronizationPending = true;
                mHandler.sendMessage(Message.obtain(null, RUN_RUNNABLE_BLOCKING, r));
                try {
                    while (mSynchronizationPending) {
                        mAwSettingsLock.wait();
                    }
                } catch (InterruptedException e) {
                    Log.e(TAG, "Interrupted waiting a Runnable to complete", e);
                    mSynchronizationPending = false;
                }
            }
        }

        void maybePostOnUiThread(Runnable r) {
            if (mHandler != null) {
                mHandler.post(r);
            }
        }

        void updateWebkitPreferencesLocked() {
            runOnUiThreadBlockingAndLocked(
                    AwSettings.this::updateWebkitPreferencesOnUiThreadLocked);
        }

        void updateCookiePolicyLocked() {
            runOnUiThreadBlockingAndLocked(AwSettings.this::updateCookiePolicyOnUiThreadLocked);
        }

        void updateAllowFileAccessLocked() {
            runOnUiThreadBlockingAndLocked(AwSettings.this::updateAllowFileAccessOnUiThreadLocked);
        }

        void updateSpeculativeLoadingAllowedLocked() {
            runOnUiThreadBlockingAndLocked(
                    AwSettings.this::updateSpeculativeLoadingAllowedOnUiThreadLocked);
        }

        void updateBackForwardCacheEnabled() {
            runOnUiThreadBlockingAndLocked(
                    AwSettings.this::updateBackForwardCacheEnabledOnUiThreadLocked);
        }

        void updateGeolocationEnabled() {
            runOnUiThreadBlockingAndLocked(
                    AwSettings.this::updateGeolocationEnabledOnUiThreadLocked);
        }
    }

    interface ZoomSupportChangeListener {
        public void onGestureZoomSupportChanged(
                boolean supportsDoubleTapZoom, boolean supportsMultiTouchZoom);
    }

    public AwSettings(
            Context context,
            boolean isAccessFromFileURLsGrantedByDefault,
            boolean supportsLegacyQuirks,
            boolean allowEmptyDocumentPersistence,
            boolean allowGeolocationOnInsecureOrigins,
            boolean doNotUpdateSelectionOnMutatingSelectionRange) {
        mContext = context;
        boolean hasInternetPermission =
                context.checkPermission(
                                android.Manifest.permission.INTERNET,
                                Process.myPid(),
                                Process.myUid())
                        == PackageManager.PERMISSION_GRANTED;
        synchronized (mAwSettingsLock) {
            mHasInternetPermission = hasInternetPermission;
            mBlockNetworkLoads = !hasInternetPermission;
            mEventHandler = new EventHandler();
            if (isAccessFromFileURLsGrantedByDefault) {
                mAllowUniversalAccessFromFileURLs = true;
                mAllowFileAccessFromFileURLs = true;
            }

            mUserAgent = LazyDefaultUserAgent.sInstance;
            mAwUserAgentMetadata = LazyDefaultUserAgentMetadata.sInstance.shallowCopy();

            // Best-guess a sensible initial value based on the features supported on the device.
            mSpatialNavigationEnabled =
                    !context.getPackageManager()
                            .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN);

            // Respect the system setting for password echoing.
            mPasswordEchoEnabled =
                    Settings.System.getInt(
                                    context.getContentResolver(),
                                    Settings.System.TEXT_SHOW_PASSWORD,
                                    1)
                            == 1;

            // By default, scale the text size by the system font scale factor. Embedders
            // may override this by invoking setTextZoom().
            mTextSizePercent =
                    (int) (mTextSizePercent * context.getResources().getConfiguration().fontScale);

            mSupportLegacyQuirks = supportsLegacyQuirks;
            mAllowEmptyDocumentPersistence = allowEmptyDocumentPersistence;
            mAllowGeolocationOnInsecureOrigins = allowGeolocationOnInsecureOrigins;
            mDoNotUpdateSelectionOnMutatingSelectionRange =
                    doNotUpdateSelectionOnMutatingSelectionRange;

            // The application context we receive in the sdk runtime is a separate
            // context from the context that actual SDKs receive (and contains asset
            // file links). This means file urls will not work in this environment.
            // Explicitly block this to cause confusion in the case of accidentally
            // hitting assets in the application context.
            mBlockSpecialFileUrls = ContextUtils.isSdkSandboxProcess();

            mAllowFileUrlAccess =
                    ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion
                            < Build.VERSION_CODES.R;
            mRequestedWithHeaderAllowedOriginRules =
                    ManifestMetadataUtil.getXRequestedWithAllowList();
            mIntegrityApiStatusConfig = new AwMediaIntegrityApiStatusConfig();
            mSpeculativeLoadingAllowedFlags =
                    SpeculativeLoadingAllowedFlags.SPECULATIVE_LOADING_DISABLED;
            mHasCalledSetSpeculativeLoadingAllowedBefore = false;
            mBackForwardCacheEnabled = false;
            mHasCalledSetBackForwardCacheEnabledBefore = false;
        }
        // Defer initializing the native side until a native WebContents instance is set.
    }

    /** Get the AwSettings for the WebView with the given WebContents */
    @Nullable
    public static AwSettings fromWebContents(@NonNull WebContents webContents) {
        return AwSettingsJni.get().fromWebContents(webContents);
    }

    public int getUiModeNight() {
        return mContext.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
    }

    @CalledByNative
    private void nativeAwSettingsGone(long nativeAwSettings) {
        assert mNativeAwSettings != 0 && mNativeAwSettings == nativeAwSettings;
        mNativeAwSettings = 0;
    }

    @CalledByNative
    private double getDIPScaleLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDIPScale;
    }

    void setDIPScale(double dipScale) {
        synchronized (mAwSettingsLock) {
            mDIPScale = dipScale;
            // TODO(joth): This should also be synced over to native side, but right now
            // the setDIPScale call is always followed by a setWebContents() which covers this.
        }
    }

    void setZoomListener(ZoomSupportChangeListener zoomChangeListener) {
        synchronized (mAwSettingsLock) {
            mZoomChangeListener = zoomChangeListener;
        }
    }

    private void flushBackForwardCacheOnUiThreadLocked() {
        synchronized (mAwSettingsLock) {
            WebContents contents = mWebContents;
            Boolean backForwardCacheEnabled = mBackForwardCacheEnabled;
            mEventHandler.maybePostOnUiThread(
                    () -> flushBackForwardCache(contents, backForwardCacheEnabled));
        }
    }

    private void flushBackForwardCache() {
        assert Thread.holdsLock(mAwSettingsLock);
        flushBackForwardCache(mWebContents, mBackForwardCacheEnabled);
    }

    private void flushBackForwardCache(WebContents contents, boolean backForwardCacheEnabled) {
        ThreadUtils.assertOnUiThread();
        backForwardCacheEnabled =
                AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_BACK_FORWARD_CACHE)
                        || backForwardCacheEnabled;
        if (contents != null && backForwardCacheEnabled) {
            AwContents awContents = AwContents.fromWebContents(contents);
            if (awContents != null) {
                awContents.flushBackForwardCache(
                        BackForwardCacheNotRestoredReason.WEBVIEW_SETTINGS_CHANGED);
            }
        }
    }

    void setWebContents(WebContents webContents) {
        synchronized (mAwSettingsLock) {
            if (mNativeAwSettings != 0) {
                AwSettingsJni.get().destroy(mNativeAwSettings, AwSettings.this);
                assert mNativeAwSettings == 0; // nativeAwSettingsGone should have been called.
            }
            if (webContents != null) {
                mEventHandler.bindUiThread();
                mNativeAwSettings = AwSettingsJni.get().init(AwSettings.this, webContents);
                updateEverythingLocked();
                setRequestedWithHeaderOriginAllowListLocked(mRequestedWithHeaderAllowedOriginRules);
                WebauthnModeProvider.getInstance()
                        .setWebauthnModeForWebContents(webContents, mWebauthnMode);
                flushBackForwardCacheOnUiThreadLocked();
            }
            mWebContents = webContents;
        }
    }

    private void updateEverythingLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        assert mNativeAwSettings != 0;
        AwSettingsJni.get().updateEverythingLocked(mNativeAwSettings, AwSettings.this);
        onGestureZoomSupportChanged(supportsDoubleTapZoomLocked(), supportsMultiTouchZoomLocked());
    }

    /** See {@link android.webkit.WebSettings#setBlockNetworkLoads}. */
    public void setBlockNetworkLoads(boolean flag) {
        if (TRACE) Log.i(TAG, "setBlockNetworkLoads=" + flag);
        synchronized (mAwSettingsLock) {
            if (!flag && !mHasInternetPermission) {
                throw new SecurityException(
                        "Permission denied - " + "application missing INTERNET permission");
            }
            if (mBlockNetworkLoads != flag) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mBlockNetworkLoads = flag;
        }
    }

    /** See {@link android.webkit.WebSettings#getBlockNetworkLoads}. */
    public boolean getBlockNetworkLoads() {
        synchronized (mAwSettingsLock) {
            return mBlockNetworkLoads;
        }
    }

    /**
     * Enable/disable third party cookies for an AwContents
     * @param accept true if we should accept third party cookies
     */
    public void setAcceptThirdPartyCookies(boolean accept) {
        if (CommandLine.getInstance().hasSwitch(AwSwitches.WEBVIEW_FORCE_DISABLE3PCS)) {
            if (TRACE) Log.i(TAG, "setAcceptThirdPartyCookies force disabled");
            return;
        }
        if (TRACE) Log.i(TAG, "setAcceptThirdPartyCookies=" + accept);
        RecordHistogram.recordBooleanHistogram(
                "Android.WebView.SetAcceptThirdPartyCookies", accept);
        synchronized (mAwSettingsLock) {
            mAcceptThirdPartyCookies = accept;
            mEventHandler.updateCookiePolicyLocked();
        }
    }

    /**
     * Enable/Disable SafeBrowsing per WebView
     *
     * @param enabled true if this WebView should have SafeBrowsing
     */
    public void setSafeBrowsingEnabled(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (mSafeBrowsingEnabled == null || mSafeBrowsingEnabled != enabled) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mSafeBrowsingEnabled = enabled;
        }
    }

    /**
     * Return whether third party cookies are enabled for an AwContents
     * @return true if accept third party cookies
     */
    public boolean getAcceptThirdPartyCookies() {
        synchronized (mAwSettingsLock) {
            return mAcceptThirdPartyCookies;
        }
    }

    @CalledByNative
    private boolean getAcceptThirdPartyCookiesLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAcceptThirdPartyCookies;
    }

    /**
     * Return whether Safe Browsing has been enabled for the current WebView
     * @return true if SafeBrowsing is enabled
     */
    public boolean getSafeBrowsingEnabled() {
        synchronized (mAwSettingsLock) {
            if (mSafeBrowsingEnabled == null) {
                return AwSafeBrowsingConfigHelper.getSafeBrowsingEnabledByManifest();
            }
            return mSafeBrowsingEnabled;
        }
    }

    /** See {@link android.webkit.WebSettings#setAllowFileAccess}. */
    public void setAllowFileAccess(boolean allow) {
        if (TRACE) Log.i(TAG, "setAllowFileAccess=" + allow);
        synchronized (mAwSettingsLock) {
            mAllowFileUrlAccess = allow;
            mEventHandler.updateAllowFileAccessLocked();
        }
    }

    /** See {@link android.webkit.WebSettings#getAllowFileAccess}. */
    @CalledByNative
    public boolean getAllowFileAccess() {
        synchronized (mAwSettingsLock) {
            return mAllowFileUrlAccess;
        }
    }

    /** See {@link android.webkit.WebSettings#setAllowContentAccess}. */
    public void setAllowContentAccess(boolean allow) {
        if (TRACE) Log.i(TAG, "setAllowContentAccess=" + allow);
        synchronized (mAwSettingsLock) {
            if (mAllowContentUrlAccess != allow) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mAllowContentUrlAccess = allow;
        }
    }

    /** See {@link android.webkit.WebSettings#getAllowContentAccess}. */
    public boolean getAllowContentAccess() {
        synchronized (mAwSettingsLock) {
            return mAllowContentUrlAccess;
        }
    }

    /** See {@link android.webkit.WebSettings#setCacheMode}. */
    public void setCacheMode(int mode) {
        if (TRACE) Log.i(TAG, "setCacheMode=" + mode);
        synchronized (mAwSettingsLock) {
            if (mCacheMode != mode) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mCacheMode = mode;
        }
    }

    /** See {@link android.webkit.WebSettings#getCacheMode}. */
    public int getCacheMode() {
        synchronized (mAwSettingsLock) {
            return mCacheMode;
        }
    }

    /** See {@link android.webkit.WebSettings#setNeedInitialFocus}. */
    public void setShouldFocusFirstNode(boolean flag) {
        if (TRACE) Log.i(TAG, "setNeedInitialFocus=" + flag);
        synchronized (mAwSettingsLock) {
            if (mShouldFocusFirstNode != flag) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mShouldFocusFirstNode = flag;
        }
    }

    /** See {@link android.webkit.WebView#setInitialScale}. */
    public void setInitialPageScale(final float scaleInPercent) {
        if (TRACE) Log.i(TAG, "setInitialScale=" + scaleInPercent);
        synchronized (mAwSettingsLock) {
            if (mInitialPageScalePercent != scaleInPercent) {
                mInitialPageScalePercent = scaleInPercent;
                mEventHandler.runOnUiThreadBlockingAndLocked(
                        () -> updateInitialPageScaleOnUiThreadLocked());
            }
        }
    }

    @CalledByNative
    private float getInitialPageScalePercentLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mInitialPageScalePercent;
    }

    @VisibleForTesting
    public void setSpatialNavigationEnabled(boolean enable) {
        synchronized (mAwSettingsLock) {
            if (mSpatialNavigationEnabled != enable) {
                mSpatialNavigationEnabled = enable;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getSpatialNavigationLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSpatialNavigationEnabled;
    }

    @VisibleForTesting
    public void setEnableSupportedHardwareAcceleratedFeatures(boolean enable) {
        synchronized (mAwSettingsLock) {
            if (mEnableSupportedHardwareAcceleratedFeatures != enable) {
                mEnableSupportedHardwareAcceleratedFeatures = enable;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getEnableSupportedHardwareAcceleratedFeaturesLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mEnableSupportedHardwareAcceleratedFeatures;
    }

    public void setFullscreenSupported(boolean supported) {
        synchronized (mAwSettingsLock) {
            if (mFullscreenSupported != supported) {
                mFullscreenSupported = supported;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getFullscreenSupportedLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mFullscreenSupported;
    }

    /** See {@link android.webkit.WebSettings#setNeedInitialFocus}. */
    public boolean shouldFocusFirstNode() {
        synchronized (mAwSettingsLock) {
            return mShouldFocusFirstNode;
        }
    }

    /** See {@link android.webkit.WebSettings#setGeolocationEnabled}. */
    public void setGeolocationEnabled(boolean flag) {
        if (TRACE) Log.i(TAG, "setGeolocationEnabled=" + flag);
        synchronized (mAwSettingsLock) {
            if (mGeolocationEnabled != flag) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mGeolocationEnabled = flag;
            mEventHandler.updateGeolocationEnabled();
        }
    }

    /**
     * @return Returns if geolocation is currently enabled.
     */
    @CalledByNative
    @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
    public boolean getGeolocationEnabled() {
        synchronized (mAwSettingsLock) {
            return mGeolocationEnabled;
        }
    }

    public void setUserAgent(int ua) {
        // Minimal implementation for backwards compatibility: just supports resetting to default.
        if (ua == 0) {
            setUserAgentString(null);
        } else {
            Log.w(TAG, "setUserAgent not supported, ua=" + ua);
        }
    }

    /**
     * @returns the default User-Agent used by each WebContents instance, i.e. unless
     * overridden by {@link #setUserAgentString()}
     */
    public static String getDefaultUserAgent() {
        return LazyDefaultUserAgent.sInstance;
    }

    /**
     * @returns the default metadata for user-agent client hints used by each WebContents instance,
     * i.e. unless overridden by {@link #setUserAgentMetadata()}
     */
    public static AwUserAgentMetadata getDefaultUserAgentMetadata() {
        return LazyDefaultUserAgentMetadata.sInstance;
    }

    @CalledByNative
    private static boolean getAllowSniffingFileUrls() {
        // Don't allow sniffing file:// URLs for MIME type if the application targets P or later.
        return ContextUtils.getApplicationContext().getApplicationInfo().targetSdkVersion
                < Build.VERSION_CODES.P;
    }

    /** See {@link android.webkit.WebSettings#setUserAgentString}. */
    public void setUserAgentString(String ua) {
        if (TRACE) Log.i(TAG, "setUserAgentString=" + ua);
        synchronized (mAwSettingsLock) {
            final String oldUserAgent = mUserAgent;
            if (ua == null || ua.length() == 0) {
                mUserAgent = LazyDefaultUserAgent.sInstance;
            } else {
                mUserAgent = ua;
            }
            if (!oldUserAgent.equals(mUserAgent)) {
                if (ua != null
                        && ua.length() > 0
                        && AwContents.BAD_HEADER_CHAR.matcher(ua).find()) {
                    throw new IllegalArgumentException(
                            AwContents.BAD_HEADER_MSG + "Invalid User-Agent '" + ua + "'");
                }
                mEventHandler.runOnUiThreadBlockingAndLocked(
                        () -> updateUserAgentOnUiThreadLocked());
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getUserAgentString}. */
    public String getUserAgentString() {
        synchronized (mAwSettingsLock) {
            return getUserAgentLocked();
        }
    }

    @CalledByNative
    private String getUserAgentLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mUserAgent;
    }

    /**
     * See {@link androidx.webkit.WebSettingsCompat#setUserAgentMetadata}.
     * Map<String, Object> represents the priorities name its value for AwUserAgentMetadata.
     */
    public void setUserAgentMetadataFromMap(Map<String, Object> uaMetadataMap) {
        if (TRACE) Log.i(TAG, "setUserAgentMetadata=" + uaMetadataMap);
        synchronized (mAwSettingsLock) {
            final AwUserAgentMetadata overrideUaMetadata =
                    AwUserAgentMetadata.fromMap(
                            uaMetadataMap, LazyDefaultUserAgentMetadata.sInstance);
            if (!mAwUserAgentMetadata.equals(overrideUaMetadata)) {
                mAwUserAgentMetadata = overrideUaMetadata;
                // We only consider it has override when the input is not empty and has difference
                // with the existing user-agent metadata. e.g. user overrides the user-agent with a
                // totally different value, initially they provide the user-agent metadata
                // overrides, we should only generate low-entropy user-agent client hints once users
                // clear the user-agent metadata overrides.
                mHasUserAgentMetadataOverrides =
                        (uaMetadataMap != null && !uaMetadataMap.isEmpty());
                mEventHandler.runOnUiThreadBlockingAndLocked(
                        () -> updateUserAgentOnUiThreadLocked());
            }
        }
    }

    /** See {@link androidx.webkit.WebSettingsCompat#getUserAgentMetadata}. */
    public Map<String, Object> getUserAgentMetadataMap() {
        synchronized (mAwSettingsLock) {
            return getUserAgentMetadataLocked().toMapObject();
        }
    }

    @CalledByNative
    private AwUserAgentMetadata getUserAgentMetadataLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAwUserAgentMetadata;
    }

    @CalledByNative
    private boolean getHasUserAgentMetadataOverridesLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mHasUserAgentMetadataOverrides;
    }

    /** See {@link android.webkit.WebSettings#setLoadWithOverviewMode}. */
    public void setLoadWithOverviewMode(boolean overview) {
        if (TRACE) Log.i(TAG, "setLoadWithOverviewMode=" + overview);
        synchronized (mAwSettingsLock) {
            if (mLoadWithOverviewMode != overview) {
                mLoadWithOverviewMode = overview;
                mEventHandler.runOnUiThreadBlockingAndLocked(
                        () -> {
                            if (mNativeAwSettings != 0) {
                                updateWebkitPreferencesOnUiThreadLocked();
                                AwSettingsJni.get()
                                        .resetScrollAndScaleState(
                                                mNativeAwSettings, AwSettings.this);
                            }
                        });
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getLoadWithOverviewMode}. */
    public boolean getLoadWithOverviewMode() {
        synchronized (mAwSettingsLock) {
            return getLoadWithOverviewModeLocked();
        }
    }

    @CalledByNative
    private boolean getLoadWithOverviewModeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mLoadWithOverviewMode;
    }

    /** See {@link android.webkit.WebSettings#setTextZoom}. */
    public void setTextZoom(final int textZoom) {
        if (TRACE) Log.i(TAG, "setTextZoom=" + textZoom);
        synchronized (mAwSettingsLock) {
            if (mTextSizePercent != textZoom) {
                mTextSizePercent = textZoom;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getTextZoom}. */
    public int getTextZoom() {
        synchronized (mAwSettingsLock) {
            return getTextSizePercentLocked();
        }
    }

    @CalledByNative
    private int getTextSizePercentLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mTextSizePercent;
    }

    /** See {@link android.webkit.WebSettings#setStandardFontFamily}. */
    public void setStandardFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setStandardFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mStandardFontFamily.equals(font)) {
                mStandardFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getStandardFontFamily}. */
    public String getStandardFontFamily() {
        synchronized (mAwSettingsLock) {
            return getStandardFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getStandardFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mStandardFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setFixedFontFamily}. */
    public void setFixedFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setFixedFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mFixedFontFamily.equals(font)) {
                mFixedFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getFixedFontFamily}. */
    public String getFixedFontFamily() {
        synchronized (mAwSettingsLock) {
            return getFixedFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getFixedFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mFixedFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setSansSerifFontFamily}. */
    public void setSansSerifFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setSansSerifFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mSansSerifFontFamily.equals(font)) {
                mSansSerifFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getSansSerifFontFamily}. */
    public String getSansSerifFontFamily() {
        synchronized (mAwSettingsLock) {
            return getSansSerifFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getSansSerifFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSansSerifFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setSerifFontFamily}. */
    public void setSerifFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setSerifFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mSerifFontFamily.equals(font)) {
                mSerifFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getSerifFontFamily}. */
    public String getSerifFontFamily() {
        synchronized (mAwSettingsLock) {
            return getSerifFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getSerifFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSerifFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setCursiveFontFamily}. */
    public void setCursiveFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setCursiveFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mCursiveFontFamily.equals(font)) {
                mCursiveFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getCursiveFontFamily}. */
    public String getCursiveFontFamily() {
        synchronized (mAwSettingsLock) {
            return getCursiveFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getCursiveFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mCursiveFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setFantasyFontFamily}. */
    public void setFantasyFontFamily(String font) {
        if (TRACE) Log.i(TAG, "setFantasyFontFamily=" + font);
        synchronized (mAwSettingsLock) {
            if (font != null && !mFantasyFontFamily.equals(font)) {
                mFantasyFontFamily = font;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getFantasyFontFamily}. */
    public String getFantasyFontFamily() {
        synchronized (mAwSettingsLock) {
            return getFantasyFontFamilyLocked();
        }
    }

    @CalledByNative
    private String getFantasyFontFamilyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mFantasyFontFamily;
    }

    /** See {@link android.webkit.WebSettings#setMinimumFontSize}. */
    public void setMinimumFontSize(int size) {
        if (TRACE) Log.i(TAG, "setMinimumFontSize=" + size);
        synchronized (mAwSettingsLock) {
            size = clipFontSize(size);
            if (mMinimumFontSize != size) {
                mMinimumFontSize = size;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getMinimumFontSize}. */
    public int getMinimumFontSize() {
        synchronized (mAwSettingsLock) {
            return getMinimumFontSizeLocked();
        }
    }

    @CalledByNative
    private int getMinimumFontSizeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mMinimumFontSize;
    }

    /** See {@link android.webkit.WebSettings#setMinimumLogicalFontSize}. */
    public void setMinimumLogicalFontSize(int size) {
        if (TRACE) Log.i(TAG, "setMinimumLogicalFontSize=" + size);
        synchronized (mAwSettingsLock) {
            size = clipFontSize(size);
            if (mMinimumLogicalFontSize != size) {
                mMinimumLogicalFontSize = size;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getMinimumLogicalFontSize}. */
    public int getMinimumLogicalFontSize() {
        synchronized (mAwSettingsLock) {
            return getMinimumLogicalFontSizeLocked();
        }
    }

    @CalledByNative
    private int getMinimumLogicalFontSizeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mMinimumLogicalFontSize;
    }

    /** See {@link android.webkit.WebSettings#setDefaultFontSize}. */
    public void setDefaultFontSize(int size) {
        if (TRACE) Log.i(TAG, "setDefaultFontSize=" + size);
        synchronized (mAwSettingsLock) {
            size = clipFontSize(size);
            if (mDefaultFontSize != size) {
                mDefaultFontSize = size;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDefaultFontSize}. */
    public int getDefaultFontSize() {
        synchronized (mAwSettingsLock) {
            return getDefaultFontSizeLocked();
        }
    }

    @CalledByNative
    private int getDefaultFontSizeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDefaultFontSize;
    }

    /** See {@link android.webkit.WebSettings#setDefaultFixedFontSize}. */
    public void setDefaultFixedFontSize(int size) {
        if (TRACE) Log.i(TAG, "setDefaultFixedFontSize=" + size);
        synchronized (mAwSettingsLock) {
            size = clipFontSize(size);
            if (mDefaultFixedFontSize != size) {
                mDefaultFixedFontSize = size;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDefaultFixedFontSize}. */
    public int getDefaultFixedFontSize() {
        synchronized (mAwSettingsLock) {
            return getDefaultFixedFontSizeLocked();
        }
    }

    @CalledByNative
    private int getDefaultFixedFontSizeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDefaultFixedFontSize;
    }

    /** See {@link android.webkit.WebSettings#setJavaScriptEnabled}. */
    public void setJavaScriptEnabled(boolean flag) {
        if (TRACE) Log.i(TAG, "setJavaScriptEnabled=" + flag);
        synchronized (mAwSettingsLock) {
            if (mJavaScriptEnabled != flag) {
                mJavaScriptEnabled = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#setAllowUniversalAccessFromFileURLs}. */
    public void setAllowUniversalAccessFromFileURLs(boolean flag) {
        if (TRACE) Log.i(TAG, "setAllowUniversalAccessFromFileURLs=" + flag);
        synchronized (mAwSettingsLock) {
            if (mAllowUniversalAccessFromFileURLs != flag) {
                mAllowUniversalAccessFromFileURLs = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#setAllowFileAccessFromFileURLs}. */
    public void setAllowFileAccessFromFileURLs(boolean flag) {
        if (TRACE) Log.i(TAG, "setAllowFileAccessFromFileURLs=" + flag);
        synchronized (mAwSettingsLock) {
            if (mAllowFileAccessFromFileURLs != flag) {
                mAllowFileAccessFromFileURLs = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#setLoadsImagesAutomatically}. */
    public void setLoadsImagesAutomatically(boolean flag) {
        if (TRACE) Log.i(TAG, "setLoadsImagesAutomatically=" + flag);
        synchronized (mAwSettingsLock) {
            if (mLoadsImagesAutomatically != flag) {
                mLoadsImagesAutomatically = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getLoadsImagesAutomatically}. */
    public boolean getLoadsImagesAutomatically() {
        synchronized (mAwSettingsLock) {
            return getLoadsImagesAutomaticallyLocked();
        }
    }

    @CalledByNative
    private boolean getLoadsImagesAutomaticallyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mLoadsImagesAutomatically;
    }

    /** See {@link android.webkit.WebSettings#setBlockNetworkImage}. */
    public void setImagesEnabled(boolean flag) {
        if (TRACE) Log.i(TAG, "setBlockNetworkImage=" + !flag);
        synchronized (mAwSettingsLock) {
            if (mImagesEnabled != flag) {
                mImagesEnabled = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getBlockNetworkImage}. */
    public boolean getImagesEnabled() {
        synchronized (mAwSettingsLock) {
            return mImagesEnabled;
        }
    }

    @CalledByNative
    private boolean getImagesEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mImagesEnabled;
    }

    /** See {@link android.webkit.WebSettings#getJavaScriptEnabled}. */
    public boolean getJavaScriptEnabled() {
        synchronized (mAwSettingsLock) {
            return mJavaScriptEnabled;
        }
    }

    @CalledByNative
    private boolean getJavaScriptEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mJavaScriptEnabled;
    }

    /** See {@link android.webkit.WebSettings#getAllowUniversalAccessFromFileURLs}. */
    public boolean getAllowUniversalAccessFromFileURLs() {
        synchronized (mAwSettingsLock) {
            return getAllowUniversalAccessFromFileURLsLocked();
        }
    }

    @CalledByNative
    private boolean getAllowUniversalAccessFromFileURLsLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAllowUniversalAccessFromFileURLs;
    }

    /** See {@link android.webkit.WebSettings#getAllowFileAccessFromFileURLs}. */
    public boolean getAllowFileAccessFromFileURLs() {
        synchronized (mAwSettingsLock) {
            return getAllowFileAccessFromFileURLsLocked();
        }
    }

    @CalledByNative
    private boolean getAllowFileAccessFromFileURLsLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAllowFileAccessFromFileURLs;
    }

    /** See {@link android.webkit.WebSettings#setJavaScriptCanOpenWindowsAutomatically}. */
    public void setJavaScriptCanOpenWindowsAutomatically(boolean flag) {
        if (TRACE) Log.i(TAG, "setJavaScriptCanOpenWindowsAutomatically=" + flag);
        synchronized (mAwSettingsLock) {
            if (mJavaScriptCanOpenWindowsAutomatically != flag) {
                mJavaScriptCanOpenWindowsAutomatically = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getJavaScriptCanOpenWindowsAutomatically}. */
    public boolean getJavaScriptCanOpenWindowsAutomatically() {
        synchronized (mAwSettingsLock) {
            return getJavaScriptCanOpenWindowsAutomaticallyLocked();
        }
    }

    @CalledByNative
    private boolean getJavaScriptCanOpenWindowsAutomaticallyLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mJavaScriptCanOpenWindowsAutomatically;
    }

    /** See {@link android.webkit.WebSettings#setLayoutAlgorithm}. */
    public void setLayoutAlgorithm(@LayoutAlgorithm int l) {
        if (TRACE) Log.i(TAG, "setLayoutAlgorithm=" + l);
        synchronized (mAwSettingsLock) {
            if (mLayoutAlgorithm != l) {
                mLayoutAlgorithm = l;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getLayoutAlgorithm}. */
    @LayoutAlgorithm
    public int getLayoutAlgorithm() {
        synchronized (mAwSettingsLock) {
            return mLayoutAlgorithm;
        }
    }

    public void setRequestedWithHeaderOriginAllowList(Set<String> allowedOriginRules) {
        // Even though clients shouldn't pass in null, it's better to guard against it
        allowedOriginRules =
                allowedOriginRules != null ? allowedOriginRules : Collections.emptySet();
        AwWebContentsMetricsRecorder.recordRequestedWithHeaderModeAPIUsage(allowedOriginRules);
        synchronized (mAwSettingsLock) {
            setRequestedWithHeaderOriginAllowListLocked(allowedOriginRules);
        }
    }

    private void setRequestedWithHeaderOriginAllowListLocked(final Set<String> allowedOriginRules) {
        assert Thread.holdsLock(mAwSettingsLock);
        if (mNativeAwSettings == 0) {
            return;
        }

        // Final set to be updated by the Runnable on the UI thread.
        final Set<String> rejectedRules = new HashSet<>();

        mEventHandler.runOnUiThreadBlockingAndLocked(
                () -> {
                    flushBackForwardCache();
                    String[] rejected =
                            AwSettingsJni.get()
                                    .updateXRequestedWithAllowListOriginMatcher(
                                            mNativeAwSettings,
                                            allowedOriginRules.toArray(new String[0]));
                    rejectedRules.addAll(java.util.Arrays.asList(rejected));
                });

        if (!rejectedRules.isEmpty()) {
            throw new IllegalArgumentException("Malformed origin match rules: " + rejectedRules);
        }
        mRequestedWithHeaderAllowedOriginRules = allowedOriginRules;
    }

    public Set<String> getRequestedWithHeaderOriginAllowList() {
        synchronized (mAwSettingsLock) {
            return mRequestedWithHeaderAllowedOriginRules;
        }
    }

    /**
     * Gets whether Text Auto-sizing layout algorithm is enabled.
     *
     * @return true if Text Auto-sizing layout algorithm is enabled
     */
    @CalledByNative
    private boolean getTextAutosizingEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mLayoutAlgorithm == LAYOUT_ALGORITHM_TEXT_AUTOSIZING;
    }

    /** See {@link android.webkit.WebSettings#setSupportMultipleWindows}. */
    public void setSupportMultipleWindows(boolean support) {
        if (TRACE) Log.i(TAG, "setSupportMultipleWindows=" + support);
        synchronized (mAwSettingsLock) {
            if (mSupportMultipleWindows != support) {
                mSupportMultipleWindows = support;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#supportMultipleWindows}. */
    public boolean supportMultipleWindows() {
        synchronized (mAwSettingsLock) {
            return mSupportMultipleWindows;
        }
    }

    public void setBlockSpecialFileUrls(boolean block) {
        if (TRACE) Log.i(TAG, "setBlockSpecialFileUrls=" + block);
        synchronized (mAwSettingsLock) {
            if (mBlockSpecialFileUrls != block) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mBlockSpecialFileUrls = block;
        }
    }

    public boolean getBlockSpecialFileUrls() {
        synchronized (mAwSettingsLock) {
            return mBlockSpecialFileUrls;
        }
    }

    @CalledByNative
    private boolean getSupportMultipleWindowsLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSupportMultipleWindows;
    }

    @CalledByNative
    private boolean getCSSHexAlphaColorEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mCSSHexAlphaColorEnabled;
    }

    public void setCSSHexAlphaColorEnabled(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (mCSSHexAlphaColorEnabled != enabled) {
                mCSSHexAlphaColorEnabled = enabled;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getScrollTopLeftInteropEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mScrollTopLeftInteropEnabled;
    }

    public void setScrollTopLeftInteropEnabled(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (mScrollTopLeftInteropEnabled != enabled) {
                mScrollTopLeftInteropEnabled = enabled;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getWillSuppressErrorPageLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mWillSuppressErrorPage;
    }

    public boolean getWillSuppressErrorPage() {
        synchronized (mAwSettingsLock) {
            return getWillSuppressErrorPageLocked();
        }
    }

    public void setWillSuppressErrorPage(boolean suppressed) {
        synchronized (mAwSettingsLock) {
            if (mWillSuppressErrorPage == suppressed) return;

            mWillSuppressErrorPage = suppressed;
            updateWillSuppressErrorStateLocked();
        }
    }

    private void updateWillSuppressErrorStateLocked() {
        mEventHandler.runOnUiThreadBlockingAndLocked(
                () -> updateWillSuppressErrorStateOnUiThreadLocked());
    }

    @CalledByNative
    private boolean getSupportLegacyQuirksLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSupportLegacyQuirks;
    }

    @CalledByNative
    private boolean getAllowEmptyDocumentPersistenceLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAllowEmptyDocumentPersistence;
    }

    @CalledByNative
    private boolean getAllowGeolocationOnInsecureOrigins() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAllowGeolocationOnInsecureOrigins;
    }

    @CalledByNative
    private boolean getDoNotUpdateSelectionOnMutatingSelectionRange() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDoNotUpdateSelectionOnMutatingSelectionRange;
    }

    /** See {@link android.webkit.WebSettings#setUseWideViewPort}. */
    public void setUseWideViewPort(boolean use) {
        if (TRACE) Log.i(TAG, "setUseWideViewPort=" + use);
        synchronized (mAwSettingsLock) {
            if (mUseWideViewport != use) {
                mUseWideViewport = use;
                onGestureZoomSupportChanged(
                        supportsDoubleTapZoomLocked(), supportsMultiTouchZoomLocked());
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getUseWideViewPort}. */
    public boolean getUseWideViewPort() {
        synchronized (mAwSettingsLock) {
            return getUseWideViewportLocked();
        }
    }

    @CalledByNative
    private boolean getUseWideViewportLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mUseWideViewport;
    }

    public void setZeroLayoutHeightDisablesViewportQuirk(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (mZeroLayoutHeightDisablesViewportQuirk != enabled) {
                mZeroLayoutHeightDisablesViewportQuirk = enabled;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    public boolean getZeroLayoutHeightDisablesViewportQuirk() {
        synchronized (mAwSettingsLock) {
            return getZeroLayoutHeightDisablesViewportQuirkLocked();
        }
    }

    @CalledByNative
    private boolean getZeroLayoutHeightDisablesViewportQuirkLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mZeroLayoutHeightDisablesViewportQuirk;
    }

    public void setForceZeroLayoutHeight(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (mForceZeroLayoutHeight != enabled) {
                mForceZeroLayoutHeight = enabled;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    public boolean getForceZeroLayoutHeight() {
        synchronized (mAwSettingsLock) {
            return getForceZeroLayoutHeightLocked();
        }
    }

    @CalledByNative
    private boolean getForceZeroLayoutHeightLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mForceZeroLayoutHeight;
    }

    @CalledByNative
    private boolean getPasswordEchoEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mPasswordEchoEnabled;
    }

    /** See {@link android.webkit.WebSettings#setDomStorageEnabled}. */
    public void setDomStorageEnabled(boolean flag) {
        if (TRACE) Log.i(TAG, "setDomStorageEnabled=" + flag);
        synchronized (mAwSettingsLock) {
            if (mDomStorageEnabled != flag) {
                mDomStorageEnabled = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDomStorageEnabled}. */
    public boolean getDomStorageEnabled() {
        synchronized (mAwSettingsLock) {
            return mDomStorageEnabled;
        }
    }

    @CalledByNative
    private boolean getDomStorageEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDomStorageEnabled;
    }

    /** See {@link android.webkit.WebSettings#setDatabaseEnabled}. */
    public void setDatabaseEnabled(boolean flag) {
        if (TRACE) Log.i(TAG, "setDatabaseEnabled=" + flag);
        synchronized (mAwSettingsLock) {
            if (mDatabaseEnabled != flag) {
                mDatabaseEnabled = flag;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDatabaseEnabled}. */
    public boolean getDatabaseEnabled() {
        synchronized (mAwSettingsLock) {
            return mDatabaseEnabled;
        }
    }

    @CalledByNative
    private boolean getDatabaseEnabledLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDatabaseEnabled;
    }

    /** See {@link android.webkit.WebSettings#setDefaultTextEncodingName}. */
    public void setDefaultTextEncodingName(String encoding) {
        if (TRACE) Log.i(TAG, "setDefaultTextEncodingName=" + encoding);
        synchronized (mAwSettingsLock) {
            if (encoding != null && !mDefaultTextEncoding.equals(encoding)) {
                mDefaultTextEncoding = encoding;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDefaultTextEncodingName}. */
    public String getDefaultTextEncodingName() {
        synchronized (mAwSettingsLock) {
            return getDefaultTextEncodingLocked();
        }
    }

    @CalledByNative
    private String getDefaultTextEncodingLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDefaultTextEncoding;
    }

    /** See {@link android.webkit.WebSettings#setMediaPlaybackRequiresUserGesture}. */
    public void setMediaPlaybackRequiresUserGesture(boolean require) {
        if (TRACE) Log.i(TAG, "setMediaPlaybackRequiresUserGesture=" + require);
        synchronized (mAwSettingsLock) {
            if (mMediaPlaybackRequiresUserGesture != require) {
                mMediaPlaybackRequiresUserGesture = require;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getMediaPlaybackRequiresUserGesture}. */
    public boolean getMediaPlaybackRequiresUserGesture() {
        synchronized (mAwSettingsLock) {
            return getMediaPlaybackRequiresUserGestureLocked();
        }
    }

    @CalledByNative
    private boolean getMediaPlaybackRequiresUserGestureLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mMediaPlaybackRequiresUserGesture;
    }

    /** See {@link android.webkit.WebSettings#setDefaultVideoPosterURL}. */
    public void setDefaultVideoPosterURL(String url) {
        synchronized (mAwSettingsLock) {
            if ((mDefaultVideoPosterURL != null && !mDefaultVideoPosterURL.equals(url))
                    || (mDefaultVideoPosterURL == null && url != null)) {
                mDefaultVideoPosterURL = url;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getDefaultVideoPosterURL}. */
    public String getDefaultVideoPosterURL() {
        synchronized (mAwSettingsLock) {
            return getDefaultVideoPosterURLLocked();
        }
    }

    @CalledByNative
    private String getDefaultVideoPosterURLLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mDefaultVideoPosterURL;
    }

    private void onGestureZoomSupportChanged(
            final boolean supportsDoubleTapZoom, final boolean supportsMultiTouchZoom) {
        // Always post asynchronously here, to avoid doubling back onto the caller.
        mEventHandler.maybePostOnUiThread(
                () -> {
                    synchronized (mAwSettingsLock) {
                        if (mZoomChangeListener != null) {
                            mZoomChangeListener.onGestureZoomSupportChanged(
                                    supportsDoubleTapZoom, supportsMultiTouchZoom);
                        }
                    }
                });
    }

    /** See {@link android.webkit.WebSettings#setSupportZoom}. */
    public void setSupportZoom(boolean support) {
        if (TRACE) Log.i(TAG, "setSupportZoom=" + support);
        synchronized (mAwSettingsLock) {
            if (mSupportZoom != support) {
                flushBackForwardCacheOnUiThreadLocked();
                mSupportZoom = support;
                onGestureZoomSupportChanged(
                        supportsDoubleTapZoomLocked(), supportsMultiTouchZoomLocked());
            }
        }
    }

    /** See {@link android.webkit.WebSettings#supportZoom}. */
    public boolean supportZoom() {
        synchronized (mAwSettingsLock) {
            return mSupportZoom;
        }
    }

    /** See {@link android.webkit.WebSettings#setBuiltInZoomControls}. */
    public void setBuiltInZoomControls(boolean enabled) {
        if (TRACE) Log.i(TAG, "setBuiltInZoomControls=" + enabled);
        synchronized (mAwSettingsLock) {
            if (mBuiltInZoomControls != enabled) {
                flushBackForwardCacheOnUiThreadLocked();
                mBuiltInZoomControls = enabled;
                onGestureZoomSupportChanged(
                        supportsDoubleTapZoomLocked(), supportsMultiTouchZoomLocked());
            }
        }
    }

    /** See {@link android.webkit.WebSettings#getBuiltInZoomControls}. */
    public boolean getBuiltInZoomControls() {
        synchronized (mAwSettingsLock) {
            return mBuiltInZoomControls;
        }
    }

    /** See {@link android.webkit.WebSettings#setDisplayZoomControls}. */
    public void setDisplayZoomControls(boolean enabled) {
        if (TRACE) Log.i(TAG, "setDisplayZoomControls=" + enabled);
        synchronized (mAwSettingsLock) {
            if (mDisplayZoomControls != enabled) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mDisplayZoomControls = enabled;
        }
    }

    /** See {@link android.webkit.WebSettings#getDisplayZoomControls}. */
    public boolean getDisplayZoomControls() {
        synchronized (mAwSettingsLock) {
            return mDisplayZoomControls;
        }
    }

    public void setMixedContentMode(int mode) {
        // Using explicit max count for the histogram since enum is defined in Android code. The
        // values can be trusted to remain stable since they are defined in the Android API.
        RecordHistogram.recordEnumeratedHistogram("Android.WebView.MixedContent.Mode", mode, 3);
        synchronized (mAwSettingsLock) {
            if (mMixedContentMode != mode) {
                mMixedContentMode = mode;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    public int getMixedContentMode() {
        synchronized (mAwSettingsLock) {
            return mMixedContentMode;
        }
    }

    public void setAttributionBehavior(@AttributionBehavior int behavior) {
        synchronized (mAwSettingsLock) {
            if (mAttributionBehavior != behavior) {
                mAttributionBehavior = behavior;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    @AttributionBehavior
    public int getAttributionBehavior() {
        synchronized (mAwSettingsLock) {
            return mAttributionBehavior;
        }
    }

    public void setSpeculativeLoadingAllowed(@SpeculativeLoadingAllowedFlags int flags) {
        synchronized (mAwSettingsLock) {
            // Only trigger an update if the value changed, or this is the first time we call this
            // function. The latter is important to make sure every embedder that calls this
            // function explicitly will be assigned a synthetic field trial group.
            if (mSpeculativeLoadingAllowedFlags != flags
                    || !mHasCalledSetSpeculativeLoadingAllowedBefore) {
                mSpeculativeLoadingAllowedFlags = flags;
                mHasCalledSetSpeculativeLoadingAllowedBefore = true;
                mEventHandler.updateSpeculativeLoadingAllowedLocked();
            }
        }
    }

    @CalledByNative
    @SpeculativeLoadingAllowedFlags
    public int getSpeculativeLoadingAllowed() {
        synchronized (mAwSettingsLock) {
            return mSpeculativeLoadingAllowedFlags;
        }
    }

    public void setBackForwardCacheEnabled(boolean enabled) {
        if (TRACE) Log.i(TAG, "setBackForwardCacheEnabled = " + enabled);
        synchronized (mAwSettingsLock) {
            // Only trigger an update if the value changed, or this is the first time we call this
            // function. The latter is important to make sure every embedder that calls this
            // function explicitly will be assigned a synthetic field trial group.
            if (mBackForwardCacheEnabled != enabled
                    || !mHasCalledSetBackForwardCacheEnabledBefore) {
                mBackForwardCacheEnabled = enabled;
                mHasCalledSetBackForwardCacheEnabledBefore = true;
                mEventHandler.updateBackForwardCacheEnabled();
            }
        }
    }

    @CalledByNative
    public boolean getBackForwardCacheEnabled() {
        synchronized (mAwSettingsLock) {
            return mBackForwardCacheEnabled;
        }
    }

    @ForceDarkMode
    public int getForceDarkMode() {
        synchronized (mAwSettingsLock) {
            return getForceDarkModeLocked();
        }
    }

    @CalledByNative
    @ForceDarkMode
    private int getForceDarkModeLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mForceDarkMode;
    }

    public void setForceDarkMode(@ForceDarkMode int forceDarkMode) {
        AwWebContentsMetricsRecorder.recordForceDarkModeAPIUsage(mContext, forceDarkMode);
        synchronized (mAwSettingsLock) {
            if (mForceDarkMode != forceDarkMode) {
                mForceDarkMode = forceDarkMode;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    public boolean isAlgorithmicDarkeningAllowed() {
        synchronized (mAwSettingsLock) {
            return isAlgorithmicDarkeningAllowedLocked();
        }
    }

    @CalledByNative
    private boolean isAlgorithmicDarkeningAllowedLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mAlgorithmicDarkeningAllowed;
    }

    public void setAlgorithmicDarkeningAllowed(boolean allow) {
        synchronized (mAwSettingsLock) {
            if (mAlgorithmicDarkeningAllowed != allow) {
                mAlgorithmicDarkeningAllowed = allow;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    public boolean isForceDarkApplied() {
        synchronized (mAwSettingsLock) {
            assert mNativeAwSettings != 0;
            return AwSettingsJni.get().isForceDarkApplied(mNativeAwSettings, AwSettings.this);
        }
    }

    public boolean prefersDarkFromTheme() {
        synchronized (mAwSettingsLock) {
            assert mNativeAwSettings != 0;
            return AwSettingsJni.get().prefersDarkFromTheme(mNativeAwSettings, AwSettings.this);
        }
    }

    @ForceDarkBehavior
    public int getForceDarkBehavior() {
        synchronized (mAwSettingsLock) {
            return getForceDarkBehaviorLocked();
        }
    }

    @CalledByNative
    @ForceDarkBehavior
    public int getForceDarkBehaviorLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mForceDarkBehavior;
    }

    public void setForceDarkBehavior(@ForceDarkBehavior int forceDarkBehavior) {
        AwWebContentsMetricsRecorder.recordForceDarkBehaviorAPIUsage(forceDarkBehavior);
        synchronized (mAwSettingsLock) {
            if (mForceDarkBehavior != forceDarkBehavior) {
                mForceDarkBehavior = forceDarkBehavior;
                mEventHandler.updateWebkitPreferencesLocked();
            }
        }
    }

    @CalledByNative
    private boolean getAllowRunningInsecureContentLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mMixedContentMode == WebSettings.MIXED_CONTENT_ALWAYS_ALLOW;
    }

    @CalledByNative
    private boolean getUseStricMixedContentCheckingLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mMixedContentMode == WebSettings.MIXED_CONTENT_NEVER_ALLOW;
    }

    @CalledByNative
    private boolean getAllowMixedContentAutoupgradesLocked() {
        if (AwFeatureMap.isEnabled(AwFeatures.WEBVIEW_MIXED_CONTENT_AUTOUPGRADES)) {
            // We only allow mixed content autoupgrades (upgrading HTTP subresources to HTTPS in
            // HTTPS sites) when the mixed content mode is set to MIXED_CONTENT_COMPATIBILITY,
            // which keeps it in line with the behavior in Chrome. With
            // MIXED_CONTENT_ALWAYS_ALLOW, we disable autoupgrades since the developer is
            // explicitly allowing mixed content, whereas with MIXED_CONTENT_NEVER_ALLOW, there
            // is no need to autoupgrade since the content will be blocked.
            assert Thread.holdsLock(mAwSettingsLock);
            return mMixedContentMode == WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE;
        }
        return false;
    }

    public boolean getOffscreenPreRaster() {
        synchronized (mAwSettingsLock) {
            return getOffscreenPreRasterLocked();
        }
    }

    @CalledByNative
    private boolean getOffscreenPreRasterLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mOffscreenPreRaster;
    }

    /**
     * Sets whether this WebView should raster tiles when it is
     * offscreen but attached to window. Turning this on can avoid
     * rendering artifacts when animating an offscreen WebView on-screen.
     * In particular, insertVisualStateCallback requires this mode to function.
     * Offscreen WebViews in this mode uses more memory. Please follow
     * these guidelines to limit memory usage:
     * - Webview size should be not be larger than the device screen size.
     * - Limit simple mode to a small number of webviews. Use it for
     *   visible webviews and webviews about to be animated to visible.
     */
    public void setOffscreenPreRaster(boolean enabled) {
        synchronized (mAwSettingsLock) {
            if (enabled != mOffscreenPreRaster) {
                mOffscreenPreRaster = enabled;
                mEventHandler.runOnUiThreadBlockingAndLocked(
                        () -> updateOffscreenPreRasterOnUiThreadLocked());
            }
        }
    }

    public int getDisabledActionModeMenuItems() {
        synchronized (mAwSettingsLock) {
            return mDisabledMenuItems;
        }
    }

    public void setDisabledActionModeMenuItems(int menuItems) {
        synchronized (mAwSettingsLock) {
            if (mDisabledMenuItems != menuItems) {
                flushBackForwardCacheOnUiThreadLocked();
            }
            mDisabledMenuItems = menuItems;
        }
    }

    public void updateAcceptLanguages() {
        synchronized (mAwSettingsLock) {
            mEventHandler.runOnUiThreadBlockingAndLocked(
                    () -> updateRendererPreferencesOnUiThreadLocked());
        }
    }

    @CalledByNative
    private boolean supportsDoubleTapZoomLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSupportZoom && mBuiltInZoomControls && mUseWideViewport;
    }

    private boolean supportsMultiTouchZoomLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mSupportZoom && mBuiltInZoomControls;
    }

    boolean supportsMultiTouchZoom() {
        synchronized (mAwSettingsLock) {
            return supportsMultiTouchZoomLocked();
        }
    }

    boolean shouldDisplayZoomControls() {
        synchronized (mAwSettingsLock) {
            return supportsMultiTouchZoomLocked() && mDisplayZoomControls;
        }
    }

    private int clipFontSize(int size) {
        if (size < MINIMUM_FONT_SIZE) {
            return MINIMUM_FONT_SIZE;
        } else if (size > MAXIMUM_FONT_SIZE) {
            return MAXIMUM_FONT_SIZE;
        }
        return size;
    }

    @CalledByNative
    private boolean getRecordFullDocument() {
        assert Thread.holdsLock(mAwSettingsLock);
        return AwContentsStatics.getRecordFullDocument();
    }

    @CalledByNative
    private void updateEverything() {
        synchronized (mAwSettingsLock) {
            updateEverythingLocked();
        }
    }

    @CalledByNative
    private void populateWebPreferences(long webPrefsPtr) {
        synchronized (mAwSettingsLock) {
            assert mNativeAwSettings != 0;
            AwSettingsJni.get()
                    .populateWebPreferencesLocked(mNativeAwSettings, AwSettings.this, webPrefsPtr);
        }
    }

    private void updateInitialPageScaleOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateInitialPageScaleLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateUserAgentOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateUserAgentLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateWebkitPreferencesOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateWebkitPreferencesLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateRendererPreferencesOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateRendererPreferencesLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateOffscreenPreRasterOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateOffscreenPreRasterLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateWillSuppressErrorStateOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get()
                    .updateWillSuppressErrorStateLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateCookiePolicyOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateCookiePolicyLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateAllowFileAccessOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        flushBackForwardCache();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateAllowFileAccessLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateSpeculativeLoadingAllowedOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get()
                    .updateSpeculativeLoadingAllowedLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateBackForwardCacheEnabledOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get()
                    .updateBackForwardCacheEnabledLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    private void updateGeolocationEnabledOnUiThreadLocked() {
        assert mEventHandler.mHandler != null;
        ThreadUtils.assertOnUiThread();
        if (mNativeAwSettings != 0) {
            AwSettingsJni.get().updateGeolocationEnabledLocked(mNativeAwSettings, AwSettings.this);
        }
    }

    public void setEnterpriseAuthenticationAppLinkPolicyEnabled(boolean enabled) {
        synchronized (mAwSettingsLock) {
            mEventHandler.runOnUiThreadBlockingAndLocked(
                    () -> {
                        flushBackForwardCache();
                        if (mNativeAwSettings != 0) {
                            AwSettingsJni.get()
                                    .setEnterpriseAuthenticationAppLinkPolicyEnabled(
                                            mNativeAwSettings, AwSettings.this, enabled);
                        }
                    });
        }
    }

    public boolean getEnterpriseAuthenticationAppLinkPolicyEnabled() {
        synchronized (mAwSettingsLock) {
            assert mNativeAwSettings != 0;
            return AwSettingsJni.get()
                    .getEnterpriseAuthenticationAppLinkPolicyEnabled(
                            mNativeAwSettings, AwSettings.this);
        }
    }

    public void setWebViewIntegrityApiStatus(
            @MediaIntegrityApiStatus int defaultStatus,
            Map<String, @MediaIntegrityApiStatus Integer> permissionConfig) {
        synchronized (mAwSettingsLock) {
            mIntegrityApiStatusConfig.setApiAvailabilityRules(defaultStatus, permissionConfig);
        }
    }

    public @MediaIntegrityApiStatus int getWebViewIntegrityApiDefaultStatus() {
        synchronized (mAwSettingsLock) {
            return mIntegrityApiStatusConfig.getDefaultStatus();
        }
    }

    public Map<String, @MediaIntegrityApiStatus Integer> getWebViewIntegrityApiOverrideRules() {
        synchronized (mAwSettingsLock) {
            return mIntegrityApiStatusConfig.getOverrideRules();
        }
    }

    public @MediaIntegrityApiStatus int getWebViewIntegrityApiStatusForUri(Uri uri) {
        synchronized (mAwSettingsLock) {
            return mIntegrityApiStatusConfig.getStatusForUri(uri);
        }
    }

    public void setWebauthnSupport(@WebauthnMode int support) {
        synchronized (mAwSettingsLock) {
            if (mWebauthnMode != support) {
                mWebauthnMode = support;
                mEventHandler.updateWebkitPreferencesLocked();
                WebauthnModeProvider.getInstance()
                        .setWebauthnModeForWebContents(mWebContents, support);
            }
        }
    }

    @CalledByNative
    public @WebauthnMode int getWebauthnSupportLocked() {
        assert Thread.holdsLock(mAwSettingsLock);
        return mWebauthnMode;
    }

    public int getWebauthnSupport() {
        synchronized (mAwSettingsLock) {
            return getWebauthnSupportLocked();
        }
    }

    @NativeMethods
    interface Natives {
        long init(AwSettings caller, WebContents webContents);

        void destroy(long nativeAwSettings, AwSettings caller);

        AwSettings fromWebContents(WebContents webContents);

        void populateWebPreferencesLocked(
                long nativeAwSettings, AwSettings caller, long webPrefsPtr);

        void resetScrollAndScaleState(long nativeAwSettings, AwSettings caller);

        void updateEverythingLocked(long nativeAwSettings, AwSettings caller);

        void updateInitialPageScaleLocked(long nativeAwSettings, AwSettings caller);

        void updateUserAgentLocked(long nativeAwSettings, AwSettings caller);

        void updateWebkitPreferencesLocked(long nativeAwSettings, AwSettings caller);

        String getDefaultUserAgent();

        AwUserAgentMetadata getDefaultUserAgentMetadata();

        void updateRendererPreferencesLocked(long nativeAwSettings, AwSettings caller);

        void updateOffscreenPreRasterLocked(long nativeAwSettings, AwSettings caller);

        void updateWillSuppressErrorStateLocked(long nativeAwSettings, AwSettings caller);

        void updateCookiePolicyLocked(long nativeAwSettings, AwSettings caller);

        void updateAllowFileAccessLocked(long nativeAwSettings, AwSettings caller);

        void updateSpeculativeLoadingAllowedLocked(long nativeAwSettings, AwSettings caller);

        void updateBackForwardCacheEnabledLocked(long nativeAwSettings, AwSettings caller);

        boolean isForceDarkApplied(long nativeAwSettings, AwSettings caller);

        boolean prefersDarkFromTheme(long nativeAwSettings, AwSettings caller);

        void setEnterpriseAuthenticationAppLinkPolicyEnabled(
                long nativeAwSettings, AwSettings caller, boolean enabled);

        boolean getEnterpriseAuthenticationAppLinkPolicyEnabled(
                long nativeAwSettings, AwSettings caller);

        String[] updateXRequestedWithAllowListOriginMatcher(long nativeAwSettings, String[] rules);

        void updateGeolocationEnabledLocked(long nativeAwSettings, AwSettings caller);
    }
}