From: uazo <uazo@users.noreply.github.com>
Date: Fri, 9 Apr 2021 20:09:08 +0000
Subject: User agent customization

Add flag to always view the desktop site for all websites
Add possibility to define a custom User agent for mobile and desktop mode.
Add possibility to reactivate the metatag view for desktop mode, allowing users to choose
to use the flag in the hamburger menu to navigate with a custom useragent leaving the standard navigation unchanged.

Original License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html
License: GPL-3.0-only - https://spdx.org/licenses/GPL-3.0-only.html
---
 base/base_switches.cc                         |   2 +
 base/base_switches.h                          |   2 +
 chrome/android/chrome_java_resources.gni      |   2 +
 chrome/android/chrome_java_sources.gni        |   1 +
 .../layout/custom_useragent_preferences.xml   | 106 ++++++++++
 .../android/java/res/xml/main_preferences.xml |   5 +
 .../java/res/xml/useragent_preferences.xml    |  31 +++
 .../init/ChromeBrowserInitializer.java        |   3 +
 .../PrivacyPreferencesManagerImpl.java        |  42 ++++
 .../settings/UserAgentPreferences.java        | 188 ++++++++++++++++++
 .../chromium/chrome/browser/tab/TabImpl.java  |  89 ++++++++-
 .../chromium/chrome/browser/tab/TabUtils.java |  24 ++-
 .../browser/android/content/content_utils.cc  |  28 +++
 .../preferences/browser_prefs_android.cc      |   7 +
 .../privacy_preferences_manager_impl.cc       | 121 +++++++++++
 .../preferences/ChromePreferenceKeys.java     |   5 +
 .../settings/PrivacyPreferencesManager.java   |   8 +
 .../org/chromium/chrome/browser/tab/Tab.java  |   2 +
 .../browser/tabmodel/TabWindowManager.java    |   2 +
 .../tabmodel/TabWindowManagerImpl.java        |  18 ++
 .../strings/android_chrome_strings.grd        |  35 ++++
 chrome/common/pref_names.cc                   |  13 ++
 chrome/common/pref_names.h                    |   8 +
 .../widget/RadioButtonWithEditText.java       |  11 +
 .../embedder_support/user_agent_utils.cc      |  15 +-
 .../navigation_controller_android.cc          |   4 +
 .../navigation_controller_android.h           |   1 +
 .../renderer_host/render_process_host_impl.cc |   1 +
 .../browser/web_contents/web_contents_impl.cc |   4 +
 .../framehost/NavigationControllerImpl.java   |   3 +-
 content/renderer/render_thread_impl.cc        |   1 -
 31 files changed, 766 insertions(+), 16 deletions(-)
 create mode 100644 chrome/android/java/res/layout/custom_useragent_preferences.xml
 create mode 100644 chrome/android/java/res/xml/useragent_preferences.xml
 create mode 100644 chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java

diff --git a/base/base_switches.cc b/base/base_switches.cc
--- a/base/base_switches.cc
+++ b/base/base_switches.cc
@@ -177,6 +177,8 @@ const char kForceFieldTrialParams[] = "force-fieldtrial-params";
 extern const char kEnableCrashpad[] = "enable-crashpad";
 #endif
 
+const char kDesktopModeViewportMetaEnabled[] = "dm-viewport-meta-enabled";
+
 #if BUILDFLAG(IS_CHROMEOS)
 // Override the default scheduling boosting value for urgent tasks.
 // This can be adjusted if a specific chromeos device shows better perf/power
diff --git a/base/base_switches.h b/base/base_switches.h
--- a/base/base_switches.h
+++ b/base/base_switches.h
@@ -70,6 +70,8 @@ extern const char kEnableCrashpad[];
 extern const char kSchedulerBoostUrgent[];
 #endif
 
+extern const char kDesktopModeViewportMetaEnabled[];
+
 }  // namespace switches
 
 #endif  // BASE_BASE_SWITCHES_H_
diff --git a/chrome/android/chrome_java_resources.gni b/chrome/android/chrome_java_resources.gni
--- a/chrome/android/chrome_java_resources.gni
+++ b/chrome/android/chrome_java_resources.gni
@@ -659,4 +659,6 @@ chrome_java_resources = [
   "java/res/xml/privacy_preferences.xml",
   "java/res/xml/search_widget_info.xml",
   "java/res/xml/tracing_preferences.xml",
+  "java/res/xml/useragent_preferences.xml",
+  "java/res/layout/custom_useragent_preferences.xml",
 ]
diff --git a/chrome/android/chrome_java_sources.gni b/chrome/android/chrome_java_sources.gni
--- a/chrome/android/chrome_java_sources.gni
+++ b/chrome/android/chrome_java_sources.gni
@@ -898,6 +898,7 @@ chrome_java_sources = [
   "java/src/org/chromium/chrome/browser/payments/ui/DimmingDialog.java",
   "java/src/org/chromium/chrome/browser/payments/ui/LineItem.java",
   "java/src/org/chromium/chrome/browser/payments/ui/PaymentAppComparator.java",
+  "java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java",
   "java/src/org/chromium/chrome/browser/payments/ui/PaymentInformation.java",
   "java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestBottomBar.java",
   "java/src/org/chromium/chrome/browser/payments/ui/PaymentRequestHeader.java",
diff --git a/chrome/android/java/res/layout/custom_useragent_preferences.xml b/chrome/android/java/res/layout/custom_useragent_preferences.xml
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/res/layout/custom_useragent_preferences.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    This file is part of Bromite.
+
+    Bromite is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Bromite is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+-->
+
+<!-- Layout used by the UserAgentPreferences. -->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:focusable="false"
+        android:orientation="vertical"
+        android:divider="?android:dividerHorizontal">
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.AccessibilityTextPreference"
+            android:background="@color/default_bg_color_secondary"
+            android:padding="16dp"
+            android:text="@string/custom_ua_text"/>
+
+        <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
+        android:id="@+id/ua_radio_button_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+            <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+                android:id="@+id/default_ua_switch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+                app:primaryText="@string/custom_ua_flag_off" />
+
+            <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
+                android:id="@+id/custom_ua_switch"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+                android:inputType="text"
+                android:hint="@string/custom_ua_placeholder"
+                app:descriptionText="@string/custom_ua_flag_on" />
+
+        </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
+
+        <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAppearance="@style/TextAppearance.AccessibilityTextPreference"
+            android:background="@color/default_bg_color_secondary"
+            android:padding="16dp"
+            android:text="@string/custom_desktop_ua_text"/>
+
+        <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
+        android:id="@+id/ua_radio_button_layout_dm"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+            <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+                android:id="@+id/default_ua_switch_dm"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+                app:primaryText="@string/custom_ua_flag_off" />
+
+            <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
+                android:id="@+id/custom_ua_switch_dm"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+                android:inputType="text"
+                android:hint="@string/custom_ua_placeholder"
+                app:descriptionText="@string/custom_ua_flag_on" />
+
+        </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
+
+        <CheckBox
+            android:id="@+id/desktop_mode_viewportmeta"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerVertical="true"
+            android:layout_marginLeft="?android:attr/listPreferredItemPaddingStart"
+            android:text="@string/desktop_mode_viewportmeta_checkbox" />
+
+    </LinearLayout>
+
+</ScrollView>
diff --git a/chrome/android/java/res/xml/main_preferences.xml b/chrome/android/java/res/xml/main_preferences.xml
--- a/chrome/android/java/res/xml/main_preferences.xml
+++ b/chrome/android/java/res/xml/main_preferences.xml
@@ -83,6 +83,11 @@ found in the LICENSE file.
         android:key="content_settings"
         android:order="18"
         android:title="@string/prefs_site_settings"/>
+    <Preference
+        android:fragment="org.chromium.chrome.browser.settings.UserAgentPreferences"
+        android:key="useragent_settings"
+        android:order="20"
+        android:title="@string/prefs_useragent_settings"/>
     <Preference
         android:fragment="org.chromium.chrome.browser.language.settings.LanguageSettings"
         android:key="languages"
diff --git a/chrome/android/java/res/xml/useragent_preferences.xml b/chrome/android/java/res/xml/useragent_preferences.xml
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/res/xml/useragent_preferences.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    This file is part of Bromite.
+
+    Bromite is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Bromite is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+-->
+
+<!-- Layout used by the UserAgentPreferences. -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
+        android:key="desktop_mode_switch"
+        android:title="@string/option_desktop_flag"
+        android:summaryOn="@string/option_desktop_flag_on"
+        android:summaryOff="@string/option_desktop_flag_off" />
+
+</PreferenceScreen>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/init/ChromeBrowserInitializer.java
@@ -50,6 +50,7 @@ import org.chromium.content_public.browser.DeviceUtils;
 import org.chromium.content_public.browser.SpeechRecognition;
 import org.chromium.content_public.browser.UiThreadTaskTraits;
 import org.chromium.net.NetworkChangeNotifier;
+import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -319,11 +320,13 @@ public class ChromeBrowserInitializer {
 
                         @Override
                         public void onSuccess() {
+                            PrivacyPreferencesManagerImpl.getInstance().updateOverrideUserAgent();
                             tasks.start(false);
                         }
                     });
         } else {
             startChromeBrowserProcessesSync();
+            PrivacyPreferencesManagerImpl.getInstance().updateOverrideUserAgent();
             tasks.start(true);
         }
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManagerImpl.java
@@ -191,10 +191,52 @@ public class PrivacyPreferencesManagerImpl implements PrivacyPreferencesManager
         PrivacyPreferencesManagerImplJni.get().setMetricsReportingEnabled(enabled);
     }
 
+    @Override
+    public void updateOverrideUserAgent() {
+        PrivacyPreferencesManagerImplJni.get().updateOverrideUserAgent();
+    }
+
+    @Override
+    public boolean isOverrideUserAgentEnabled(boolean desktopMode) {
+        return PrivacyPreferencesManagerImplJni.get().isOverrideUserAgentEnabled(desktopMode);
+    }
+
+    @Override
+    public void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode) {
+        PrivacyPreferencesManagerImplJni.get().setOverrideUserAgentEnabled(enabled, desktopMode);
+    }
+
+    @Override
+    public String getOverrideUserAgentValue(boolean desktopMode) {
+        return PrivacyPreferencesManagerImplJni.get().getOverrideUserAgentValue(desktopMode);
+    }
+
+    @Override
+    public void setOverrideUserAgentValue(String user_agent, boolean desktopMode) {
+        PrivacyPreferencesManagerImplJni.get().setOverrideUserAgentValue(user_agent, desktopMode);
+    }
+
+    @Override
+    public boolean isDesktopModeViewportMetaEnabled() {
+        return PrivacyPreferencesManagerImplJni.get().isDesktopModeViewportMetaEnabled();
+    }
+
+    @Override
+    public void setDesktopModeViewportMetaEnabled(boolean enabled) {
+        PrivacyPreferencesManagerImplJni.get().setDesktopModeViewportMetaEnabled(enabled);
+    }
+
     @NativeMethods
     public interface Natives {
         boolean isMetricsReportingEnabled();
         void setMetricsReportingEnabled(boolean enabled);
         boolean isMetricsReportingDisabledByPolicy();
+        void updateOverrideUserAgent();
+        boolean isOverrideUserAgentEnabled(boolean desktopMode);
+        void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode);
+        String getOverrideUserAgentValue(boolean desktopMode);
+        void setOverrideUserAgentValue(String user_agent, boolean desktopMode);
+        boolean isDesktopModeViewportMetaEnabled();
+        void setDesktopModeViewportMetaEnabled(boolean enabled);
     }
 }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java b/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java
new file mode 100644
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/settings/UserAgentPreferences.java
@@ -0,0 +1,188 @@
+/*
+    This file is part of Bromite.
+
+    Bromite is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Bromite is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Bromite. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+package org.chromium.chrome.browser.settings;
+
+import android.os.Bundle;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceViewHolder;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.widget.RadioGroup;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
+import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
+import org.chromium.components.browser_ui.widget.RadioButtonWithEditText;
+import org.chromium.components.browser_ui.settings.SettingsUtils;
+
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+
+import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
+import org.chromium.chrome.browser.tabmodel.TabWindowManager;
+import org.chromium.chrome.browser.privacy.settings.PrivacyPreferencesManagerImpl;
+import org.chromium.chrome.R;
+
+/**
+ * Fragment that allows the user to configure User Agent related preferences.
+ */
+public class UserAgentPreferences
+        extends PreferenceFragmentCompat implements RadioGroup.OnCheckedChangeListener {
+
+    private static final String PREF_STICK_DESKTOP_MODE_SWITCH = "desktop_mode_switch";
+    private RadioButtonWithDescription useDefaultAgentSwitch;
+    private RadioButtonWithEditText useCustomAgentSwitch;
+    private RadioButtonWithDescription useDefaultAgentSwitchDesktopMode;
+    private RadioButtonWithEditText useCustomAgentSwitchDesktopMode;
+    private RadioGroup mRadioGroup;
+    private RadioGroup mRadioGroupDesktopMode;
+    private CheckBox mDesktopModeViewportmeta;
+
+    @Override
+    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+        getActivity().setTitle(R.string.useragent_settings_title);
+        SettingsUtils.addPreferencesFromResource(this, R.xml.useragent_preferences);
+
+        ChromeSwitchPreference alwaysDesktopModeSwitch =
+                (ChromeSwitchPreference) findPreference(PREF_STICK_DESKTOP_MODE_SWITCH);
+        boolean enabled = SharedPreferencesManager.getInstance().readBoolean(
+            ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
+        alwaysDesktopModeSwitch.setChecked(enabled);
+        alwaysDesktopModeSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
+            SharedPreferencesManager.getInstance().writeBoolean(
+                ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, (boolean) newValue);
+            UpdateAllTabs();
+            return true;
+        });
+    }
+
+    @Override
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        LinearLayout viewGroup = (LinearLayout) super.onCreateView(inflater, container, savedInstanceState);
+        LinearLayout.LayoutParams params =
+            new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
+        viewGroup.setLayoutParams(params);
+        ScrollView view = (ScrollView) inflater.inflate(R.layout.custom_useragent_preferences, viewGroup, false);
+        viewGroup.addView(view);
+
+        boolean enabledCustomUA = PrivacyPreferencesManagerImpl.getInstance().isOverrideUserAgentEnabled(false);
+        boolean enabledCustomUADesktopMode = PrivacyPreferencesManagerImpl.getInstance().isOverrideUserAgentEnabled(true);
+        boolean enabledDesktopModeViewportmeta = PrivacyPreferencesManagerImpl.getInstance().isDesktopModeViewportMetaEnabled();
+
+        useDefaultAgentSwitch =
+                (RadioButtonWithDescription) view.findViewById(R.id.default_ua_switch);
+        useCustomAgentSwitch =
+                (RadioButtonWithEditText) view.findViewById(R.id.custom_ua_switch);
+        useDefaultAgentSwitchDesktopMode =
+                (RadioButtonWithDescription) view.findViewById(R.id.default_ua_switch_dm);
+        useCustomAgentSwitchDesktopMode =
+                (RadioButtonWithEditText) view.findViewById(R.id.custom_ua_switch_dm);
+
+        mRadioGroup = (RadioGroup) view.findViewById(R.id.ua_radio_button_layout);
+        mRadioGroup.setOnCheckedChangeListener(this);
+
+        mRadioGroupDesktopMode = (RadioGroup) view.findViewById(R.id.ua_radio_button_layout_dm);
+        mRadioGroupDesktopMode.setOnCheckedChangeListener(this);
+
+        mDesktopModeViewportmeta = (CheckBox) view.findViewById(R.id.desktop_mode_viewportmeta);
+        mDesktopModeViewportmeta.setChecked(enabledDesktopModeViewportmeta);
+        mDesktopModeViewportmeta.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                PrivacyPreferencesManagerImpl.getInstance().setDesktopModeViewportMetaEnabled(
+                    mDesktopModeViewportmeta.isChecked());
+            }
+        });
+
+        useDefaultAgentSwitch.setChecked(!enabledCustomUA);
+        useCustomAgentSwitch.setChecked(enabledCustomUA);
+
+        useDefaultAgentSwitchDesktopMode.setChecked(!enabledCustomUADesktopMode);
+        useCustomAgentSwitchDesktopMode.setChecked(enabledCustomUADesktopMode);
+
+        useCustomAgentSwitch.setPrimaryText(
+            PrivacyPreferencesManagerImpl.getInstance().getOverrideUserAgentValue(false));
+        useCustomAgentSwitch.addTextChangeListener(new RadioButtonWithEditText.OnTextChangeListener() {
+            @Override
+            public void onTextChanged(CharSequence newText) {
+                PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentValue(
+                    newText.toString(), false);
+            }
+        });
+        useCustomAgentSwitch.setFocusChangeListener( hasFocus -> {
+            if( hasFocus )
+                PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, false);
+        });
+
+        useCustomAgentSwitchDesktopMode.setPrimaryText(
+            PrivacyPreferencesManagerImpl.getInstance().getOverrideUserAgentValue(true));
+        useCustomAgentSwitchDesktopMode.addTextChangeListener(new RadioButtonWithEditText.OnTextChangeListener() {
+            @Override
+            public void onTextChanged(CharSequence newText) {
+                PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentValue(
+                    newText.toString(), true);
+            }
+        });
+        useCustomAgentSwitchDesktopMode.setFocusChangeListener( hasFocus -> {
+            if( hasFocus )
+                PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, true);
+        });
+
+        return viewGroup;
+    }
+
+    private void UpdateAllTabs() {
+        final boolean alwaysDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
+            ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false);
+        TabWindowManagerSingleton.getInstance().SetOverrideUserAgentForAllTabs(alwaysDesktopModeEnabled);
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        if (useDefaultAgentSwitch.isChecked()) {
+            PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(false, false);
+        } else if (useCustomAgentSwitch.isChecked()) {
+            PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, false);
+        }
+
+        if (useDefaultAgentSwitchDesktopMode.isChecked()) {
+            PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(false, true);
+        } else if (useCustomAgentSwitchDesktopMode.isChecked()) {
+            PrivacyPreferencesManagerImpl.getInstance().setOverrideUserAgentEnabled(true, true);
+        }
+
+        UpdateAllTabs();
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        UpdateAllTabs();
+    }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -28,7 +28,11 @@ import org.chromium.base.annotations.CalledByNative;
 import org.chromium.base.annotations.NativeMethods;
 import org.chromium.base.metrics.RecordHistogram;
 import org.chromium.base.metrics.RecordUserAction;
+import org.chromium.content_public.browser.NavigationController;
 import org.chromium.base.supplier.ObservableSupplierImpl;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+import org.chromium.components.embedder_support.util.UrlUtilities;
 import org.chromium.base.supplier.Supplier;
 import org.chromium.chrome.R;
 import org.chromium.chrome.browser.ActivityUtils;
@@ -530,6 +534,32 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
                 params.setOverrideUserAgent(calculateUserAgentOverrideOption(null));
             }
 
+            final boolean stickyDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
+                ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
+            if (stickyDesktopModeEnabled) {
+                boolean alwaysDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
+                    ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false);
+
+                if (UrlUtilities.isInternalScheme(UrlFormatter.fixupUrl(params.getUrl()))) {
+                    alwaysDesktopModeEnabled = false;
+                }
+
+                WebContents webContents = this.getWebContents();
+                if (webContents != null) {
+                    NavigationController navigationController = webContents.getNavigationController();
+                    boolean currentUseDesktopUserAgent = navigationController.getUseDesktopUserAgent();
+                    if (currentUseDesktopUserAgent != alwaysDesktopModeEnabled)
+                        navigationController.setUseDesktopUserAgent(alwaysDesktopModeEnabled, false,
+                            UseDesktopUserAgentCaller.OTHER);
+                }
+
+                if (alwaysDesktopModeEnabled) {
+                    params.setOverrideUserAgent((int)UserAgentOverrideOption.TRUE);
+                } else {
+                    params.setOverrideUserAgent((int)UserAgentOverrideOption.FALSE);
+                }
+            }
+
             @TabLoadStatus
             int result = loadUrlInternal(params, fixedUrl);
 
@@ -1418,8 +1448,15 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
 
             mWebContents.setImportance(mImportance);
 
-            ContentUtils.setUserAgentOverride(mWebContents,
-                    calculateUserAgentOverrideOption(null) == UserAgentOverrideOption.TRUE);
+            if (SharedPreferencesManager.getInstance().readBoolean(
+                        ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false) &&
+                SharedPreferencesManager.getInstance().readBoolean(
+                        ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false)) {
+                ContentUtils.setUserAgentOverride(mWebContents, true);
+            } else {
+                ContentUtils.setUserAgentOverride(mWebContents,
+                        calculateUserAgentOverrideOption(null) == UserAgentOverrideOption.TRUE);
+            }
 
             mContentView.addOnAttachStateChangeListener(mAttachStateChangeListener);
             updateInteractableState();
@@ -1577,6 +1614,10 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
             if (mWebContents != null) mWebContents.getNavigationController().loadIfNecessary();
             mIsBeingRestored = true;
             for (TabObserver observer : mObservers) observer.onRestoreStarted(this);
+            if(overrideUserAgentWhenUnFrozen != UserAgentOverrideOption.INHERIT) {
+                SetOverrideUserAgent(overrideUserAgentWhenUnFrozen == (int)UserAgentOverrideOption.TRUE ? true : false,
+                    /*forcedByUser*/ true);
+            }
         } finally {
             TraceEvent.end("Tab.restoreIfNeeded");
         }
@@ -1713,6 +1754,13 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
     }
 
     private @UserAgentOverrideOption int calculateUserAgentOverrideOption(@Nullable GURL url) {
+        if (SharedPreferencesManager.getInstance().readBoolean(
+                    ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false) &&
+                SharedPreferencesManager.getInstance().readBoolean(
+                    ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, false)) {
+            return UserAgentOverrideOption.INHERIT;
+        }
+
         WebContents webContents = getWebContents();
         boolean currentRequestDesktopSite = TabUtils.isUsingDesktopUserAgent(webContents);
         @TabUserAgent
@@ -1773,6 +1821,43 @@ public class TabImpl implements Tab, TabObscuringHandler.Observer {
                 /* forcedByUser */ false, caller);
     }
 
+    int overrideUserAgentWhenUnFrozen = (int)UserAgentOverrideOption.INHERIT;
+
+    public void SetOverrideUserAgent(boolean usingDesktopUserAgent, boolean forcedByUser) {
+        WebContents webContents = this.getWebContents();
+        overrideUserAgentWhenUnFrozen = UserAgentOverrideOption.INHERIT;
+
+        if (usingDesktopUserAgent) {
+            GURL url = this.getUrl();
+            if (webContents == null && this.getPendingLoadParams() != null) {
+                url = UrlFormatter.fixupUrl(this.getPendingLoadParams().getUrl());
+            }
+            if (UrlUtilities.isInternalScheme(url) == true)
+                usingDesktopUserAgent = false;
+        }
+
+        if (webContents != null) {
+            ContentUtils.setUserAgentOverride(webContents, /*forcedByUser*/ true);
+
+            NavigationController navigationController = webContents.getNavigationController();
+            navigationController.setUseDesktopUserAgent(
+                usingDesktopUserAgent, !this.isNativePage(), UseDesktopUserAgentCaller.OTHER);
+            if (forcedByUser) CriticalPersistedTabData.from(this).setUserAgent(TabUserAgent.DESKTOP);
+        }
+        else if (this.getPendingLoadParams() != null) {
+            if (usingDesktopUserAgent) {
+                this.getPendingLoadParams().setOverrideUserAgent((int)UserAgentOverrideOption.TRUE);
+            }
+            else {
+                this.getPendingLoadParams().setOverrideUserAgent((int)UserAgentOverrideOption.FALSE);
+            }
+        }
+        else {
+            overrideUserAgentWhenUnFrozen = usingDesktopUserAgent ? UserAgentOverrideOption.TRUE :
+                                                                    UserAgentOverrideOption.FALSE;
+        }
+    }
+
     @NativeMethods
     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
     public interface Natives {
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabUtils.java
@@ -36,6 +36,10 @@ import org.chromium.ui.base.WindowAndroid;
 import org.chromium.ui.display.DisplayAndroidManager;
 import org.chromium.url.GURL;
 
+import org.chromium.chrome.browser.app.tabmodel.TabWindowManagerSingleton;
+import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
+import org.chromium.chrome.browser.preferences.SharedPreferencesManager;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -140,17 +144,15 @@ public class TabUtils {
      */
     public static void switchUserAgent(
             Tab tab, boolean switchToDesktop, boolean forcedByUser, int caller) {
-        final boolean reloadOnChange = !tab.isNativePage();
-        tab.getWebContents().getNavigationController().setUseDesktopUserAgent(
-                switchToDesktop, reloadOnChange, caller);
-        if (forcedByUser) {
-            @TabUserAgent
-            int tabUserAgent = switchToDesktop ? TabUserAgent.DESKTOP : TabUserAgent.MOBILE;
-            if (isDesktopSiteGlobalEnabled(Profile.fromWebContents(tab.getWebContents()))
-                    == switchToDesktop) {
-                tabUserAgent = TabUserAgent.DEFAULT;
-            }
-            CriticalPersistedTabData.from(tab).setUserAgent(tabUserAgent);
+        SharedPreferencesManager.getInstance().writeBoolean(
+            ChromePreferenceKeys.USERAGENT_ALWAYS_DESKTOP_MODE, switchToDesktop);
+
+        final boolean stickyDesktopModeEnabled = SharedPreferencesManager.getInstance().readBoolean(
+                ChromePreferenceKeys.USERAGENT_STICKY_DESKTOP_MODE, false);
+        if (stickyDesktopModeEnabled) {
+            TabWindowManagerSingleton.getInstance().SetOverrideUserAgentForAllTabs(switchToDesktop);
+        } else {
+            tab.SetOverrideUserAgent(switchToDesktop, forcedByUser);
         }
     }
 
diff --git a/chrome/browser/android/content/content_utils.cc b/chrome/browser/android/content/content_utils.cc
--- a/chrome/browser/android/content/content_utils.cc
+++ b/chrome/browser/android/content/content_utils.cc
@@ -8,6 +8,20 @@
 #include "components/version_info/version_info.h"
 #include "content/public/browser/web_contents.h"
 
+#include "base/android/jni_android.h"
+#include "base/android/scoped_java_ref.h"
+#include "chrome/browser/browser_process.h"
+#include "components/prefs/pref_service.h"
+#include "chrome/common/pref_names.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF16ToJavaString;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+
 static base::android::ScopedJavaLocalRef<jstring>
 JNI_ContentUtils_GetBrowserUserAgent(JNIEnv* env) {
   return base::android::ConvertUTF8ToJavaString(
@@ -18,6 +32,20 @@ static void JNI_ContentUtils_SetUserAgentOverride(
     JNIEnv* env,
     const base::android::JavaParamRef<jobject>& jweb_contents,
     jboolean j_override_in_new_tabs) {
+  bool enabled =
+    g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled);
+
+  if (enabled == true) {
+    std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgentDesktopMode);
+    blink::UserAgentOverride spoofed_ua;
+    spoofed_ua.ua_string_override = ua;
+
+    content::WebContents* web_contents =
+        content::WebContents::FromJavaWebContents(jweb_contents);
+    web_contents->SetUserAgentOverride(spoofed_ua, false);
+    return;
+  }
+
   content::WebContents* web_contents =
       content::WebContents::FromJavaWebContents(jweb_contents);
   embedder_support::SetDesktopUserAgentOverride(
diff --git a/chrome/browser/android/preferences/browser_prefs_android.cc b/chrome/browser/android/preferences/browser_prefs_android.cc
--- a/chrome/browser/android/preferences/browser_prefs_android.cc
+++ b/chrome/browser/android/preferences/browser_prefs_android.cc
@@ -11,12 +11,19 @@
 #include "components/content_settings/core/common/pref_names.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_registry_simple.h"
+#include "chrome/common/pref_names.h"
 
 namespace android {
 
 void RegisterPrefs(PrefRegistrySimple* registry) {
   RegisterClipboardAndroidPrefs(registry);
   webauthn::authenticator::RegisterLocalState(registry);
+
+  registry->RegisterBooleanPref(prefs::kOverrideUserAgentEnabled, false);
+  registry->RegisterStringPref(prefs::kOverrideUserAgent, "");
+  registry->RegisterBooleanPref(prefs::kOverrideUserAgentDesktopModeEnabled, false);
+  registry->RegisterStringPref(prefs::kOverrideUserAgentDesktopMode, "");
+  registry->RegisterBooleanPref(prefs::kDesktopModeViewportMetaEnabled, false);
 }
 
 void RegisterUserProfilePrefs(user_prefs::PrefRegistrySyncable* registry) {
diff --git a/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc b/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
--- a/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
+++ b/chrome/browser/android/preferences/privacy_preferences_manager_impl.cc
@@ -14,6 +14,30 @@
 #include "components/policy/core/common/features.h"
 #include "components/prefs/pref_service.h"
 
+#include "base/command_line.h"
+#include "base/base_switches.h"
+#include "chrome/common/chrome_switches.h"
+#include "content/browser/renderer_host/render_process_host_impl.h"
+#include "content/common/renderer.mojom.h"
+#include "chrome/browser/chrome_content_browser_client.h"
+
+#include "components/embedder_support/content_settings_utils.h"
+#include "components/embedder_support/switches.h"
+#include "components/embedder_support/user_agent_utils.h"
+
+#include "base/android/jni_android.h"
+#include "base/android/jni_array.h"
+#include "base/android/jni_string.h"
+#include "base/android/scoped_java_ref.h"
+
+using base::android::ConvertJavaStringToUTF8;
+using base::android::ConvertUTF16ToJavaString;
+using base::android::ConvertUTF8ToJavaString;
+using base::android::JavaParamRef;
+using base::android::JavaRef;
+using base::android::ScopedJavaGlobalRef;
+using base::android::ScopedJavaLocalRef;
+
 static jboolean JNI_PrivacyPreferencesManagerImpl_IsMetricsReportingEnabled(
     JNIEnv* env) {
   PrefService* local_state = g_browser_process->local_state();
@@ -41,3 +65,100 @@ JNI_PrivacyPreferencesManagerImpl_IsMetricsReportingDisabledByPolicy(
              metrics::prefs::kMetricsReportingEnabled) &&
          !local_state->GetBoolean(metrics::prefs::kMetricsReportingEnabled);
 }
+
+static void UpdateOverrideUserAgent() {
+  bool overrideUserAgentEnabled =
+    g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentEnabled);
+  std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgent);
+  if (ua.empty()) {
+    ua = ChromeContentBrowserClient().GetUserAgent();
+  }
+
+  base::CommandLine* parsed_command_line =
+      base::CommandLine::ForCurrentProcess();
+  parsed_command_line->RemoveSwitch(embedder_support::kUserAgent);
+  if (!ua.empty()) {
+    if (overrideUserAgentEnabled) {
+      parsed_command_line->AppendSwitchASCII(embedder_support::kUserAgent, ua);
+    }
+
+    for (auto iter = content::RenderProcessHost::AllHostsIterator(); !iter.IsAtEnd();
+         iter.Advance()) {
+      if (iter.GetCurrentValue()->IsInitializedAndNotDead()) {
+        std::vector<std::string> cors_exempt_header_list;
+        iter.GetCurrentValue()->GetRendererInterface()->InitializeRenderer(
+          /*user_agent*/ ua, /*full_user_agent*/ ua, /*reduced_user_agent*/ ua,
+          /*metadata*/ blink::UserAgentMetadata(), cors_exempt_header_list);
+      }
+    }
+  }
+
+  parsed_command_line->RemoveSwitch(switches::kDesktopModeViewportMetaEnabled);
+  if (g_browser_process->local_state()->GetBoolean(prefs::kDesktopModeViewportMetaEnabled))
+    parsed_command_line->AppendSwitch(switches::kDesktopModeViewportMetaEnabled);
+}
+
+static void JNI_PrivacyPreferencesManagerImpl_UpdateOverrideUserAgent(
+    JNIEnv* env) {
+  UpdateOverrideUserAgent();
+}
+
+static jboolean JNI_PrivacyPreferencesManagerImpl_IsOverrideUserAgentEnabled(
+    JNIEnv* env, jboolean desktopMode) {
+  if (desktopMode == false)
+    return g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentEnabled);
+  else
+    return g_browser_process->local_state()->GetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled);
+}
+
+static void JNI_PrivacyPreferencesManagerImpl_SetOverrideUserAgentEnabled(
+    JNIEnv* env,
+    jboolean enabled, jboolean desktopMode) {
+  if (desktopMode == false) {
+    g_browser_process->local_state()->SetBoolean(prefs::kOverrideUserAgentEnabled,
+                                                enabled);
+    UpdateOverrideUserAgent();
+  } else {
+    g_browser_process->local_state()->SetBoolean(prefs::kOverrideUserAgentDesktopModeEnabled,
+                                                enabled);
+  }
+}
+
+static void JNI_PrivacyPreferencesManagerImpl_SetOverrideUserAgentValue(
+    JNIEnv* env,
+    const JavaParamRef<jstring>& ua, jboolean desktopMode) {
+  std::string new_ua = ConvertJavaStringToUTF8(env, ua);
+  if (desktopMode == false) {
+    g_browser_process->local_state()->SetString(prefs::kOverrideUserAgent,
+                                                new_ua);
+    UpdateOverrideUserAgent();
+  } else {
+    g_browser_process->local_state()->SetString(prefs::kOverrideUserAgentDesktopMode,
+                                                new_ua);
+  }
+}
+
+static base::android::ScopedJavaLocalRef<jstring>
+    JNI_PrivacyPreferencesManagerImpl_GetOverrideUserAgentValue(
+      JNIEnv* env, jboolean desktopMode) {
+  if (desktopMode == false) {
+    std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgent);
+    return ConvertUTF8ToJavaString(env, ua);
+  } else {
+    std::string ua = g_browser_process->local_state()->GetString(prefs::kOverrideUserAgentDesktopMode);
+    return ConvertUTF8ToJavaString(env, ua);
+  }
+}
+
+static jboolean JNI_PrivacyPreferencesManagerImpl_IsDesktopModeViewportMetaEnabled(
+    JNIEnv* env) {
+  return g_browser_process->local_state()->GetBoolean(prefs::kDesktopModeViewportMetaEnabled);
+}
+
+static void JNI_PrivacyPreferencesManagerImpl_SetDesktopModeViewportMetaEnabled(
+    JNIEnv* env,
+    jboolean enabled) {
+  g_browser_process->local_state()->SetBoolean(prefs::kDesktopModeViewportMetaEnabled,
+                                              enabled);
+  UpdateOverrideUserAgent();
+}
diff --git a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
--- a/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
+++ b/chrome/browser/preferences/android/java/src/org/chromium/chrome/browser/preferences/ChromePreferenceKeys.java
@@ -1034,6 +1034,9 @@ public final class ChromePreferenceKeys {
     public static final String BLUETOOTH_NOTIFICATION_IDS = "Chrome.Bluetooth.NotificationIds";
     public static final String USB_NOTIFICATION_IDS = "Chrome.USB.NotificationIds";
 
+    public static final String USERAGENT_STICKY_DESKTOP_MODE = "Chrome.UserAgent.StickyDesktopMode";
+    public static final String USERAGENT_ALWAYS_DESKTOP_MODE = "Chrome.UserAgent.AlwaysDesktopMode";
+
     /**
      * These values are currently used as SharedPreferences keys, along with the keys in
      * {@link LegacyChromePreferenceKeys#getKeysInUse()}. Add new SharedPreferences keys
@@ -1179,6 +1182,8 @@ public final class ChromePreferenceKeys {
                 SYNC_PROMO_TOTAL_SHOW_COUNT,
                 START_NEXT_SHOW_ON_STARTUP_DECISION_MS,
                 SEARCH_RESUMPTION_MODULE_COLLAPSE_ON_NTP,
+                USERAGENT_STICKY_DESKTOP_MODE,
+                USERAGENT_ALWAYS_DESKTOP_MODE,
                 START_SHOW_ON_STARTUP,
                 TAP_FEED_CARDS_COUNT,
                 TAP_MV_TILES_COUNT,
diff --git a/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java b/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
--- a/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
+++ b/chrome/browser/privacy/settings/java/src/org/chromium/chrome/browser/privacy/settings/PrivacyPreferencesManager.java
@@ -81,6 +81,14 @@ public interface PrivacyPreferencesManager extends CrashReportingPermissionManag
      */
     boolean isMetricsReportingEnabled();
 
+    void updateOverrideUserAgent();
+    boolean isOverrideUserAgentEnabled(boolean desktopMode);
+    void setOverrideUserAgentEnabled(boolean enabled, boolean desktopMode);
+    String getOverrideUserAgentValue(boolean desktopMode);
+    void setOverrideUserAgentValue(String user_agent, boolean desktopMode);
+    boolean isDesktopModeViewportMetaEnabled();
+    void setDesktopModeViewportMetaEnabled(boolean enabled);
+
     /**
      * Sets whether the usage and crash reporting pref should be enabled.
      */
diff --git a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
--- a/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
+++ b/chrome/browser/tab/java/src/org/chromium/chrome/browser/tab/Tab.java
@@ -280,6 +280,8 @@ public interface Tab extends TabLifecycle {
      */
     void setIsTabSaveEnabled(boolean isSaveEnabled);
 
+    void SetOverrideUserAgent(boolean usingDesktopUserAgent, boolean forcedByUser);
+
     /**
      * @return true if the {@link Tab} is a custom tab.
      */
diff --git a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
--- a/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
+++ b/chrome/browser/tabmodel/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManager.java
@@ -89,6 +89,8 @@ public interface TabWindowManager {
      */
     Tab getTabById(int tabId);
 
+    void SetOverrideUserAgentForAllTabs(boolean usingDesktopUserAgent);
+
     /**
      * Finds the {@link TabModelSelector} bound to an Activity instance of a given index.
      * @param index The index of {@link TabModelSelector} to get.
diff --git a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
--- a/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
+++ b/chrome/browser/tabmodel/internal/android/java/src/org/chromium/chrome/browser/tabmodel/TabWindowManagerImpl.java
@@ -133,6 +133,24 @@ public class TabWindowManagerImpl implements ActivityStateListener, TabWindowMan
         return null;
     }
 
+    @Override
+    public void SetOverrideUserAgentForAllTabs(boolean usingDesktopUserAgent) {
+        for (int selectorIndex = 0; selectorIndex < mSelectors.size(); selectorIndex++) {
+            TabModelSelector selector = mSelectors.get(selectorIndex);
+            if (selector != null) {
+                List<TabModel> models = selector.getModels();
+                for (int modelIndex = 0; modelIndex < models.size(); modelIndex++) {
+                    TabModel model = models.get(modelIndex);
+
+                    for (int tabIdex = 0; tabIdex < model.getCount(); tabIdex++) {
+                        Tab theTab = model.getTabAt(tabIdex);
+                        theTab.SetOverrideUserAgent(usingDesktopUserAgent, /*forcedByUser*/ true);
+                    }
+                }
+            }
+        }
+    }
+
     @Override
     public Tab getTabById(int tabId) {
         for (int i = 0; i < mSelectors.size(); i++) {
diff --git a/chrome/browser/ui/android/strings/android_chrome_strings.grd b/chrome/browser/ui/android/strings/android_chrome_strings.grd
--- a/chrome/browser/ui/android/strings/android_chrome_strings.grd
+++ b/chrome/browser/ui/android/strings/android_chrome_strings.grd
@@ -229,6 +229,41 @@ CHAR_LIMIT guidelines:
         Visit help page
       </message>
 
+      <!-- User Agent settings -->
+      <message name="IDS_PREFS_USERAGENT_SETTINGS" desc="Title of the User Agent preference. [CHAR-LIMIT=32]">
+        User Agent
+      </message>
+      <message name="IDS_USERAGENT_SETTINGS_TITLE" desc="Title of the User Agent screen. [CHAR-LIMIT=32]">
+        Customize User Agent
+      </message>
+      <message name="IDS_OPTION_DESKTOP_FLAG" desc="The label of the option that allows users to sticky desktop mode view flag under hambuger menu.">
+        Current behaviour for desktop mode toggle in hamburger menu
+      </message>
+      <message name="IDS_OPTION_DESKTOP_FLAG_ON" desc="The label of the option that allows users to sticky desktop mode view flag under hambuger menu. [CHAR-LIMIT=32]">
+        Applies to all tabs (sticky mode)
+      </message>
+      <message name="IDS_OPTION_DESKTOP_FLAG_OFF" desc="The label of the option that revert the hambuger menu flag to actual behaviour. [CHAR-LIMIT=32]">
+        Applies to current tab only
+      </message>
+      <message name="IDS_CUSTOM_UA_FLAG_ON" desc="The label of the option that allows users to define custom user agent.">
+        Use custom user agent
+      </message>
+      <message name="IDS_CUSTOM_UA_FLAG_OFF" desc="The label of the option that revert the user agent to actual value.">
+        Use standard user agent
+      </message>
+      <message name="IDS_CUSTOM_UA_PLACEHOLDER" desc="The label of the placeholder for user agent textbox.">
+        Insert a valid user agent
+      </message>
+      <message name="IDS_CUSTOM_UA_TEXT" desc="The label of the placeholder for user agent textbox.">
+        Mobile User Agent
+      </message>
+      <message name="IDS_CUSTOM_DESKTOP_UA_TEXT" desc="The label of the placeholder for user agent textbox.">
+        Desktop Mode User Agent
+      </message>
+      <message name="IDS_DESKTOP_MODE_VIEWPORTMETA_CHECKBOX" desc="The label of the enable viewport meta checkbox for user desktop mode.">
+        Enable processing of the viewport meta tag also for desktop mode
+      </message>
+
       <!-- Notification channels -->
       <message name="IDS_NOTIFICATION_CATEGORY_GROUP_GENERAL" desc='Subheading for "General" section of a list of notification categories. [CHAR_LIMIT=32]'>
         General
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -445,6 +445,19 @@ const char kAllowJavascriptAppleEvents[] =
 
 #endif
 
+#if BUILDFLAG(IS_ANDROID)
+const char kOverrideUserAgentEnabled[] =
+    "override_user_agent_enabled";
+const char kOverrideUserAgent[] =
+    "override_user_agent";
+const char kOverrideUserAgentDesktopModeEnabled[] =
+    "override_user_agent_dm_enabled";
+const char kOverrideUserAgentDesktopMode[] =
+    "override_user_agent_dm";
+const char kDesktopModeViewportMetaEnabled[] =
+    "dm-viewport-meta-enabled";
+#endif
+
 // Boolean which specifies whether we should ask the user if we should download
 // a file (true) or just download it automatically.
 const char kPromptForDownload[] = "download.prompt_for_download";
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -1250,6 +1250,14 @@ extern const char kDesktopSharingHubEnabled[];
 #if !BUILDFLAG(IS_ANDROID)
 extern const char kLastWhatsNewVersion[];
 #endif
+#if BUILDFLAG(IS_ANDROID)
+extern const char kOverrideUserAgentEnabled[];
+extern const char kOverrideUserAgent[];
+extern const char kOverrideUserAgentDesktopModeEnabled[];
+extern const char kOverrideUserAgentDesktopMode[];
+extern const char kDesktopModeViewportMetaEnabled[];
+#endif
+
 
 #if !BUILDFLAG(IS_ANDROID)
 extern const char kLensRegionSearchEnabled[];
diff --git a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
--- a/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
+++ b/components/browser_ui/widget/android/java/src/org/chromium/components/browser_ui/widget/RadioButtonWithEditText.java
@@ -167,6 +167,17 @@ public class RadioButtonWithEditText extends RadioButtonWithDescription {
             mEditText.setCursorVisible(false);
             KeyboardVisibilityDelegate.getInstance().hideKeyboard(mEditText);
         }
+        if (mRadioButtonWithEditTextFocusListener != null) {
+            mRadioButtonWithEditTextFocusListener.onRadioButtonWithEditTextFocusChanged(hasFocus);
+        }
+    }
+
+    public interface RadioButtonWithEditTextFocusListener {
+        void onRadioButtonWithEditTextFocusChanged(boolean hasFocus);
+    }
+    private RadioButtonWithEditTextFocusListener mRadioButtonWithEditTextFocusListener;
+    public void setFocusChangeListener(RadioButtonWithEditTextFocusListener listener) {
+        mRadioButtonWithEditTextFocusListener = listener;
     }
 
     /**
diff --git a/components/embedder_support/user_agent_utils.cc b/components/embedder_support/user_agent_utils.cc
--- a/components/embedder_support/user_agent_utils.cc
+++ b/components/embedder_support/user_agent_utils.cc
@@ -430,6 +430,13 @@ std::string GetUserAgent(
 
 std::string GetReducedUserAgent(
     ForceMajorVersionToMinorPosition force_major_to_minor) {
+  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+  if (command_line->HasSwitch(kUserAgent)) {
+    std::string ua = command_line->GetSwitchValueASCII(kUserAgent);
+    if (net::HttpUtil::IsValidHeaderValue(ua))
+      return ua;
+    LOG(WARNING) << "Ignored invalid value for flag --" << kUserAgent;
+  }
   return content::GetReducedUserAgent(
       base::CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kUseMobileUserAgent),
@@ -602,6 +609,10 @@ blink::UserAgentMetadata GetUserAgentMetadata(const PrefService* pref_service) {
           policy::policy_prefs::kUserAgentClientHintsGREASEUpdateEnabled);
     ua_options.force_major_to_minor = GetMajorToMinorFromPrefs(pref_service);
   }
+  if (base::CommandLine::ForCurrentProcess()->HasSwitch(kUserAgent)) {
+    //NOTE: metadata is not updated with custom UA information
+    return metadata;
+  }
   metadata.brand_version_list = GetBrandMajorVersionList(
       enable_updated_grease_by_policy, ua_options.force_major_to_minor);
   metadata.brand_full_version_list = GetBrandFullVersionList(
@@ -639,7 +650,9 @@ void SetDesktopUserAgentOverride(content::WebContents* web_contents,
 
   blink::UserAgentOverride spoofed_ua;
   spoofed_ua.ua_string_override = content::BuildUserAgentFromOSAndProduct(
-      kLinuxInfoStr, GetProductAndVersion());
+      kLinuxInfoStr,
+      GetProductAndVersion(ForceMajorVersionToMinorPosition::kDefault,
+                           UserAgentReductionEnterprisePolicyState::kForceEnabled));
   spoofed_ua.ua_metadata_override = metadata;
   spoofed_ua.ua_metadata_override->platform = "Linux";
   spoofed_ua.ua_metadata_override->platform_version =
diff --git a/content/browser/renderer_host/navigation_controller_android.cc b/content/browser/renderer_host/navigation_controller_android.cc
--- a/content/browser/renderer_host/navigation_controller_android.cc
+++ b/content/browser/renderer_host/navigation_controller_android.cc
@@ -255,6 +255,7 @@ NavigationControllerAndroid::LoadUrl(
     jboolean can_load_local_resources,
     jboolean is_renderer_initiated,
     jboolean should_replace_current_entry,
+    jint user_agent_override_option,
     const JavaParamRef<jobject>& j_initiator_origin,
     jboolean has_user_gesture,
     jboolean should_clear_history_list,
@@ -326,6 +327,9 @@ NavigationControllerAndroid::LoadUrl(
 
   params.navigation_ui_data = std::move(navigation_ui_data);
 
+  params.override_user_agent = static_cast<NavigationController::UserAgentOverrideOption>(
+    user_agent_override_option);
+
   base::WeakPtr<NavigationHandle> handle =
       navigation_controller_->LoadURLWithParams(params);
 
diff --git a/content/browser/renderer_host/navigation_controller_android.h b/content/browser/renderer_host/navigation_controller_android.h
--- a/content/browser/renderer_host/navigation_controller_android.h
+++ b/content/browser/renderer_host/navigation_controller_android.h
@@ -87,6 +87,7 @@ class CONTENT_EXPORT NavigationControllerAndroid {
       jboolean can_load_local_resources,
       jboolean is_renderer_initiated,
       jboolean should_replace_current_entry,
+      jint user_agent_override_option,
       const base::android::JavaParamRef<jobject>& j_initiator_origin,
       jboolean has_user_gesture,
       jboolean should_clear_history_list,
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
--- a/content/browser/renderer_host/render_process_host_impl.cc
+++ b/content/browser/renderer_host/render_process_host_impl.cc
@@ -3436,6 +3436,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
     switches::kLacrosUseChromeosProtectedMedia,
     switches::kLacrosUseChromeosProtectedAv1,
 #endif
+    switches::kDesktopModeViewportMetaEnabled,
   };
   renderer_cmd->CopySwitchesFrom(browser_cmd, kSwitchNames,
                                  std::size(kSwitchNames));
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
--- a/content/browser/web_contents/web_contents_impl.cc
+++ b/content/browser/web_contents/web_contents_impl.cc
@@ -14,6 +14,7 @@
 
 #include "base/allocator/partition_alloc_features.h"
 #include "base/allocator/partition_allocator/starscan/pcscan.h"
+#include "base/base_switches.h"
 #include "base/bind.h"
 #include "base/check_op.h"
 #include "base/command_line.h"
@@ -2799,6 +2800,9 @@ const blink::web_pref::WebPreferences WebContentsImpl::ComputeWebPreferences() {
         !renderer_preferences_.user_agent_override.ua_metadata_override->mobile)
 #endif
       prefs.viewport_meta_enabled = false;
+    if (!command_line.HasSwitch(switches::kDesktopModeViewportMetaEnabled)) {
+      prefs.viewport_meta_enabled = false;
+    }
   }
 
   prefs.spatial_navigation_enabled =
diff --git a/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java b/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
--- a/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/framehost/NavigationControllerImpl.java
@@ -183,6 +183,7 @@ import org.chromium.url.Origin;
                     params.getBaseUrl(), params.getVirtualUrlForDataUrl(),
                     params.getDataUrlAsString(), params.getCanLoadLocalResources(),
                     params.getIsRendererInitiated(), params.getShouldReplaceCurrentEntry(),
+                    params.getUserAgentOverrideOption(),
                     params.getInitiatorOrigin(), params.getHasUserGesture(),
                     params.getShouldClearHistoryList(), inputStart,
                     params.getNavigationUIDataSupplier() == null
@@ -375,7 +376,7 @@ import org.chromium.url.Origin;
                 String referrerUrl, int referrerPolicy, int uaOverrideOption, String extraHeaders,
                 ResourceRequestBody postData, String baseUrlForDataUrl, String virtualUrlForDataUrl,
                 String dataUrlAsString, boolean canLoadLocalResources, boolean isRendererInitiated,
-                boolean shouldReplaceCurrentEntry, Origin initiatorOrigin, boolean hasUserGesture,
+                boolean shouldReplaceCurrentEntry, int userAgentOverrideOption, Origin initiatorOrigin, boolean hasUserGesture,
                 boolean shouldClearHistoryList, long inputStart, long navigationUIDataPtr);
         void clearHistory(long nativeNavigationControllerAndroid, NavigationControllerImpl caller);
         int getNavigationHistory(long nativeNavigationControllerAndroid,
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
--- a/content/renderer/render_thread_impl.cc
+++ b/content/renderer/render_thread_impl.cc
@@ -908,7 +908,6 @@ void RenderThreadImpl::InitializeRenderer(
     const std::string& reduced_user_agent,
     const blink::UserAgentMetadata& user_agent_metadata,
     const std::vector<std::string>& cors_exempt_header_list) {
-  DCHECK(user_agent_.IsNull());
   DCHECK(reduced_user_agent_.IsNull());
   DCHECK(full_user_agent_.IsNull());
 
--
2.25.1
