From: uazo 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 --- .../ChromeSiteSettingsHelpClient.java | 18 ++ .../browser_ui/site_settings/android/BUILD.gn | 3 + .../res/layout/time_zone_select_dialog.xml | 36 ++++ ...ezoneoverride_site_settings_preference.xml | 68 ++++++ .../res/xml/single_website_preferences.xml | 2 + .../res/xml/site_settings_preferences.xml | 4 + .../java/res/xml/website_preferences.xml | 9 + .../ContentSettingsResources.java | 29 +++ .../site_settings/SingleCategorySettings.java | 72 ++++++- .../site_settings/SingleWebsiteSettings.java | 20 ++ .../site_settings/SiteSettings.java | 2 +- .../site_settings/SiteSettingsCategory.java | 9 +- .../site_settings/SiteSettingsHelpClient.java | 7 + ...imezoneOverrideSiteSettingsPreference.java | 193 ++++++++++++++++++ .../browser_ui/site_settings/Website.java | 10 + .../WebsitePermissionsFetcher.java | 3 + .../WebsitePreferenceBridge.java | 12 ++ .../android/website_preference_bridge.cc | 17 ++ .../strings/android/site_settings.grdp | 35 ++++ .../browser/content_settings_pref_provider.cc | 16 ++ .../browser/content_settings_pref_provider.h | 5 + .../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 | 89 ++++++++ .../renderer/content_settings_agent_impl.h | 4 + .../WebLayerSiteSettingsClient.java | 3 + 36 files changed, 716 insertions(+), 10 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/ChromeSiteSettingsHelpClient.java b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsHelpClient.java --- a/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsHelpClient.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/site_settings/ChromeSiteSettingsHelpClient.java @@ -11,6 +11,12 @@ import org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncherImpl; import org.chromium.chrome.browser.profiles.Profile; import org.chromium.components.browser_ui.site_settings.SiteSettingsHelpClient; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.provider.Browser; + /** * A SiteSettingsHelpClient instance that provides Chrome-specific help functionality. */ @@ -33,4 +39,16 @@ public class ChromeSiteSettingsHelpClient implements SiteSettingsHelpClient { currentActivity.getString(R.string.help_context_protected_content), Profile.getLastUsedRegularProfile(), null); } + + // 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); + } } 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 @@ -74,6 +74,7 @@ android_library("java") { "java/src/org/chromium/components/browser_ui/site_settings/WebsitePermissionsFetcher.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/TimezoneOverrideSiteSettingsPreference.java" ] annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] resources_package = "org.chromium.components.browser_ui.site_settings" @@ -204,6 +205,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 @@ + + + + + + + + + + \ 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 @@ + + + + + + + + + + + + + + + + + + + diff --git a/components/browser_ui/site_settings/android/java/res/xml/single_website_preferences.xml b/components/browser_ui/site_settings/android/java/res/xml/single_website_preferences.xml --- a/components/browser_ui/site_settings/android/java/res/xml/single_website_preferences.xml +++ b/components/browser_ui/site_settings/android/java/res/xml/single_website_preferences.xml @@ -59,6 +59,8 @@ android:key="push_notifications_list" /> + + + + + + + + + 0; return getString(resource); @@ -576,6 +598,13 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment if (mCategory.showSites(SiteSettingsCategory.Type.COOKIES) && mRequiresFourStateSetting) { setting = cookieSettingsExceptionShouldBlock() ? ContentSettingValues.BLOCK : ContentSettingValues.ALLOW; + } else if (mRequiresTriStateSetting) { + setting = WebsitePreferenceBridge.getContentSetting(browserContextHandle, mCategory.getContentSettingsType()); + if (setting == ContentSettingValues.ALLOW) { + setting = ContentSettingValues.BLOCK; + } else { + setting = ContentSettingValues.ALLOW; + } } else { setting = (WebsitePreferenceBridge.isCategoryEnabled( browserContextHandle, mCategory.getContentSettingsType())) @@ -633,6 +662,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment && !WebsitePreferenceBridge.isCategoryEnabled( browserContextHandle, ContentSettingsType.AUTOMATIC_DOWNLOADS)) { exception = true; + } else if (mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE)) { + exception = true; } if (exception) { getPreferenceScreen().addPreference(new AddExceptionPreference(getStyledContext(), @@ -806,7 +837,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( @@ -835,6 +873,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); Preference notificationsQuietUi = screen.findPreference(NOTIFICATIONS_QUIET_UI_TOGGLE_KEY); Preference explainProtectedMediaKey = screen.findPreference(EXPLAIN_PROTECTED_MEDIA_KEY); @@ -846,20 +887,32 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment if (mRequiresTriStateSetting) { screen.removePreference(binaryToggle); screen.removePreference(fourStateCookieToggle); - configureTriStateToggle(triStateToggle, contentType); + if (mCategory.showSites(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.showSites(SiteSettingsCategory.Type.COOKIES)) { screen.removePreference(screen.findPreference(COOKIE_INFO_TEXT_KEY)); } + if (!(mCategory.showSites(SiteSettingsCategory.Type.TIMEZONE_OVERRIDE))) { + screen.removePreference(screen.findPreference(TIMEOVERRIDE_INFO_TEXT)); + } if (permissionBlockedByOs) { maybeShowOsWarning(screen); @@ -974,6 +1027,15 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment triStateToggle.initialize(setting, descriptionIds); } + private void configureTimeOverrideStateToggle( + TimezoneOverrideSiteSettingsPreference timeOverrideStateToggle) { + timeOverrideStateToggle.setOnPreferenceChangeListener(this); + @ContentSettingValues + int setting = WebsitePreferenceBridge.getContentSetting( + getSiteSettingsClient().getBrowserContextHandle(), ContentSettingsType.TIMEZONE_OVERRIDE); + timeOverrideStateToggle.initialize(setting, getSiteSettingsClient().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 @@ -109,6 +109,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: @@ -836,6 +838,24 @@ public class SingleWebsiteSettings extends SiteSettingsPreferenceFragment setUpPreferenceCommon(preference, value); ListPreference listPreference = (ListPreference) preference; + if (getContentSettingsTypeFromPreferenceKey(preference.getKey()) == ContentSettingsType.TIMEZONE_OVERRIDE) { + CharSequence[] keys = new String[3]; + keys[0] = ContentSetting.toString(ContentSettingValues.ALLOW); + keys[1] = ContentSetting.toString(ContentSettingValues.ASK); + keys[2] = ContentSetting.toString(ContentSettingValues.BLOCK); + int[] descriptionsId = ContentSettingsResources.getTriStateSettingDescriptionIDs(ContentSettingsType.TIMEZONE_OVERRIDE); + CharSequence[] descriptions = new String[3]; + descriptions[0] = getString(descriptionsId[0]); + descriptions[1] = getString(descriptionsId[1]); + descriptions[2] = getString(descriptionsId[2]); + listPreference.setEntryValues(keys); + listPreference.setEntries(descriptions); + listPreference.setOnPreferenceChangeListener(this); + int indexSelected = (value == ContentSettingValues.ASK ? 1 : (value == ContentSettingValues.ALLOW ? 0 : 2)); + listPreference.setValueIndex(indexSelected); + return; + } + CharSequence[] keys = new String[2]; CharSequence[] descriptions = new String[2]; keys[0] = ContentSetting.toString(ContentSettingValues.ALLOW); 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 @@ -113,7 +113,7 @@ public class SiteSettings } else if (Type.SOUND == prefCategory && !checked) { p.setSummary(ContentSettingsResources.getSoundBlockedListSummary()); } 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.CLIPBOARD, Type.COOKIES, Type.IDLE_DETECTION, Type.DEVICE_LOCATION, 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.USE_STORAGE, Type.TIMEZONE_OVERRIDE}) @Retention(RetentionPolicy.SOURCE) public @interface Type { // All updates here must also be reflected in {@link #preferenceKey(int) @@ -71,10 +71,11 @@ public class SiteSettingsCategory { int BLUETOOTH = 20; int VIRTUAL_REALITY = 21; int USE_STORAGE = 22; + int TIMEZONE_OVERRIDE = 23; /** * Number of handled categories used for calculating array sizes. */ - int NUM_ENTRIES = 23; + int NUM_ENTRIES = 24; } private final BrowserContextHandle mBrowserContextHandle; @@ -192,6 +193,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: @@ -265,6 +268,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/SiteSettingsHelpClient.java b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsHelpClient.java --- a/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsHelpClient.java +++ b/components/browser_ui/site_settings/android/java/src/org/chromium/components/browser_ui/site_settings/SiteSettingsHelpClient.java @@ -28,4 +28,11 @@ public interface SiteSettingsHelpClient { * @see org.chromium.chrome.browser.feedback.HelpAndFeedbackLauncher#show */ void launchProtectedContentHelpAndFeedbackActivity(Activity currentActivity); + + /** + * Launches a support page related to timezone override content. + * + * @see org.chromium.chrome.browser.help.HelpAndFeedback#show + */ + void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity); } 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 . +*/ + +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.components.content_settings.ContentSettingValues; +import org.chromium.components.embedder_support.browser_context.BrowserContextHandle; +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 timezones = new ArrayList<>(Arrays.asList(TimeZone.getAvailableIDs())); + ArrayAdapter adapter = new ArrayAdapter(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.Theme_Chromium_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 @@ -229,6 +229,16 @@ public final class Website implements Serializable { } 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, ""); + 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 @@ -64,6 +64,7 @@ public class WebsitePermissionsFetcher { case ContentSettingsType.JAVASCRIPT: case ContentSettingsType.POPUPS: case ContentSettingsType.SOUND: + case ContentSettingsType.TIMEZONE_OVERRIDE: return WebsitePermissionsType.CONTENT_SETTING_EXCEPTION; case ContentSettingsType.AR: case ContentSettingsType.CLIPBOARD_READ_WRITE: @@ -144,6 +145,8 @@ public class WebsitePermissionsFetcher { for (@ContentSettingsType int type = 0; type < ContentSettingsType.NUM_TYPES; type++) { addFetcherForContentSettingsType(queue, type); } + queue.add(new ExceptionInfoFetcher(ContentSettingsType.TIMEZONE_OVERRIDE)); + queue.add(new PermissionsAvailableCallbackRunner(callback)); queue.next(); } 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 @@ -223,6 +223,8 @@ public class WebsitePreferenceBridge { switch (contentSettingsType) { case ContentSettingsType.PROTECTED_MEDIA_IDENTIFIER: return true; + case ContentSettingsType.TIMEZONE_OVERRIDE: + return true; default: return false; } @@ -324,6 +326,14 @@ public class WebsitePreferenceBridge { contentSettingType, primaryPattern, secondaryPattern, setting); } + public static String getCustomTimezone(BrowserContextHandle browserContextHandle) { + return WebsitePreferenceBridgeJni.get().getCustomTimezone(browserContextHandle); + } + + public static void setCustomTimezone(BrowserContextHandle browserContextHandle, String timezone) { + WebsitePreferenceBridgeJni.get().setCustomTimezone(browserContextHandle, timezone); + } + @NativeMethods public interface Natives { boolean isNotificationEmbargoedForOrigin( @@ -377,5 +387,7 @@ public class WebsitePreferenceBridge { boolean isContentSettingManagedByCustodian( BrowserContextHandle browserContextHandle, int contentSettingType); boolean getLocationAllowedByPolicy(BrowserContextHandle browserContextHandle); + String getCustomTimezone(BrowserContextHandle browserContextHandle); + void setCustomTimezone(BrowserContextHandle browserContextHandle, String 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 @@ -918,3 +918,20 @@ static jboolean JNI_WebsitePreferenceBridge_GetLocationAllowedByPolicy( ->GetDefaultContentSetting(ContentSettingsType::GEOLOCATION, nullptr) == CONTENT_SETTING_ALLOW; } + +static void JNI_WebsitePreferenceBridge_SetCustomTimezone( + JNIEnv* env, + const JavaParamRef& jbrowser_context_handle, + const JavaParamRef& timezone) { + std::string new_timezone = ConvertJavaStringToUTF8(env, timezone); + GetHostContentSettingsMap(jbrowser_context_handle)->SetTimezoneOverrideValue(new_timezone); +} + +static base::android::ScopedJavaLocalRef JNI_WebsitePreferenceBridge_GetCustomTimezone( + JNIEnv* env, + const JavaParamRef& jbrowser_context_handle) { + std::string timezone; + GetHostContentSettingsMap(jbrowser_context_handle)->GetTimezoneOverrideValue(timezone); + return ConvertUTF8ToJavaString(env, 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 @@ -69,6 +69,9 @@ Virtual reality + + Timezone override + @@ -469,6 +472,38 @@ Block sites from playing protected content + + + Override timezone with a custom or random one, or use the system timezone + + + None (use system timezone) + + + Random + + + System timezone + + + Custom timezone + + + Specify a custom timezone (default UTC) + + + Random (for each page) + + + Choose Timezone... + + + Choose Timezone + + + Select + + 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 @@ -106,6 +106,8 @@ void PrefProvider::RegisterProfilePrefs( registry->RegisterDictionaryPref(kDeprecatedNativeFileSystemReadGuardPref); registry->RegisterDictionaryPref(kDeprecatedNativeFileSystemWriteGuardPref); #endif // !defined(OS_ANDROID) + + registry->RegisterStringPref(prefs::kContentSettingsCustomTimezone, std::string()); } PrefProvider::PrefProvider(PrefService* prefs, @@ -162,6 +164,10 @@ PrefProvider::PrefProvider(PrefService* prefs, num_exceptions); } + custom_timezone_ = + prefs_->GetString( + prefs::kContentSettingsCustomTimezone); + TRACE_EVENT_END1("startup", "PrefProvider::PrefProvider", "NumberOfExceptions", num_exceptions); } @@ -317,4 +323,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 @@ -63,6 +63,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. @@ -95,6 +98,8 @@ class PrefProvider : public UserModifiableProvider { base::Clock* clock_; + std::string custom_timezone_; + DISALLOW_COPY_AND_ASSIGN(PrefProvider); }; 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 @@ -621,6 +621,18 @@ void ContentSettingsRegistry::Init() { ContentSettingsInfo::INHERIT_IF_LESS_PERMISSIVE, ContentSettingsInfo::PERSISTENT, ContentSettingsInfo::EXCEPTIONS_ON_SECURE_ORIGINS_ONLY); + + 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::SINGLE_ORIGIN_WITH_EMBEDDED_EXCEPTIONS_SCOPE, + WebsiteSettingsRegistry::PLATFORM_ANDROID, + ContentSettingsInfo::INHERIT_IN_INCOGNITO, + ContentSettingsInfo::PERSISTENT, + ContentSettingsInfo::EXCEPTIONS_ON_SECURE_AND_INSECURE_ORIGINS); } void ContentSettingsRegistry::Register( 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 @@ -145,6 +145,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 @@ -618,6 +618,14 @@ void HostContentSettingsMap::SetClockForTesting(base::Clock* clock) { provider->SetClockForTesting(clock); } +void HostContentSettingsMap::GetTimezoneOverrideValue(std::string& timezone) const { + GetPrefProvider()->GetPrefTimezoneOverrideValue(timezone); +} + +void HostContentSettingsMap::SetTimezoneOverrideValue(const std::string& timezone) { + GetPrefProvider()->SetPrefTimezoneOverrideValue(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 @@ -335,6 +335,9 @@ class HostContentSettingsMap : public content_settings::Observer, allow_invalid_secondary_pattern_for_testing_ = allow; } + void GetTimezoneOverrideValue(std::string& timezone) const; + void SetTimezoneOverrideValue(const std::string& timezone); + private: friend class base::RefCountedThreadSafe; 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 @@ -93,6 +93,7 @@ constexpr HistogramValue kHistogramValue[] = { {ContentSettingsType::FONT_ACCESS, 71}, {ContentSettingsType::PERMISSION_AUTOREVOCATION_DATA, 72}, {ContentSettingsType::FILE_SYSTEM_LAST_PICKED_DIRECTORY, 73}, + {ContentSettingsType::TIMEZONE_OVERRIDE, 74}, }; } // namespace @@ -179,7 +180,8 @@ bool RendererContentSettingRules::IsRendererContentSetting( content_type == ContentSettingsType::JAVASCRIPT || content_type == ContentSettingsType::CLIENT_HINTS || content_type == ContentSettingsType::POPUPS || - content_type == ContentSettingsType::MIXEDSCRIPT; + content_type == ContentSettingsType::MIXEDSCRIPT || + content_type == ContentSettingsType::TIMEZONE_OVERRIDE; } RendererContentSettingRules::RendererContentSettingRules() {} 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 @@ -79,6 +79,8 @@ struct RendererContentSettingRules { ContentSettingsForOneType script_rules; ContentSettingsForOneType popup_redirect_rules; ContentSettingsForOneType mixed_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 @@ -77,4 +77,6 @@ struct RendererContentSettingRules { array script_rules; array popup_redirect_rules; array mixed_content_rules; + array 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 @@ -100,7 +100,9 @@ bool StructTraitsimage_rules) && data.ReadScriptRules(&out->script_rules) && data.ReadPopupRedirectRules(&out->popup_redirect_rules) && - data.ReadMixedContentRules(&out->mixed_content_rules); + data.ReadMixedContentRules(&out->mixed_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 @@ -145,6 +145,16 @@ struct StructTraits< return r.mixed_content_rules; } + static const std::vector& 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 @@ -229,6 +229,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, + NUM_TYPES, }; 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 @@ -128,4 +128,7 @@ const char kQuietNotificationPermissionUiEnablingMethod[] = const char kNotificationsVibrateEnabled[] = "notifications.vibrate_enabled"; #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 @@ -71,6 +71,8 @@ extern const char kQuietNotificationPermissionUiEnablingMethod[]; extern const char kNotificationsVibrateEnabled[]; #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 @@ -9,8 +9,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" @@ -32,6 +34,10 @@ #include "third_party/blink/public/web/web_local_frame.h" #include "third_party/blink/public/web/web_local_frame_client.h" #include "third_party/blink/public/web/web_view.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" #include "url/gurl.h" #include "url/origin.h" #include "url/url_constants.h" @@ -45,6 +51,8 @@ using blink::WebURL; using blink::WebView; using content::DocumentState; +std::unique_ptr timezone_override_; + namespace content_settings { namespace { @@ -368,6 +376,10 @@ bool ContentSettingsAgentImpl::AllowScript(bool enabled_per_settings) { allow = allow || IsAllowlistedForContentSettings(); cached_script_permissions_[frame] = allow; + + if (allow) + UpdateOverrides(); + return allow; } @@ -498,4 +510,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, frame, + 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 @@ -178,6 +178,10 @@ class ContentSettingsAgentImpl mojo::AssociatedReceiverSet receivers_; + bool UpdateOverrides(); + bool UpdateTimeZoneOverride(ContentSetting setting, const std::string& timezone_override_value); + bool UpdateLocaleOverride(ContentSetting setting); + DISALLOW_COPY_AND_ASSIGN(ContentSettingsAgentImpl); }; diff --git a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java --- a/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java +++ b/weblayer/browser/java/org/chromium/weblayer_private/WebLayerSiteSettingsClient.java @@ -147,6 +147,9 @@ public class WebLayerSiteSettingsClient implements SiteSettingsClient, ManagedPr @Override public void launchProtectedContentHelpAndFeedbackActivity(Activity currentActivity) {} + @Override + public void launchTimeZoneOverrideHelpAndFeedbackActivity(Activity currentActivity) {} + // WebappSettingsClient implementation: // A no-op since WebLayer doesn't support webapps. -- 2.17.1