From: uazo <uazo@users.noreply.github.com>
Date: Wed, 30 Sep 2020 07:40:01 +0000
Subject: Timezone customization

Allow specifying a custom timezone, or using a random one.

See also: https://github.com/bromite/bromite/wiki/TimezoneOverride

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
---
 .../ChromeSiteSettingsDelegate.java           |  16 ++
 .../browser_ui/site_settings/android/BUILD.gn |   3 +
 .../res/layout/time_zone_select_dialog.xml    |  36 ++++
 ...ezoneoverride_site_settings_preference.xml |  68 ++++++
 .../res/xml/site_settings_preferences.xml     |   4 +
 .../java/res/xml/website_preferences.xml      |   9 +
 .../ContentSettingsResources.java             |  33 +++
 .../site_settings/SingleCategorySettings.java |  72 ++++++-
 .../site_settings/SingleWebsiteSettings.java  |   8 +-
 .../site_settings/SiteSettings.java           |   2 +-
 .../site_settings/SiteSettingsCategory.java   |   9 +-
 .../site_settings/SiteSettingsDelegate.java   |   2 +
 ...imezoneOverrideSiteSettingsPreference.java | 193 ++++++++++++++++++
 .../browser_ui/site_settings/Website.java     |  11 +
 .../WebsitePermissionsFetcher.java            |   2 +
 .../WebsitePreferenceBridge.java              |  12 ++
 .../android/website_preference_bridge.cc      |  16 ++
 .../strings/android/site_settings.grdp        |  35 ++++
 .../browser/content_settings_pref_provider.cc |  16 ++
 .../browser/content_settings_pref_provider.h  |   4 +
 .../core/browser/content_settings_registry.cc |  12 ++
 .../core/browser/content_settings_utils.cc    |   7 +
 .../core/browser/host_content_settings_map.cc |   8 +
 .../core/browser/host_content_settings_map.h  |   3 +
 .../core/common/content_settings.cc           |   4 +-
 .../core/common/content_settings.h            |   2 +
 .../core/common/content_settings.mojom        |   2 +
 .../common/content_settings_mojom_traits.cc   |   4 +-
 .../common/content_settings_mojom_traits.h    |  10 +
 .../core/common/content_settings_types.h      |   3 +
 .../core/common/pref_names.cc                 |   3 +
 .../content_settings/core/common/pref_names.h |   2 +
 .../renderer/content_settings_agent_impl.cc   |  83 ++++++++
 .../renderer/content_settings_agent_impl.h    |  11 +
 .../WebLayerSiteSettingsDelegate.java         |   3 +
 35 files changed, 696 insertions(+), 12 deletions(-)
 create mode 100755 components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
 create mode 100755 components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
 create mode 100755 components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
--- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsDelegate.java
@@ -42,6 +42,10 @@ import org.chromium.url.GURL;
 
 import java.util.Set;
 
+import android.content.Intent;
+import android.provider.Browser;
+import android.net.Uri;
+
 /**
  * A SiteSettingsDelegate instance that contains Chrome-specific Site Settings logic.
  */
@@ -242,6 +246,18 @@ public class ChromeSiteSettingsDelegate implements SiteSettingsDelegate {
         return PrivacySandboxBridge.getFirstPartySetOwner(memberOrigin);
     }
 
+    // open wiki page for documentation about the timezone override feature
+    @Override
+    public void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity) {
+        Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/bromite/bromite/wiki/TimezoneOverride"));
+        // Let Chromium know that this intent is from Chromium, so that it does not close the app when
+        // the user presses 'back' button.
+        intent.putExtra(Browser.EXTRA_APPLICATION_ID, currentActivity.getPackageName());
+        intent.putExtra(Browser.EXTRA_CREATE_NEW_TAB, true);
+        intent.setPackage(currentActivity.getPackageName());
+        currentActivity.startActivity(intent);
+    }
+
     @Override
     public boolean canLaunchClearBrowsingDataDialog() {
         return true;
diff --git a/components/browser_ui/site_settings/android/BUILD.gn b/components/browser_ui/site_settings/android/BUILD.gn
--- a/components/browser_ui/site_settings/android/BUILD.gn
+++ b/components/browser_ui/site_settings/android/BUILD.gn
@@ -86,6 +86,7 @@ android_library("java") {
     "java/src/org/chromium/components/browser_ui/site_settings/WebsitePreference.java",
     "java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java",
     "java/src/org/chromium/components/browser_ui/site_settings/WebsiteRowPreference.java",
+    "java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java"
   ]
   annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
   resources_package = "org.chromium.components.browser_ui.site_settings"
@@ -214,6 +215,8 @@ android_resources("java_resources") {
     "java/res/xml/single_website_preferences.xml",
     "java/res/xml/site_settings_preferences.xml",
     "java/res/xml/website_preferences.xml",
+    "java/res/layout/timezoneoverride_site_settings_preference.xml",
+    "java/res/layout/time_zone_select_dialog.xml",
   ]
 
   deps = [
diff --git a/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml b/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
new file mode 100755
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/res/layout/time_zone_select_dialog.xml
@@ -0,0 +1,36 @@
+<?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 TimezoneOverrideSettingsPreference. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    style="@style/AlertDialogContent">
+
+  <ListView
+      android:id="@+id/listView"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:layout_marginBottom="8dp"
+      android:layout_marginEnd="8dp"
+      android:layout_marginStart="8dp"
+      android:layout_marginTop="8dp"
+      android:choiceMode="singleChoice"
+      android:listSelector="#666666"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml b/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
new file mode 100755
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/res/layout/timezoneoverride_site_settings_preference.xml
@@ -0,0 +1,68 @@
+<!--
+    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 TimezoneOverrideSettingsPreference. -->
+
+<LinearLayout
+    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"
+    android:focusable="false"
+    android:orientation="vertical">
+
+  <org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout
+      android:id="@+id/radio_button_layout"
+      android:layout_width="match_parent"
+      android:layout_height="match_parent">
+
+    <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+        android:id="@+id/allowed"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        app:primaryText="@string/website_settings_category_timezone_override_allowed" />
+
+    <org.chromium.components.browser_ui.widget.RadioButtonWithDescription
+        android:id="@+id/blocked"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        app:primaryText="@string/website_settings_category_timezone_override_random" />
+
+    <org.chromium.components.browser_ui.widget.RadioButtonWithEditText
+        android:id="@+id/ask"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+        android:inputType="text"
+        android:hint="@string/website_settings_category_timezone_override_custom_hint"
+        app:descriptionText="@string/website_settings_category_timezone_override_custom" />
+
+  </org.chromium.components.browser_ui.widget.RadioButtonWithDescriptionLayout>
+
+  <org.chromium.ui.widget.ButtonCompat
+    android:id="@+id/select_button"
+    style="@style/TextButton"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingBottom="16dp"
+    android:paddingTop="16dp"
+    android:text="@string/website_settings_select_button"
+    app:verticalInset="0dp" />
+
+</LinearLayout>
diff --git a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
--- a/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
+++ b/components/browser_ui/site_settings/android/java/res/xml/site_settings_preferences.xml
@@ -43,6 +43,10 @@ found in the LICENSE file.
     <org.chromium.components.browser_ui.settings.ChromeBasePreference
         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
         android:key="javascript" />
+    <!-- Timezone Override -->
+    <org.chromium.components.browser_ui.settings.ChromeBasePreference
+        android:key="timezone_override"
+        android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings" />
     <!-- Popups -->
     <org.chromium.components.browser_ui.settings.ChromeBasePreference
         android:fragment="org.chromium.components.browser_ui.site_settings.SingleCategorySettings"
diff --git a/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
--- a/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
+++ b/components/browser_ui/site_settings/android/java/res/xml/website_preferences.xml
@@ -15,6 +15,12 @@ found in the LICENSE file.
         android:key="cookie_info_text"
         android:summary="@string/website_settings_cookie_info"/>
 
+  <!-- A text message describing timeoverride settings. -->
+  <org.chromium.components.browser_ui.settings.TextMessagePreference
+      android:key="timeoverride_info_text"
+      android:summary="@string/website_settings_timeoverride_info"/>
+
+
     <!-- A common binary toggle, only shown for specific categories that allow
          turning default values for that category on/off.-->
     <org.chromium.components.browser_ui.settings.ChromeSwitchPreference
@@ -28,6 +34,9 @@ found in the LICENSE file.
     <!-- A four state toggle for cookie preferences, only shown for the Cookies category. -->
     <org.chromium.components.browser_ui.site_settings.FourStateCookieSettingsPreference
         android:key="four_state_cookie_toggle" />
+  <!-- A specific state toggle for time override preferences, only shown for the TimeOverride category. -->
+  <org.chromium.components.browser_ui.site_settings.TimezoneOverrideSiteSettingsPreference
+      android:key="timeoverride_state_toggle" />
 
     <!-- A checkbox for enabling a quiet ui for notification prompts. Only shown in the Notifications category. -->
     <org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/ContentSettingsResources.java
@@ -269,6 +269,12 @@ public class ContentSettingsResources {
                         R.string.vr_permission_title, ContentSettingValues.ASK,
                         ContentSettingValues.BLOCK, R.string.website_settings_category_vr_ask,
                         R.string.website_settings_category_vr_blocked);
+
+            case ContentSettingsType.TIMEZONE_OVERRIDE:
+                return new ResourceItem(R.drawable.web_asset, R.string.timezone_override_permission_title,
+                            ContentSettingValues.ALLOW, ContentSettingValues.BLOCK,
+                            R.string.website_settings_category_timezone_override_custom,
+                            R.string.website_settings_category_timezone_override_random);
         }
         assert false; // NOTREACHED
         return null;
@@ -421,6 +427,23 @@ public class ContentSettingsResources {
         }
     }
 
+    public static int getCategorySummary(int contentType, @Nullable @ContentSettingValues int value) {
+        if (contentType == ContentSettingsType.TIMEZONE_OVERRIDE) {
+            switch (value) {
+                case ContentSettingValues.ALLOW:
+                    return R.string.website_settings_category_timezone_override_allowed;
+                case ContentSettingValues.ASK:
+                    return R.string.website_settings_category_timezone_override_custom;
+                case ContentSettingValues.BLOCK:
+                    return R.string.website_settings_category_timezone_override_random;
+                default:
+                    return 0;
+            }
+        }
+        else
+            return getCategorySummary(value);
+    }
+
     /**
      * Returns the string resource id for a content type to show with a permission category.
      * @param enabled Whether the content type is enabled.
@@ -438,6 +461,9 @@ public class ContentSettingsResources {
      */
     public static int getSiteSummary(@ContentSettingValues @Nullable Integer value,
             @ContentSettingsType int contentSettingsType) {
+        if (contentSettingsType == ContentSettingsType.TIMEZONE_OVERRIDE)
+            return getCategorySummary(contentSettingsType, value);
+
         switch (value) {
             case ContentSettingValues.ALLOW:
                 return contentSettingsType == ContentSettingsType.REQUEST_DESKTOP_SITE
@@ -547,6 +573,13 @@ public class ContentSettingsResources {
                 return descriptionIDs;
             }
         }
+        else if (contentType == ContentSettingsType.TIMEZONE_OVERRIDE) {
+          int[] descriptionIDs = {
+                  R.string.website_settings_category_timezone_override_allowed, // ALLOWED
+                  R.string.website_settings_category_timezone_override_custom,  // ASK
+                  R.string.website_settings_category_timezone_override_random}; // BLOCKED
+          return descriptionIDs;
+        }
 
         assert false;
         return null;
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleCategorySettings.java
@@ -68,6 +68,10 @@ import org.chromium.ui.widget.Toast;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import android.app.Activity;
+import android.content.Intent;
+import org.chromium.components.browser_ui.site_settings.TimezoneOverrideSiteSettingsPreference;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -167,6 +171,7 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
     public static final String BINARY_TOGGLE_KEY = "binary_toggle";
     public static final String TRI_STATE_TOGGLE_KEY = "tri_state_toggle";
     public static final String FOUR_STATE_COOKIE_TOGGLE_KEY = "four_state_cookie_toggle";
+    public static final String TIMEOVERRIDE_STATE_TOGGLE_KEY = "timeoverride_state_toggle";
 
     // Keys for category-specific preferences (toggle, link, button etc.), dynamically shown.
     public static final String NOTIFICATIONS_VIBRATE_TOGGLE_KEY = "notifications_vibrate";
@@ -176,6 +181,7 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
     public static final String EXPLAIN_PROTECTED_MEDIA_KEY = "protected_content_learn_more";
     public static final String ADD_EXCEPTION_KEY = "add_exception";
     public static final String COOKIE_INFO_TEXT_KEY = "cookie_info_text";
+    public static final String TIMEOVERRIDE_INFO_TEXT = "timeoverride_info_text";
 
     // Keys for Allowed/Blocked preference groups/headers.
     private static final String ALLOWED_GROUP = "allowed_group";
@@ -242,7 +248,11 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
         Integer contentSetting = website.site().getContentSetting(
                 browserContextHandle, mCategory.getContentSettingsType());
         if (contentSetting != null) {
-            return ContentSettingValues.BLOCK == contentSetting;
+            if (mCategory.getContentSettingsType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+                return ContentSettingValues.ALLOW != contentSetting;
+            } else {
+                return ContentSettingValues.BLOCK == contentSetting;
+            }
         }
         return false;
     }
@@ -415,7 +425,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
             if (queryHasChanged) getInfoForOrigins();
         });
 
-        if (getSiteSettingsDelegate().isHelpAndFeedbackEnabled()) {
+        if (getSiteSettingsDelegate().isHelpAndFeedbackEnabled() ||
+                mCategory.getType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
             MenuItem help = menu.add(
                     Menu.NONE, R.id.menu_id_site_settings_help, Menu.NONE, R.string.menu_help);
             help.setIcon(VectorDrawableCompat.create(
@@ -426,7 +437,10 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         if (item.getItemId() == R.id.menu_id_site_settings_help) {
-            if (mCategory.getType() == SiteSettingsCategory.Type.PROTECTED_MEDIA) {
+            if (mCategory.getType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+                getSiteSettingsDelegate()
+                        .launchTimeZoneOverrideHelpAndFeedbackActivity(getActivity());
+            } else if (mCategory.getType() == SiteSettingsCategory.Type.PROTECTED_MEDIA) {
                 getSiteSettingsDelegate().launchProtectedContentHelpAndFeedbackActivity(
                         getActivity());
             } else {
@@ -525,6 +539,12 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
             WebsitePreferenceBridge.setDefaultContentSetting(
                     browserContextHandle, mCategory.getContentSettingsType(), setting);
             getInfoForOrigins();
+        } else if (TIMEOVERRIDE_STATE_TOGGLE_KEY.equals(preference.getKey())) {
+            @ContentSettingValues
+            int setting = (int) newValue;
+            WebsitePreferenceBridge.setDefaultContentSetting(
+                    browserContextHandle, ContentSettingsType.TIMEZONE_OVERRIDE, setting);
+            getInfoForOrigins();
         } else if (FOUR_STATE_COOKIE_TOGGLE_KEY.equals(preference.getKey())) {
             setCookieSettingsPreference((CookieSettingsState) newValue);
             getInfoForOrigins();
@@ -640,6 +660,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
                                browserContextHandle, ContentSettingsType.REQUEST_DESKTOP_SITE)
                     ? R.string.website_settings_blocked_group_heading_request_desktop_site
                     : R.string.website_settings_allowed_group_heading_request_desktop_site;
+        } else if (mCategory.getType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+            resource = R.string.website_settings_category_timezone_override_allowed;
         }
         assert resource > 0;
         return getString(resource);
@@ -680,6 +702,13 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
         if (mCategory.getType() == SiteSettingsCategory.Type.COOKIES && mRequiresFourStateSetting) {
             setting = cookieSettingsExceptionShouldBlock() ? ContentSettingValues.BLOCK
                                                            : ContentSettingValues.ALLOW;
+        } else if (mRequiresTriStateSetting) {
+            setting = WebsitePreferenceBridge.getDefaultContentSetting(browserContextHandle, mCategory.getContentSettingsType());
+            if (setting == ContentSettingValues.ALLOW) {
+              setting = ContentSettingValues.BLOCK;
+            } else {
+              setting = ContentSettingValues.ALLOW;
+            }
         } else {
             setting = (WebsitePreferenceBridge.isCategoryEnabled(
                               browserContextHandle, mCategory.getContentSettingsType()))
@@ -744,6 +773,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
                 && ContentFeatureList.isEnabled(
                         ContentFeatureList.REQUEST_DESKTOP_SITE_EXCEPTIONS)) {
             allowSpecifyingExceptions = true;
+        } else if (mCategory.getType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+            allowSpecifyingExceptions = true;
         }
         if (allowSpecifyingExceptions) {
             getPreferenceScreen().addPreference(new AddExceptionPreference(getStyledContext(),
@@ -915,7 +946,14 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
             TriStateSiteSettingsPreference triStateToggle =
                     (TriStateSiteSettingsPreference) getPreferenceScreen().findPreference(
                             TRI_STATE_TOGGLE_KEY);
-            return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK);
+            if (triStateToggle != null)
+              return (triStateToggle.getCheckedSetting() == ContentSettingValues.BLOCK);
+
+            TimezoneOverrideSiteSettingsPreference timeOverrideStatePreference =
+                    (TimezoneOverrideSiteSettingsPreference) getPreferenceScreen().findPreference(
+                            TIMEOVERRIDE_STATE_TOGGLE_KEY);
+            if (timeOverrideStatePreference != null)
+              return (timeOverrideStatePreference.getCheckedSetting() != ContentSettingValues.ALLOW);
         } else if (mRequiresFourStateSetting) {
             FourStateCookieSettingsPreference fourStateCookieToggle =
                     (FourStateCookieSettingsPreference) getPreferenceScreen().findPreference(
@@ -944,6 +982,9 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
                 (FourStateCookieSettingsPreference) screen.findPreference(
                         FOUR_STATE_COOKIE_TOGGLE_KEY);
         // TODO(crbug.com/1104836): Remove the old third-party cookie blocking UI
+        TimezoneOverrideSiteSettingsPreference timeOverrideStatePreference =
+                (TimezoneOverrideSiteSettingsPreference) screen.findPreference(
+                        TIMEOVERRIDE_STATE_TOGGLE_KEY);
         Preference notificationsVibrate = screen.findPreference(NOTIFICATIONS_VIBRATE_TOGGLE_KEY);
         mNotificationsQuietUiPref = screen.findPreference(NOTIFICATIONS_QUIET_UI_TOGGLE_KEY);
         mDesktopSitePeripheralPref = screen.findPreference(DESKTOP_SITE_PERIPHERAL_TOGGLE_KEY);
@@ -957,20 +998,32 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
         if (mRequiresTriStateSetting) {
             screen.removePreference(binaryToggle);
             screen.removePreference(fourStateCookieToggle);
-            configureTriStateToggle(triStateToggle, contentType);
+            if (mCategory.getType() == SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+              screen.removePreference(triStateToggle);
+              configureTimeOverrideStateToggle(timeOverrideStatePreference);
+            }
+            else {
+              screen.removePreference(timeOverrideStatePreference);
+              configureTriStateToggle(triStateToggle, contentType);
+            }
         } else if (mRequiresFourStateSetting) {
             screen.removePreference(binaryToggle);
             screen.removePreference(triStateToggle);
+            screen.removePreference(timeOverrideStatePreference);
             configureFourStateCookieToggle(fourStateCookieToggle);
         } else {
             screen.removePreference(triStateToggle);
             screen.removePreference(fourStateCookieToggle);
+            screen.removePreference(timeOverrideStatePreference);
             configureBinaryToggle(binaryToggle, contentType);
         }
 
         if (mCategory.getType() != SiteSettingsCategory.Type.COOKIES) {
             screen.removePreference(screen.findPreference(COOKIE_INFO_TEXT_KEY));
         }
+        if (mCategory.getType() != SiteSettingsCategory.Type.TIMEZONE_OVERRIDE) {
+            screen.removePreference(screen.findPreference(TIMEOVERRIDE_INFO_TEXT));
+        }
 
         if (permissionBlockedByOs) {
             maybeShowOsWarning(screen);
@@ -1105,6 +1158,15 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
         triStateToggle.initialize(setting, descriptionIds);
     }
 
+    private void configureTimeOverrideStateToggle(
+            TimezoneOverrideSiteSettingsPreference timeOverrideStateToggle) {
+        timeOverrideStateToggle.setOnPreferenceChangeListener(this);
+        @ContentSettingValues
+        int setting = WebsitePreferenceBridge.getDefaultContentSetting(
+                getSiteSettingsDelegate().getBrowserContextHandle(), ContentSettingsType.TIMEZONE_OVERRIDE);
+        timeOverrideStateToggle.initialize(setting, getSiteSettingsDelegate().getBrowserContextHandle());
+    }
+
     private void configureBinaryToggle(ChromeSwitchPreference binaryToggle, int contentType) {
         binaryToggle.setOnPreferenceChangeListener(this);
         binaryToggle.setTitle(ContentSettingsResources.getTitle(contentType));
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SingleWebsiteSettings.java
@@ -138,6 +138,8 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
                 return "popup_permission_list";
             case ContentSettingsType.SOUND:
                 return "sound_permission_list";
+            case ContentSettingsType.TIMEZONE_OVERRIDE:
+                return "timezone_override_permission_list";
             case ContentSettingsType.AR:
                 return "ar_permission_list";
             case ContentSettingsType.MEDIASTREAM_CAMERA:
@@ -934,11 +936,13 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
         if (value == null) return;
         setUpPreferenceCommon(preference, value);
 
+        int content_type = getContentSettingsTypeFromPreferenceKey(preference.getKey());
+
         ChromeSwitchPreference switchPreference = (ChromeSwitchPreference) preference;
         switchPreference.setChecked(value == ContentSettingValues.ALLOW);
         switchPreference.setSummary(isEmbargoed
                         ? getString(R.string.automatically_blocked)
-                        : getString(ContentSettingsResources.getCategorySummary(value)));
+                        : getString(ContentSettingsResources.getCategorySummary(content_type, value)));
         switchPreference.setOnPreferenceChangeListener(this);
         @ContentSettingsType
         int contentType = getContentSettingsTypeFromPreferenceKey(preference.getKey());
@@ -1137,7 +1141,7 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment
         }
 
         mSite.setContentSetting(browserContextHandle, type, permission);
-        preference.setSummary(getString(ContentSettingsResources.getCategorySummary(permission)));
+        preference.setSummary(getString(ContentSettingsResources.getCategorySummary(type, permission)));
         preference.setIcon(getContentSettingsIcon(type, permission));
 
         if (mWebsiteSettingsObserver != null) {
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettings.java
@@ -117,7 +117,7 @@ public class SiteSettings
             } else if (Type.AUTO_DARK_WEB_CONTENT == prefCategory) {
                 p.setSummary(ContentSettingsResources.getAutoDarkWebContentListSummary(checked));
             } else if (requiresTriStateSetting) {
-                p.setSummary(ContentSettingsResources.getCategorySummary(setting));
+                p.setSummary(ContentSettingsResources.getCategorySummary(contentType, setting));
             } else {
                 p.setSummary(ContentSettingsResources.getCategorySummary(contentType, checked));
             }
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsCategory.java
@@ -43,7 +43,7 @@ public class SiteSettingsCategory {
             Type.JAVASCRIPT, Type.MICROPHONE, Type.NFC, Type.NOTIFICATIONS, Type.POPUPS,
             Type.PROTECTED_MEDIA, Type.SENSORS, Type.SOUND, Type.USB, Type.VIRTUAL_REALITY,
             Type.USE_STORAGE, Type.AUTO_DARK_WEB_CONTENT, Type.REQUEST_DESKTOP_SITE,
-            Type.FEDERATED_IDENTITY_API})
+            Type.FEDERATED_IDENTITY_API, Type.TIMEZONE_OVERRIDE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {
         // All updates here must also be reflected in {@link #preferenceKey(int)
@@ -74,10 +74,11 @@ public class SiteSettingsCategory {
         int AUTO_DARK_WEB_CONTENT = 23;
         int REQUEST_DESKTOP_SITE = 24;
         int FEDERATED_IDENTITY_API = 25;
+        int TIMEZONE_OVERRIDE = 26;
         /**
          * Number of handled categories used for calculating array sizes.
          */
-        int NUM_ENTRIES = 26;
+        int NUM_ENTRIES = 27;
     }
 
     private final BrowserContextHandle mBrowserContextHandle;
@@ -202,6 +203,8 @@ public class SiteSettingsCategory {
                 return ContentSettingsType.USB_GUARD;
             case Type.VIRTUAL_REALITY:
                 return ContentSettingsType.VR;
+            case Type.TIMEZONE_OVERRIDE:
+                return ContentSettingsType.TIMEZONE_OVERRIDE;
             // case Type.ALL_SITES
             // case Type.USE_STORAGE
             default:
@@ -282,6 +285,8 @@ public class SiteSettingsCategory {
                 return "use_storage";
             case Type.VIRTUAL_REALITY:
                 return "virtual_reality";
+            case Type.TIMEZONE_OVERRIDE:
+                return "timezone_override";
             default:
                 assert false;
                 return "";
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsDelegate.java
@@ -158,6 +158,8 @@ public interface SiteSettingsDelegate {
      */
     String getFirstPartySetOwner(String memberOrigin);
 
+    void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity);
+
     /**
      * Returns whether the current implementation of the delegate is able to launch the Clear
      * Browsing Data dialog in Settings.
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java
new file mode 100755
--- /dev/null
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/TimezoneOverrideSiteSettingsPreference.java
@@ -0,0 +1,193 @@
+/*
+    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.components.browser_ui.site_settings;
+
+import org.chromium.base.Log;
+import android.content.Context;
+import android.content.Intent;
+import android.content.DialogInterface;
+import android.view.View;
+import android.widget.RadioGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.util.AttributeSet;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.view.LayoutInflater;
+import android.widget.AdapterView;
+import android.graphics.Color;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+import androidx.appcompat.app.AlertDialog;
+
+import org.chromium.content_public.browser.BrowserContextHandle;
+import org.chromium.components.content_settings.ContentSettingValues;
+import org.chromium.components.browser_ui.site_settings.WebsitePreferenceBridge;
+import org.chromium.components.browser_ui.widget.RadioButtonWithDescription;
+import org.chromium.components.browser_ui.widget.RadioButtonWithEditText;
+import org.chromium.components.browser_ui.widget.TintedDrawable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.TimeZone;
+
+/**
+ * TimezoneOverride Preference for SiteSettings.
+ */
+public class TimezoneOverrideSiteSettingsPreference
+        extends Preference implements RadioGroup.OnCheckedChangeListener,
+                                      RadioButtonWithEditText.OnTextChangeListener {
+    private @ContentSettingValues int mSetting = ContentSettingValues.DEFAULT;
+    private RadioButtonWithDescription mAllowed;
+    private RadioButtonWithEditText mAsk;
+    private RadioButtonWithDescription mBlocked;
+    private RadioGroup mRadioGroup;
+    private TextView mSelectButton;
+
+    private String currentSelected;
+
+    private BrowserContextHandle mBrowserContextHandle;
+
+    public TimezoneOverrideSiteSettingsPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setLayoutResource(R.layout.timezoneoverride_site_settings_preference);
+        setSelectable(false);
+    }
+
+    public void initialize(@ContentSettingValues int setting, BrowserContextHandle browserContextHandle) {
+        mSetting = setting;
+        mBrowserContextHandle = browserContextHandle;
+    }
+
+    public @ContentSettingValues int getCheckedSetting() {
+        return mSetting;
+    }
+
+    @Override
+    public void onCheckedChanged(RadioGroup group, int checkedId) {
+        if (mAllowed.isChecked()) {
+            mSetting = ContentSettingValues.ALLOW;
+        } else if (mAsk.isChecked()) {
+            mSetting = ContentSettingValues.ASK;
+        } else if (mBlocked.isChecked()) {
+            mSetting = ContentSettingValues.BLOCK;
+        }
+
+        callChangeListener(mSetting);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        mAllowed = (RadioButtonWithDescription) holder.findViewById(R.id.allowed);
+        mAsk = (RadioButtonWithEditText) holder.findViewById(R.id.ask);
+        mBlocked = (RadioButtonWithDescription) holder.findViewById(R.id.blocked);
+        mRadioGroup = (RadioGroup) holder.findViewById(R.id.radio_button_layout);
+        mRadioGroup.setOnCheckedChangeListener(this);
+
+        mAsk.setPrimaryText(WebsitePreferenceBridge.getCustomTimezone(mBrowserContextHandle));
+        mAsk.addTextChangeListener(this);
+
+        ListView listView = (ListView)holder.findViewById(R.id.listView);
+
+        mSelectButton = (TextView) holder.findViewById(R.id.select_button);
+        mSelectButton.setOnClickListener(view -> {
+          showSelectTimeZoneDialog();
+        });
+
+        RadioButtonWithDescription radioButton = findRadioButton(mSetting);
+        if (radioButton != null) radioButton.setChecked(true);
+    }
+
+    private RadioButtonWithDescription findRadioButton(@ContentSettingValues int setting) {
+        if (setting == ContentSettingValues.ALLOW) {
+            return mAllowed;
+        } else if (setting == ContentSettingValues.ASK) {
+            return mAsk;
+        } else if (setting == ContentSettingValues.BLOCK) {
+            return mBlocked;
+        } else {
+            return null;
+        }
+    }
+
+    public void onTextChanged(CharSequence newText) {
+        WebsitePreferenceBridge.setCustomTimezone(mBrowserContextHandle, newText.toString());
+    }
+
+    private void showSelectTimeZoneDialog() {
+        LayoutInflater inflater =
+                (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(R.layout.time_zone_select_dialog, null);
+
+        ListView listView = view.findViewById(R.id.listView);
+        ArrayList<String> timezones = new ArrayList<>(Arrays.asList(TimeZone.getAvailableIDs()));
+        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, android.R.id.text1, timezones);
+        listView.setAdapter(adapter);
+
+        currentSelected = String.valueOf(mAsk.getPrimaryText());
+        listView.post(new Runnable()
+        {
+          public void run()
+          {
+            for (int j = 0; j < timezones.size(); j++) {
+                if (currentSelected.equals(timezones.get(j))) {
+                    listView.requestFocusFromTouch();
+                    listView.setSelection(j);
+                    adapter.notifyDataSetChanged();
+                    break;
+                }
+            }
+          }
+        });
+
+        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
+                currentSelected = timezones.get(i);
+                listView.setSelected(true);
+            }
+        });
+
+        DialogInterface.OnClickListener onClickListener = new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int button) {
+                if (button == AlertDialog.BUTTON_POSITIVE) {
+                    mAsk.setPrimaryText(currentSelected);
+                } else {
+                    dialog.dismiss();
+                }
+            }
+        };
+
+        AlertDialog.Builder alert =
+                new AlertDialog.Builder(getContext(), R.style.ThemeOverlay_BrowserUI_AlertDialog);
+        AlertDialog alertDialog =
+                alert.setTitle(R.string.website_settings_select_dialog_title)
+                        .setView(view)
+                        .setPositiveButton(
+                                R.string.website_settings_select_dialog_button, onClickListener)
+                        .setNegativeButton(R.string.cancel, onClickListener)
+                        .create();
+        alertDialog.getDelegate().setHandleNativeActionModesEnabled(false);
+        alertDialog.show();
+    }
+}
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/Website.java
@@ -243,6 +243,17 @@ public final class Website implements WebsiteEntry {
             } else {
                 RecordUserAction.record("SoundContentSetting.UnmuteBy.SiteSettings");
             }
+        } else if (type == ContentSettingsType.TIMEZONE_OVERRIDE) {
+            // It is possible to set the permission without having an existing exception,
+            // because we can show the ALLOW state even when this permission is set to the
+            // default. In that case, just set an exception now to ALLOW to enable changing the
+            // permission.
+            if (exception == null) {
+                exception = new ContentSettingException(
+                        ContentSettingsType.TIMEZONE_OVERRIDE, getAddress().getHost(), value, "",
+                        /*isEmbargoed=*/false);
+                setContentSettingException(type, exception);
+            }
         }
         // We want to call setContentSetting even after explicitly setting
         // mContentSettingException above because this will trigger the actual change
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.java
@@ -71,6 +71,7 @@ public class WebsitePermissionsFetcher {
             case ContentSettingsType.POPUPS:
             case ContentSettingsType.REQUEST_DESKTOP_SITE:
             case ContentSettingsType.SOUND:
+            case ContentSettingsType.TIMEZONE_OVERRIDE:
                 return WebsitePermissionsType.CONTENT_SETTING_EXCEPTION;
             case ContentSettingsType.AR:
             case ContentSettingsType.CLIPBOARD_READ_WRITE:
@@ -208,6 +209,7 @@ public class WebsitePermissionsFetcher {
             for (@ContentSettingsType int type = 0; type < ContentSettingsType.NUM_TYPES; type++) {
                 addFetcherForContentSettingsType(queue, type);
             }
+            queue.add(new ExceptionInfoFetcher(ContentSettingsType.TIMEZONE_OVERRIDE));
         }
 
         /**
diff --git a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java
--- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java
+++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/WebsitePreferenceBridge.java
@@ -243,6 +243,8 @@ public class WebsitePreferenceBridge {
         switch (contentSettingsType) {
             case ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER:
                 return true;
+            case ContentSettingsType.TIMEZONE_OVERRIDE:
+                return true;
             default:
                 return false;
         }
@@ -392,6 +394,14 @@ public class WebsitePreferenceBridge {
         return WebsitePreferenceBridgeJni.get().toHostOnlyPattern(pattern);
     }
 
+    public static String getCustomTimezone(BrowserContextHandle browserContextHandle) {
+        return WebsitePreferenceBridgeJni.get().getCustomTimezone(browserContextHandle);
+    }
+
+    public static void setCustomTimezone(BrowserContextHandle browserContextHandle, String custom_timezone) {
+        WebsitePreferenceBridgeJni.get().setCustomTimezone(browserContextHandle, custom_timezone);
+    }
+
     @NativeMethods
     public interface Natives {
         boolean isNotificationEmbargoedForOrigin(
@@ -456,5 +466,7 @@ public class WebsitePreferenceBridge {
         boolean getLocationAllowedByPolicy(BrowserContextHandle browserContextHandle);
         String toDomainWildcardPattern(String pattern);
         String toHostOnlyPattern(String pattern);
+        String getCustomTimezone(BrowserContextHandle browserContextHandle);
+        void setCustomTimezone(BrowserContextHandle browserContextHandle, String custom_timezone);
     }
 }
diff --git a/components/browser_ui/site_settings/android/website_preference_bridge.cc b/components/browser_ui/site_settings/android/website_preference_bridge.cc
--- a/components/browser_ui/site_settings/android/website_preference_bridge.cc
+++ b/components/browser_ui/site_settings/android/website_preference_bridge.cc
@@ -1016,3 +1016,19 @@ JNI_WebsitePreferenceBridge_ToHostOnlyPattern(
           ContentSettingsPattern::FromString(pattern_string));
   return ConvertUTF8ToJavaString(env, host_only_pattern.ToString());
 }
+
+static void JNI_WebsitePreferenceBridge_SetCustomTimezone(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& jbrowser_context_handle,
+    const JavaParamRef<jstring>& custom_timezone) {
+  std::string new_timezone = ConvertJavaStringToUTF8(env, custom_timezone);
+  GetHostContentSettingsMap(jbrowser_context_handle)->SetTimezoneOverrideValue(new_timezone);
+}
+
+static base::android::ScopedJavaLocalRef<jstring> JNI_WebsitePreferenceBridge_GetCustomTimezone(
+    JNIEnv* env,
+    const JavaParamRef<jobject>& jbrowser_context_handle) {
+  std::string custom_timezone;
+  GetHostContentSettingsMap(jbrowser_context_handle)->GetTimezoneOverrideValue(custom_timezone);
+  return ConvertUTF8ToJavaString(env, custom_timezone);
+}
diff --git a/components/browser_ui/strings/android/site_settings.grdp b/components/browser_ui/strings/android/site_settings.grdp
--- a/components/browser_ui/strings/android/site_settings.grdp
+++ b/components/browser_ui/strings/android/site_settings.grdp
@@ -75,6 +75,9 @@
   <message name="IDS_AUTO_DARK_WEB_CONTENT_TITLE" desc="Title of the permission auto darken web content [CHAR_LIMIT=32]">
     Dark theme for sites
   </message>
+  <message name="IDS_TIMEZONE_OVERRIDE_PERMISSION_TITLE" desc="Title of the permission to use TimeZone Override [CHAR-LIMIT=32]">
+    Timezone override
+  </message>
   <message name="IDS_DESKTOP_SITE_TITLE" desc="Title of the permission to request the desktop view of a site by default [CHAR_LIMIT=32]">
     Desktop site
   </message>
@@ -587,6 +590,38 @@
     Block sites from playing protected content
   </message>
 
+  <!-- Timezone override -->
+  <message name="IDS_WEBSITE_SETTINGS_TIMEOVERRIDE_INFO" desc="Primary text explaining the timezone override feature.">
+    Override timezone with a custom or random one, or use the system timezone
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_ALLOWED_TEXT" desc="Primary text explaining that sites are allowed to access the system timezone.">
+    None (use system timezone)
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_RANDOM_TEXT" desc="Primary text for random timezone override.">
+    Random
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_ALLOWED" desc="Summary text explaining that sites are allowed to access the system timezone.">
+    System timezone
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_CUSTOM" desc="Summary text explaining that sites use custom timezone.">
+    Custom timezone
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_CUSTOM_HINT" desc="Hint text for edit custom timezone.">
+    Specify a custom timezone (default UTC)
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_CATEGORY_TIMEZONE_OVERRIDE_RANDOM" desc="Summary text for random timezone override.">
+    Random (for each page)
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SELECT_BUTTON" desc="Primary button text for choosing a timezone from a list.">
+    Choose Timezone...
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SELECT_DIALOG_TITLE" desc="Text of the dialog to choose a timezone.">
+    Choose Timezone
+  </message>
+  <message name="IDS_WEBSITE_SETTINGS_SELECT_DIALOG_BUTTON" desc="Primary button text in the dialog used to choose the custom timezone.">
+    Select
+  </message>
+
   <!-- Sound -->
 
   <message name="IDS_WEBSITE_SETTINGS_CATEGORY_SOUND_ALLOWED" desc="Summary text explaining that sites are allowed to play sound and that it is the recommended setting.">
diff --git a/components/content_settings/core/browser/content_settings_pref_provider.cc b/components/content_settings/core/browser/content_settings_pref_provider.cc
--- a/components/content_settings/core/browser/content_settings_pref_provider.cc
+++ b/components/content_settings/core/browser/content_settings_pref_provider.cc
@@ -90,6 +90,8 @@ void PrefProvider::RegisterProfilePrefs(
                                      info->GetPrefRegistrationFlags());
   }
 
+  registry->RegisterStringPref(prefs::kContentSettingsCustomTimezone, std::string());
+
   // Obsolete prefs ----------------------------------------------------------
 
   // These prefs have been removed, but need to be registered so they can
@@ -178,6 +180,10 @@ PrefProvider::PrefProvider(PrefService* prefs,
     event_args->set_number_of_exceptions(
         num_exceptions);  // PrefProvider::PrefProvider.
   });
+
+  custom_timezone_ =
+    prefs_->GetString(
+          prefs::kContentSettingsCustomTimezone);
 }
 
 PrefProvider::~PrefProvider() {
@@ -342,4 +348,14 @@ void PrefProvider::SetClockForTesting(base::Clock* clock) {
   clock_ = clock;
 }
 
+void PrefProvider::GetPrefTimezoneOverrideValue(std::string& timezone) const {
+  timezone = custom_timezone_;
+}
+
+void PrefProvider::SetPrefTimezoneOverrideValue(const std::string& timezone) {
+  prefs_->SetString(
+        prefs::kContentSettingsCustomTimezone, timezone);
+  custom_timezone_ = timezone;
+}
+
 }  // namespace content_settings
diff --git a/components/content_settings/core/browser/content_settings_pref_provider.h b/components/content_settings/core/browser/content_settings_pref_provider.h
--- a/components/content_settings/core/browser/content_settings_pref_provider.h
+++ b/components/content_settings/core/browser/content_settings_pref_provider.h
@@ -65,6 +65,9 @@ class PrefProvider : public UserModifiableProvider {
 
   ContentSettingsPref* GetPref(ContentSettingsType type) const;
 
+  void GetPrefTimezoneOverrideValue(std::string& timezone) const;
+  void SetPrefTimezoneOverrideValue(const std::string& timezone);
+
  private:
   friend class DeadlockCheckerObserver;  // For testing.
 
@@ -96,6 +99,7 @@ class PrefProvider : public UserModifiableProvider {
   base::ThreadChecker thread_checker_;
 
   raw_ptr<base::Clock> clock_;
+  std::string custom_timezone_;
 };
 
 }  // namespace content_settings
diff --git a/components/content_settings/core/browser/content_settings_registry.cc b/components/content_settings/core/browser/content_settings_registry.cc
--- a/components/content_settings/core/browser/content_settings_registry.cc
+++ b/components/content_settings/core/browser/content_settings_registry.cc
@@ -645,6 +645,18 @@ void ContentSettingsRegistry::Init() {
            ContentSettingsInfo::PERSISTENT,
            ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS);
 
+  Register(ContentSettingsType::TIMEZONE_OVERRIDE, "timezone-override", CONTENT_SETTING_ALLOW,
+           WebsiteSettingsInfo::SYNCABLE,
+           AllowlistedSchemes(kChromeUIScheme, kChromeDevToolsScheme),
+           ValidSettings(CONTENT_SETTING_ALLOW,   // use system time
+                         CONTENT_SETTING_ASK,     // custom timezone, default UTC
+                         CONTENT_SETTING_BLOCK),  // random
+           WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE,
+           WebsiteSettingsRegistry::PLATFORM_ANDROID,
+           ContentSettingsInfo::INHERIT_IN_INCOGNITO,
+           ContentSettingsInfo::PERSISTENT,
+           ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS);
+
   Register(ContentSettingsType::FEDERATED_IDENTITY_API, "webid-api",
            CONTENT_SETTING_ALLOW, WebsiteSettingsInfo::UNSYNCABLE,
            AllowlistedSchemes(),
diff --git a/components/content_settings/core/browser/content_settings_utils.cc b/components/content_settings/core/browser/content_settings_utils.cc
--- a/components/content_settings/core/browser/content_settings_utils.cc
+++ b/components/content_settings/core/browser/content_settings_utils.cc
@@ -151,6 +151,13 @@ void GetRendererContentSettingRules(const HostContentSettingsMap* map,
                              &(rules->script_rules));
   map->GetSettingsForOneType(ContentSettingsType::POPUPS,
                              &(rules->popup_redirect_rules));
+
+  // pass custom timezone rules and value to the render process
+  map->GetSettingsForOneType(ContentSettingsType::TIMEZONE_OVERRIDE,
+                             &(rules->timezone_override_rules));
+  std::string timezone;
+  map->GetTimezoneOverrideValue(timezone);
+  rules->timezone_override_value = timezone;
 }
 
 bool IsMorePermissive(ContentSetting a, ContentSetting b) {
diff --git a/components/content_settings/core/browser/host_content_settings_map.cc b/components/content_settings/core/browser/host_content_settings_map.cc
--- a/components/content_settings/core/browser/host_content_settings_map.cc
+++ b/components/content_settings/core/browser/host_content_settings_map.cc
@@ -602,6 +602,14 @@ void HostContentSettingsMap::SetClockForTesting(base::Clock* clock) {
     provider->SetClockForTesting(clock);
 }
 
+void HostContentSettingsMap::GetTimezoneOverrideValue(std::string& custom_timezone) const {
+  GetPrefProvider()->GetPrefTimezoneOverrideValue(custom_timezone);
+}
+
+void HostContentSettingsMap::SetTimezoneOverrideValue(const std::string& custom_timezone) {
+  GetPrefProvider()->SetPrefTimezoneOverrideValue(custom_timezone);
+}
+
 void HostContentSettingsMap::RecordExceptionMetrics() {
   auto* content_setting_registry =
       content_settings::ContentSettingsRegistry::GetInstance();
diff --git a/components/content_settings/core/browser/host_content_settings_map.h b/components/content_settings/core/browser/host_content_settings_map.h
--- a/components/content_settings/core/browser/host_content_settings_map.h
+++ b/components/content_settings/core/browser/host_content_settings_map.h
@@ -329,6 +329,9 @@ class HostContentSettingsMap : public content_settings::Observer,
     allow_invalid_secondary_pattern_for_testing_ = allow;
   }
 
+  void GetTimezoneOverrideValue(std::string& custom_timezone) const;
+  void SetTimezoneOverrideValue(const std::string& custom_timezone);
+
  private:
   friend class base::RefCountedThreadSafe<HostContentSettingsMap>;
   friend class content_settings::TestUtils;
diff --git a/components/content_settings/core/common/content_settings.cc b/components/content_settings/core/common/content_settings.cc
--- a/components/content_settings/core/common/content_settings.cc
+++ b/components/content_settings/core/common/content_settings.cc
@@ -204,7 +204,8 @@ bool RendererContentSettingRules::IsRendererContentSetting(
          content_type == ContentSettingsType::JAVASCRIPT ||
          content_type == ContentSettingsType::POPUPS ||
          content_type == ContentSettingsType::MIXEDSCRIPT ||
-         content_type == ContentSettingsType::AUTO_DARK_WEB_CONTENT;
+         content_type == ContentSettingsType::AUTO_DARK_WEB_CONTENT ||
+         content_type == ContentSettingsType::TIMEZONE_OVERRIDE;
 }
 
 void RendererContentSettingRules::FilterRulesByOutermostMainFrameURL(
@@ -214,6 +215,7 @@ void RendererContentSettingRules::FilterRulesByOutermostMainFrameURL(
   FilterRulesForType(popup_redirect_rules, outermost_main_frame_url);
   FilterRulesForType(mixed_content_rules, outermost_main_frame_url);
   FilterRulesForType(auto_dark_content_rules, outermost_main_frame_url);
+  FilterRulesForType(timezone_override_rules, outermost_main_frame_url);
 }
 
 RendererContentSettingRules::RendererContentSettingRules() = default;
diff --git a/components/content_settings/core/common/content_settings.h b/components/content_settings/core/common/content_settings.h
--- a/components/content_settings/core/common/content_settings.h
+++ b/components/content_settings/core/common/content_settings.h
@@ -93,6 +93,8 @@ struct RendererContentSettingRules {
   ContentSettingsForOneType popup_redirect_rules;
   ContentSettingsForOneType mixed_content_rules;
   ContentSettingsForOneType auto_dark_content_rules;
+  ContentSettingsForOneType timezone_override_rules;
+  std::string timezone_override_value;
 };
 
 namespace content_settings {
diff --git a/components/content_settings/core/common/content_settings.mojom b/components/content_settings/core/common/content_settings.mojom
--- a/components/content_settings/core/common/content_settings.mojom
+++ b/components/content_settings/core/common/content_settings.mojom
@@ -78,4 +78,6 @@ struct RendererContentSettingRules {
   array<ContentSettingPatternSource> popup_redirect_rules;
   array<ContentSettingPatternSource> mixed_content_rules;
   array<ContentSettingPatternSource> auto_dark_content_rules;
+  array<ContentSettingPatternSource> timezone_override_rules;
+  string timezone_override_value;
 };
diff --git a/components/content_settings/core/common/content_settings_mojom_traits.cc b/components/content_settings/core/common/content_settings_mojom_traits.cc
--- a/components/content_settings/core/common/content_settings_mojom_traits.cc
+++ b/components/content_settings/core/common/content_settings_mojom_traits.cc
@@ -102,7 +102,9 @@ bool StructTraits<content_settings::mojom::RendererContentSettingRulesDataView,
          data.ReadScriptRules(&out->script_rules) &&
          data.ReadPopupRedirectRules(&out->popup_redirect_rules) &&
          data.ReadMixedContentRules(&out->mixed_content_rules) &&
-         data.ReadAutoDarkContentRules(&out->auto_dark_content_rules);
+         data.ReadAutoDarkContentRules(&out->auto_dark_content_rules) &&
+         data.ReadTimezoneOverrideRules(&out->timezone_override_rules) &&
+         data.ReadTimezoneOverrideValue(&out->timezone_override_value);
 }
 
 }  // namespace mojo
diff --git a/components/content_settings/core/common/content_settings_mojom_traits.h b/components/content_settings/core/common/content_settings_mojom_traits.h
--- a/components/content_settings/core/common/content_settings_mojom_traits.h
+++ b/components/content_settings/core/common/content_settings_mojom_traits.h
@@ -150,6 +150,16 @@ struct StructTraits<
     return r.auto_dark_content_rules;
   }
 
+  static const std::vector<ContentSettingPatternSource>& timezone_override_rules(
+      const RendererContentSettingRules& r) {
+    return r.timezone_override_rules;
+  }
+
+  static const std::string& timezone_override_value(
+      const RendererContentSettingRules& r) {
+    return r.timezone_override_value;
+  }
+
   static bool Read(
       content_settings::mojom::RendererContentSettingRulesDataView data,
       RendererContentSettingRules* out);
diff --git a/components/content_settings/core/common/content_settings_types.h b/components/content_settings/core/common/content_settings_types.h
--- a/components/content_settings/core/common/content_settings_types.h
+++ b/components/content_settings/core/common/content_settings_types.h
@@ -226,6 +226,9 @@ enum class ContentSettingsType : int32_t {
   // by the File System Access API.
   FILE_SYSTEM_LAST_PICKED_DIRECTORY,
 
+  // Content setting for timezone customization functionality.
+  TIMEZONE_OVERRIDE,
+
   // Controls access to the getDisplayMedia API when {preferCurrentTab: true}
   // is specified.
   // TODO(crbug.com/1150788): Also apply this when getDisplayMedia() is called
diff --git a/components/content_settings/core/common/pref_names.cc b/components/content_settings/core/common/pref_names.cc
--- a/components/content_settings/core/common/pref_names.cc
+++ b/components/content_settings/core/common/pref_names.cc
@@ -176,4 +176,7 @@ const char kDesktopSitePeripheralSettingEnabled[] =
 const char kDesktopSiteDisplaySettingEnabled[] = "desktop_site.display_setting";
 #endif
 
+const char kContentSettingsCustomTimezone[] =
+    "profile.content_settings.custom_timezone";
+
 }  // namespace prefs
diff --git a/components/content_settings/core/common/pref_names.h b/components/content_settings/core/common/pref_names.h
--- a/components/content_settings/core/common/pref_names.h
+++ b/components/content_settings/core/common/pref_names.h
@@ -91,6 +91,8 @@ extern const char kDesktopSitePeripheralSettingEnabled[];
 extern const char kDesktopSiteDisplaySettingEnabled[];
 #endif
 
+extern const char kContentSettingsCustomTimezone[];
+
 }  // namespace prefs
 
 #endif  // COMPONENTS_CONTENT_SETTINGS_CORE_COMMON_PREF_NAMES_H_
diff --git a/components/content_settings/renderer/content_settings_agent_impl.cc b/components/content_settings/renderer/content_settings_agent_impl.cc
--- a/components/content_settings/renderer/content_settings_agent_impl.cc
+++ b/components/content_settings/renderer/content_settings_agent_impl.cc
@@ -8,8 +8,10 @@
 
 #include "base/bind.h"
 #include "base/feature_list.h"
+#include "base/logging.h"
 #include "base/metrics/histogram_macros.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/rand_util.h"
 #include "components/content_settings/core/common/content_settings.h"
 #include "components/content_settings/core/common/content_settings.mojom.h"
 #include "components/content_settings/core/common/content_settings_pattern.h"
@@ -329,6 +331,10 @@ bool ContentSettingsAgentImpl::AllowScript(bool enabled_per_settings) {
   allow = allow || IsAllowlistedForContentSettings();
 
   cached_script_permissions_[frame] = allow;
+
+  if (allow)
+    UpdateOverrides();
+
   return allow;
 }
 
@@ -465,4 +471,81 @@ bool ContentSettingsAgentImpl::IsAllowlistedForContentSettings() const {
   return false;
 }
 
+bool ContentSettingsAgentImpl::UpdateOverrides() {
+  // Evaluate the content setting rules
+  ContentSetting setting = CONTENT_SETTING_ALLOW;
+
+  if (content_setting_rules_) {
+    blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
+
+    setting = GetContentSettingFromRules(
+        content_setting_rules_->timezone_override_rules,
+        url::Origin(frame->GetDocument().GetSecurityOrigin()).GetURL());
+  }
+  return UpdateTimeZoneOverride(
+      setting, content_setting_rules_->timezone_override_value);
+  //&& UpdateLocaleOverride(setting);
+}
+
+bool ContentSettingsAgentImpl::UpdateTimeZoneOverride(
+    ContentSetting setting,
+    const std::string& timezone_override_value) {
+  // base/i18n/icu_util.cc # 329
+
+  /* timezone_id: third_party/icu/source/i18n/timezone.cpp
+      We first try to lookup the zone ID in our system list.  If this
+      * fails, we try to parse it as a custom string GMT[+-]hh:mm.  If
+      * all else fails, we return GMT, which is probably not what the
+      * user wants, but at least is a functioning TimeZone object.
+      */
+  String timezone_id;
+
+  if (setting == CONTENT_SETTING_ALLOW) {
+    // system time
+    if (timezone_override_) {
+      timezone_override_.reset();
+    }
+    return true;
+  } else if (setting == CONTENT_SETTING_BLOCK) {
+    // timezone random
+    UErrorCode ec = U_ZERO_ERROR;
+    int32_t rawOffset = base::RandInt(-12, 11) * 3600 * 1000;
+    icu::StringEnumeration* timezones = icu::TimeZone::createEnumeration(
+        rawOffset);  // Obtain timezones by GMT timezone offset
+    if (timezones) {
+      const char* tzID;
+      int32_t length;
+      if ((tzID = timezones->next(&length, ec)) != NULL) {
+        timezone_id = String(tzID);
+      }
+      delete timezones;
+    }
+  } else if (setting == CONTENT_SETTING_ASK) {
+    if (timezone_override_value.empty())
+      timezone_id = "Europe/London";
+    else
+      timezone_id = String(timezone_override_value.c_str());
+  }
+
+  if (blink::TimeZoneController::HasTimeZoneOverride() == false) {
+    timezone_override_.reset();
+    timezone_override_ =
+        blink::TimeZoneController::SetTimeZoneOverride(timezone_id);
+    if (!timezone_override_) {
+      LOG(WARNING) << "UpdateTimeZoneOverride - Invalid timezone id '"
+                   << timezone_id << "'";
+      return false;
+    } else {
+      LOG(INFO)
+          << "UpdateTimeZoneOverride - setting to '"
+          << timezone_id << "'";
+      return true;
+    }
+  } else {
+    LOG(INFO)
+        << "UpdateTimeZoneOverride: already set";
+    return false;
+  }
+}
+
 }  // namespace content_settings
diff --git a/components/content_settings/renderer/content_settings_agent_impl.h b/components/content_settings/renderer/content_settings_agent_impl.h
--- a/components/content_settings/renderer/content_settings_agent_impl.h
+++ b/components/content_settings/renderer/content_settings_agent_impl.h
@@ -25,6 +25,11 @@
 #include "url/gurl.h"
 #include "url/origin.h"
 
+#include "third_party/blink/renderer/core/inspector/locale_controller.h"
+#include "third_party/blink/renderer/core/timezone/timezone_controller.h"
+#include "third_party/icu/source/common/unicode/strenum.h"
+#include "third_party/icu/source/i18n/unicode/timezone.h"
+
 namespace blink {
 class WebFrame;
 class WebURL;
@@ -167,6 +172,12 @@ class ContentSettingsAgentImpl
   std::unique_ptr<Delegate> delegate_;
 
   mojo::AssociatedReceiverSet<mojom::ContentSettingsAgent> receivers_;
+
+  std::unique_ptr<blink::TimeZoneController::TimeZoneOverride> timezone_override_;
+
+  bool UpdateOverrides();
+  bool UpdateTimeZoneOverride(ContentSetting setting, const std::string& timezone_override_value);
+  bool UpdateLocaleOverride(ContentSetting setting);
 };
 
 }  // namespace content_settings
diff --git a/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java b/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
--- a/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
+++ b/weblayer/browser/java/org/chromium/weblayer_private/settings/WebLayerSiteSettingsDelegate.java
@@ -143,6 +143,9 @@ public class WebLayerSiteSettingsDelegate
     @Override
     public void dismissPrivacySandboxSnackbar() {}
 
+    @Override
+    public void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity) {}
+
     @Override
     public boolean isFirstPartySetsDataAccessEnabled() {
         return false;
--
2.25.1
