[Android] Add app language prompt
This CL adds an app language prompt on second run. The new prompt is similar to the existing language prompt except that only one language can be selected and the UI language is updated. Two feature flags control the new prompt: - AppLanguagePrompt: If enabled the prompt will be shown once when Chrome starts. - ForceAppLanguagePrompt: For testing, when enabled the prompt will always be shown. This flag is can be controlled through chrome://flags. If the new app language prompt is shown to a user the old language ask prompt will never be shown. See https://crbug.com/1192802#c5 for a screenshot prompt this CL adds. Bug: 1192802 Change-Id: Ibb6362ae27220fb11acf1b882db885d288cb3308 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2911854 Reviewed-by: Theresa <twellington@chromium.org> Reviewed-by: Josh Simmons <jds@google.com> Commit-Queue: Trevor Perrier <perrier@chromium.org> Cr-Commit-Position: refs/heads/master@{#889516}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
dd1ed74459
commit
c1384bbd1d
chrome
android
java
src
org
chromium
chrome
browser
tabbed_mode
browser
about_flags.ccflag-metadata.jsonflag_descriptions.ccflag_descriptions.h
flags
android
language
android
translate
android
components
language
core
translate
core
tools/metrics/histograms
@ -47,6 +47,7 @@ import org.chromium.chrome.browser.gesturenav.HistoryNavigationCoordinator;
|
||||
import org.chromium.chrome.browser.gesturenav.NavigationSheet;
|
||||
import org.chromium.chrome.browser.gesturenav.TabbedSheetDelegate;
|
||||
import org.chromium.chrome.browser.history.HistoryManagerUtils;
|
||||
import org.chromium.chrome.browser.language.AppLanguagePromoDialog;
|
||||
import org.chromium.chrome.browser.language.LanguageAskPrompt;
|
||||
import org.chromium.chrome.browser.layouts.LayoutStateProvider;
|
||||
import org.chromium.chrome.browser.locale.LocaleManager;
|
||||
@ -724,7 +725,10 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator {
|
||||
mActivity, mActivity.getWindowAndroid())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (AppLanguagePromoDialog.maybeShowPrompt(
|
||||
mActivity, mActivity.getModalDialogManagerSupplier())) {
|
||||
return true;
|
||||
}
|
||||
return LanguageAskPrompt.maybeShowLanguageAskPrompt(
|
||||
mActivity, mActivity.getModalDialogManagerSupplier());
|
||||
}
|
||||
|
@ -6613,6 +6613,10 @@ const FeatureEntry kFeatureEntries[] = {
|
||||
flag_descriptions::kAndroidDetailedLanguageSettingsName,
|
||||
flag_descriptions::kAndroidDetailedLanguageSettingsDescription, kOsAndroid,
|
||||
FEATURE_VALUE_TYPE(language::kDetailedLanguageSettings)},
|
||||
{"android-force-app-language-prompt",
|
||||
flag_descriptions::kAndroidForceAppLanguagePromptName,
|
||||
flag_descriptions::kAndroidForceAppLanguagePromptDescription, kOsAndroid,
|
||||
FEATURE_VALUE_TYPE(language::kForceAppLanguagePrompt)},
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN) || defined(OS_MAC) || defined(OS_LINUX)
|
||||
|
@ -125,7 +125,12 @@
|
||||
{
|
||||
"name": "android-detailed-language-settings",
|
||||
"owners": [ "perrier", "chrome-language@google.com" ],
|
||||
"expiry_milestone": 93
|
||||
"expiry_milestone": 95
|
||||
},
|
||||
{
|
||||
"name": "android-force-app-language-prompt",
|
||||
"owners": [" perrier", "chrome-language@google.com"],
|
||||
"expiry_milestone": 95
|
||||
},
|
||||
{
|
||||
"name": "android-multiple-display",
|
||||
|
@ -2752,6 +2752,12 @@ const char kAndroidDetailedLanguageSettingsName[] =
|
||||
const char kAndroidDetailedLanguageSettingsDescription[] =
|
||||
"Enable the new detailed language settings page";
|
||||
|
||||
const char kAndroidForceAppLanguagePromptName[] =
|
||||
"Force second run app language prompt";
|
||||
const char kAndroidForceAppLanguagePromptDescription[] =
|
||||
"When enabled the app language prompt to change the UI language will"
|
||||
"always be shown.";
|
||||
|
||||
const char kAndroidLayoutChangeTabReparentingName[] =
|
||||
"Android Chrome UI phone/tablet layout change tab reparenting";
|
||||
const char kAndroidLayoutChangeTabReparentingDescription[] =
|
||||
|
@ -1590,6 +1590,9 @@ extern const char kAImageReaderDescription[];
|
||||
extern const char kAndroidDetailedLanguageSettingsName[];
|
||||
extern const char kAndroidDetailedLanguageSettingsDescription[];
|
||||
|
||||
extern const char kAndroidForceAppLanguagePromptName[];
|
||||
extern const char kAndroidForceAppLanguagePromptDescription[];
|
||||
|
||||
extern const char kAndroidLayoutChangeTabReparentingName[];
|
||||
extern const char kAndroidLayoutChangeTabReparentingDescription[];
|
||||
|
||||
|
@ -264,8 +264,10 @@ const base::Feature* const kFeaturesExposedToJava[] = {
|
||||
¬ifications::features::kUseChimeAndroidSdk,
|
||||
&paint_preview::kPaintPreviewDemo,
|
||||
&paint_preview::kPaintPreviewShowOnStartup,
|
||||
&language::kAppLanguagePrompt,
|
||||
&language::kDetailedLanguageSettings,
|
||||
&language::kExplicitLanguageAsk,
|
||||
&language::kForceAppLanguagePrompt,
|
||||
&language::kTranslateAssistContent,
|
||||
&language::kTranslateIntent,
|
||||
&messages::kMessagesForAndroidInfrastructure,
|
||||
|
@ -228,6 +228,7 @@ public abstract class ChromeFeatureList {
|
||||
"AndroidPartnerCustomizationPhenotype";
|
||||
public static final String ANDROID_SEARCH_ENGINE_CHOICE_NOTIFICATION =
|
||||
"AndroidSearchEngineChoiceNotification";
|
||||
public static final String APP_LANGUAGE_PROMPT = "AppLanguagePrompt";
|
||||
public static final String ASSISTANT_INTENT_EXPERIMENT_ID = "AssistantIntentExperimentId";
|
||||
public static final String ASSISTANT_INTENT_PAGE_URL = "AssistantIntentPageUrl";
|
||||
public static final String ASSISTANT_INTENT_TRANSLATE_INFO = "AssistantIntentTranslateInfo";
|
||||
@ -339,6 +340,7 @@ public abstract class ChromeFeatureList {
|
||||
public static final String FILLING_PASSWORDS_FROM_ANY_ORIGIN = "FillingPasswordsFromAnyOrigin";
|
||||
public static final String FOCUS_OMNIBOX_IN_INCOGNITO_TAB_INTENTS =
|
||||
"FocusOmniboxInIncognitoTabIntents";
|
||||
public static final String FORCE_APP_LANGUAGE_PROMPT = "ForceAppLanguagePrompt";
|
||||
public static final String GRANT_NOTIFICATIONS_TO_DSE = "GrantNotificationsToDSE";
|
||||
public static final String HANDLE_MEDIA_INTENTS = "HandleMediaIntents";
|
||||
public static final String CHROME_SURVEY_NEXT_ANDROID = "ChromeSurveyNextAndroid";
|
||||
|
@ -21,6 +21,7 @@ android_library("base_module_java") {
|
||||
|
||||
android_library("java") {
|
||||
sources = [
|
||||
"java/src/org/chromium/chrome/browser/language/AppLanguagePromoDialog.java",
|
||||
"java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java",
|
||||
"java/src/org/chromium/chrome/browser/language/settings/AddLanguageFragment.java",
|
||||
"java/src/org/chromium/chrome/browser/language/settings/AlwaysTranslateListFragment.java",
|
||||
@ -78,6 +79,8 @@ android_resources("java_resources") {
|
||||
"java/res/layout/accept_languages_item.xml",
|
||||
"java/res/layout/accept_languages_list.xml",
|
||||
"java/res/layout/add_languages_main.xml",
|
||||
"java/res/layout/app_language_prompt_content.xml",
|
||||
"java/res/layout/app_language_prompt_row.xml",
|
||||
"java/res/layout/language_ask_prompt_content.xml",
|
||||
"java/res/layout/language_ask_prompt_row.xml",
|
||||
"java/res/layout/language_ask_prompt_row_separator.xml",
|
||||
|
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2021 The Chromium Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/list_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textDirection="locale"
|
||||
app:leading="@dimen/text_size_medium_leading"
|
||||
android:text="@string/languages_srp_subtitle"
|
||||
style="@style/TextAppearance.TextMedium.Secondary" />
|
||||
|
||||
|
||||
<!-- FrameLayout is used for top and bottom shadow on RecyclerView -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/app_language_prompt_content_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="vertical"
|
||||
/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/top_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_shadow_height"
|
||||
android:src="@drawable/modern_toolbar_shadow"
|
||||
android:scaleType="fitXY"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_gravity="top"
|
||||
android:visibility="invisible" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bottom_shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/toolbar_shadow_height"
|
||||
android:src="@drawable/modern_toolbar_shadow"
|
||||
android:scaleType="fitXY"
|
||||
android:scaleY="-1"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_gravity="bottom" />
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright 2017 The Chromium Authors. All rights reserved.
|
||||
Use of this source code is governed by a BSD-style license that can be
|
||||
found in the LICENSE file. -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/app_language_prompt_row"
|
||||
style="@style/ListItemContainer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/app_language_prompt_radiobutton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/native_language_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.TextLarge.Primary"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/display_language_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:textAppearance="@style/TextAppearance.TextMedium.Secondary"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -8,12 +8,13 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/language_ask_row"
|
||||
style="@style/ListItemContainer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:baselineAligned="true">
|
||||
|
||||
<View
|
||||
style="@style/HorizontalDivider"
|
||||
android:layout_marginEnd="@dimen/list_item_default_margin" />
|
||||
android:layout_marginEnd="@dimen/list_item_default_margin"
|
||||
android:layout_marginStart="@dimen/list_item_default_margin"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
334
chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLanguagePromoDialog.java
Normal file
334
chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/AppLanguagePromoDialog.java
Normal file
@ -0,0 +1,334 @@
|
||||
// Copyright 2021 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
package org.chromium.chrome.browser.language;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.Resources;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.chromium.base.supplier.ObservableSupplier;
|
||||
import org.chromium.chrome.browser.flags.ChromeFeatureList;
|
||||
import org.chromium.chrome.browser.language.settings.LanguageItem;
|
||||
import org.chromium.chrome.browser.language.settings.LanguagesManager;
|
||||
import org.chromium.chrome.browser.translate.TranslateBridge;
|
||||
import org.chromium.components.language.GeoLanguageProviderBridge;
|
||||
import org.chromium.ui.modaldialog.DialogDismissalCause;
|
||||
import org.chromium.ui.modaldialog.ModalDialogManager;
|
||||
import org.chromium.ui.modaldialog.ModalDialogProperties;
|
||||
import org.chromium.ui.modelutil.PropertyModel;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implements a modal dialog that prompts the user to change their UI language. Displayed once at
|
||||
* browser startup when no other promo or modals are shown.
|
||||
*/
|
||||
public class AppLanguagePromoDialog implements ModalDialogProperties.Controller {
|
||||
private Activity mActivity;
|
||||
private ModalDialogManager mModalDialogManager;
|
||||
private PropertyModel mAppLanguageModal;
|
||||
private LanguageItemAdapter mAdapter;
|
||||
|
||||
@IntDef({ItemType.LANGUAGE, ItemType.SEPARATOR})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface ItemType {
|
||||
int LANGUAGE = 0;
|
||||
int SEPARATOR = 1;
|
||||
}
|
||||
|
||||
public AppLanguagePromoDialog(
|
||||
Activity activity, ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier) {
|
||||
mActivity = activity;
|
||||
mModalDialogManager = modalDialogManagerSupplier.get();
|
||||
|
||||
Resources resources = mActivity.getResources();
|
||||
mAppLanguageModal =
|
||||
new PropertyModel.Builder(ModalDialogProperties.ALL_KEYS)
|
||||
.with(ModalDialogProperties.CONTROLLER, this)
|
||||
.with(ModalDialogProperties.TITLE, resources, R.string.languages_srp_title)
|
||||
.with(ModalDialogProperties.POSITIVE_BUTTON_TEXT, resources,
|
||||
R.string.languages_srp_accept_title)
|
||||
.with(ModalDialogProperties.NEGATIVE_BUTTON_TEXT, resources,
|
||||
R.string.languages_srp_cancel_title)
|
||||
.with(ModalDialogProperties.CANCEL_ON_TOUCH_OUTSIDE, true)
|
||||
.with(ModalDialogProperties.PRIMARY_BUTTON_FILLED, true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class for managing a list of languages in a RecyclerView.
|
||||
*/
|
||||
private class LanguageItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private List<LanguageItem> mTopLanguages;
|
||||
private List<LanguageItem> mOtherLanguages;
|
||||
private LanguageItem mCurrentLanguage;
|
||||
|
||||
/**
|
||||
* @param topLanguages - LanguageItems to appear at the top of the adapter list.
|
||||
* @param otherLanguages - LanguageItems to appear below the top languages.
|
||||
* @param currentLanguage - The currently selected app language.
|
||||
*/
|
||||
public LanguageItemAdapter(Collection<LanguageItem> topLanguages,
|
||||
Collection<LanguageItem> otherLanguages, LanguageItem currentLanguage) {
|
||||
mTopLanguages = new ArrayList<LanguageItem>(topLanguages);
|
||||
mOtherLanguages = new ArrayList<LanguageItem>(otherLanguages);
|
||||
mCurrentLanguage = currentLanguage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
// The seperator is between top and other languages.
|
||||
return (position == mTopLanguages.size()) ? ItemType.SEPARATOR : ItemType.LANGUAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
switch (viewType) {
|
||||
case ItemType.LANGUAGE:
|
||||
View row = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.app_language_prompt_row, parent, false);
|
||||
return new AppLanguagePromptRowViewHolder(row);
|
||||
case ItemType.SEPARATOR:
|
||||
return new SeparatorViewHolder(
|
||||
LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.language_ask_prompt_row_separator, parent,
|
||||
false));
|
||||
default:
|
||||
assert false : "No matching viewType";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
|
||||
switch (getItemViewType(position)) {
|
||||
case ItemType.LANGUAGE:
|
||||
LanguageItem languageItem = getLanguageItemAt(position);
|
||||
((AppLanguagePromptRowViewHolder) holder)
|
||||
.bindViewHolder(languageItem, languageItem.equals(mCurrentLanguage));
|
||||
break;
|
||||
case ItemType.SEPARATOR:
|
||||
// No binding necessary for the separator.
|
||||
break;
|
||||
default:
|
||||
assert false : "No matching viewType";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the currently selected LanguageItem based on the position.
|
||||
* @param postion Offset of the LanguageItem to select.
|
||||
*/
|
||||
public void setSelectedLanguage(int position) {
|
||||
mCurrentLanguage = getLanguageItemAt(position);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
// Sum of both lists + a separator.
|
||||
return mTopLanguages.size() + mOtherLanguages.size() + 1;
|
||||
}
|
||||
|
||||
public LanguageItem getSelectedLanguage() {
|
||||
return mCurrentLanguage;
|
||||
}
|
||||
|
||||
private LanguageItem getLanguageItemAt(int position) {
|
||||
if (position < mTopLanguages.size()) {
|
||||
return mTopLanguages.get(position);
|
||||
} else if (position > mTopLanguages.size()) {
|
||||
// Other languages are offset by one from the seperator.
|
||||
return mOtherLanguages.get(position - mTopLanguages.size() - 1);
|
||||
}
|
||||
assert false : "The language item at the separator can not be accessed";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class representing an individual langauge row.
|
||||
*/
|
||||
private class AppLanguagePromptRowViewHolder
|
||||
extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
private TextView mDisplayNameTextView;
|
||||
private TextView mNativeNameTextView;
|
||||
private RadioButton mRadioButton;
|
||||
|
||||
AppLanguagePromptRowViewHolder(View view) {
|
||||
super(view);
|
||||
mDisplayNameTextView = ((TextView) itemView.findViewById(R.id.display_language_name));
|
||||
mNativeNameTextView = ((TextView) itemView.findViewById(R.id.native_language_name));
|
||||
mRadioButton =
|
||||
((RadioButton) itemView.findViewById(R.id.app_language_prompt_radiobutton));
|
||||
|
||||
view.setOnClickListener(this);
|
||||
mRadioButton.setOnClickListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
LanguageItemAdapter adapter = (LanguageItemAdapter) getBindingAdapter();
|
||||
adapter.setSelectedLanguage(getBindingAdapterPosition());
|
||||
}
|
||||
|
||||
public void bindViewHolder(LanguageItem languageItem, boolean checked) {
|
||||
mRadioButton.setChecked(checked);
|
||||
mDisplayNameTextView.setText(languageItem.getDisplayName());
|
||||
mNativeNameTextView.setText(languageItem.getNativeDisplayName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal class representing the separator row.
|
||||
*/
|
||||
private class SeparatorViewHolder extends RecyclerView.ViewHolder {
|
||||
SeparatorViewHolder(View view) {
|
||||
super(view);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the app language modal and add a custom view holding a list of languages with the
|
||||
* current location's and users preferred languages at the top.
|
||||
*/
|
||||
protected void showAppLanguageModal() {
|
||||
// Setup initial language lists.
|
||||
LanguageItem currentLanguage =
|
||||
LanguagesManager.getInstance().getLanguageItem(AppLocaleUtils.getAppLanguagePref());
|
||||
LinkedHashSet<LanguageItem> uiLanguages =
|
||||
new LinkedHashSet<>(LanguagesManager.getInstance().getPotentialUiLanguages());
|
||||
LinkedHashSet<LanguageItem> topLanguages = getTopLanguages(uiLanguages, currentLanguage);
|
||||
uiLanguages.removeAll(topLanguages);
|
||||
mAdapter = new LanguageItemAdapter(topLanguages, uiLanguages, currentLanguage);
|
||||
// Release all static LanguagesManager resources since they are no longer needed.
|
||||
LanguagesManager.recycle();
|
||||
|
||||
// Setup RecyclerView.
|
||||
View customView = LayoutInflater.from(mActivity).inflate(
|
||||
R.layout.app_language_prompt_content, null, false);
|
||||
RecyclerView list = customView.findViewById(R.id.app_language_prompt_content_recycler_view);
|
||||
list.setAdapter(mAdapter);
|
||||
list.setHasFixedSize(true);
|
||||
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mActivity);
|
||||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
list.setLayoutManager(linearLayoutManager);
|
||||
|
||||
// Make top and bottom shadow visible when needed.
|
||||
ImageView topShadow = customView.findViewById(R.id.top_shadow);
|
||||
ImageView bottomShadow = customView.findViewById(R.id.bottom_shadow);
|
||||
list.setOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
if (recyclerView.canScrollVertically(-1)) {
|
||||
topShadow.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
topShadow.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (recyclerView.canScrollVertically(1)) {
|
||||
bottomShadow.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
bottomShadow.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mAppLanguageModal.set(ModalDialogProperties.CUSTOM_VIEW, customView);
|
||||
mModalDialogManager.showDialog(mAppLanguageModal, ModalDialogManager.ModalDialogType.APP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(PropertyModel model, int buttonType) {
|
||||
if (buttonType == ModalDialogProperties.ButtonType.NEGATIVE) {
|
||||
mModalDialogManager.dismissDialog(model, DialogDismissalCause.NEGATIVE_BUTTON_CLICKED);
|
||||
} else {
|
||||
saveAppLanguage();
|
||||
mModalDialogManager.dismissDialog(model, DialogDismissalCause.POSITIVE_BUTTON_CLICKED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(PropertyModel model, int dismissalCause) {
|
||||
if (dismissalCause == DialogDismissalCause.POSITIVE_BUTTON_CLICKED) {
|
||||
saveAppLanguage();
|
||||
}
|
||||
TranslateBridge.setAppLanguagePromptShown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ordered set of LanguageItems that should be shown at the top of the list. These
|
||||
* languages come from the user's currently location and preferred languages.
|
||||
* @param uiLanguages Collection of possible UI langauges.
|
||||
* @param currentLanguage The LanguageItem representing the current UI language.
|
||||
* @return An ordered set of LangaugeItems.
|
||||
*/
|
||||
private LinkedHashSet<LanguageItem> getTopLanguages(
|
||||
Collection<LanguageItem> uiLanguages, LanguageItem currentLanguage) {
|
||||
LinkedHashSet<String> topLanguageCodes =
|
||||
new LinkedHashSet<>(GeoLanguageProviderBridge.getCurrentGeoLanguages());
|
||||
topLanguageCodes.addAll(TranslateBridge.getUserLanguageCodes());
|
||||
LinkedHashSet<LanguageItem> topLanguages = new LinkedHashSet<>();
|
||||
topLanguages.add(currentLanguage);
|
||||
// Only add top languages that can be UI languages.
|
||||
for (LanguageItem item : uiLanguages) {
|
||||
if (topLanguageCodes.contains(item.getCode())) topLanguages.add(item);
|
||||
}
|
||||
return topLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the currently selected language as the app language. Setting the app language preference
|
||||
* will start a downloaded for the correct language split if needed.
|
||||
*/
|
||||
private void saveAppLanguage() {
|
||||
AppLocaleUtils.setAppLanguagePref(mAdapter.getSelectedLanguage().getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays starts showing the App language prompt if the experiment is enabled.
|
||||
* @param activity The current activity to display the prompt into.
|
||||
* @param modalDialogManagerSupplier Supplier of {@link ModalDialogManager}.
|
||||
* @return Whether the prompt was shown or not.
|
||||
*/
|
||||
public static boolean maybeShowPrompt(
|
||||
Activity activity, ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier) {
|
||||
if (!shouldShowPrompt()) return false;
|
||||
|
||||
AppLanguagePromoDialog prompt =
|
||||
new AppLanguagePromoDialog(activity, modalDialogManagerSupplier);
|
||||
prompt.showAppLanguageModal();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the app language prompt should be shown or not.
|
||||
*/
|
||||
private static boolean shouldShowPrompt() {
|
||||
// This switch is only used for testing so it is ok to override all other checks.
|
||||
if (ChromeFeatureList.isEnabled(ChromeFeatureList.FORCE_APP_LANGUAGE_PROMPT)) return true;
|
||||
|
||||
// Don't show the prompt if not enabled or already shown.
|
||||
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.APP_LANGUAGE_PROMPT)) return false;
|
||||
if (TranslateBridge.getAppLanguagePromptShown()) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
5
chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java
5
chrome/browser/language/android/java/src/org/chromium/chrome/browser/language/LanguageAskPrompt.java
@ -42,7 +42,8 @@ import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Implements a modal dialog that prompts the user about the languages they can read. Displayed
|
||||
* once at browser startup when no other promo or modals are shown.
|
||||
* once at browser startup when no other promo or modals are shown. Selected languages are added to
|
||||
* the user Accept-Languages.
|
||||
*/
|
||||
public class LanguageAskPrompt implements ModalDialogProperties.Controller {
|
||||
// Enum values for the Translate.ExplicitLanguageAsk.Event histogram.
|
||||
@ -245,6 +246,8 @@ public class LanguageAskPrompt implements ModalDialogProperties.Controller {
|
||||
*/
|
||||
public static boolean maybeShowLanguageAskPrompt(
|
||||
Activity activity, ObservableSupplier<ModalDialogManager> modalDialogManagerSupplier) {
|
||||
// Do not show the Accept-Language prompt if the App Language prompt was shown.
|
||||
if (TranslateBridge.getAppLanguagePromptShown()) return false;
|
||||
if (!ChromeFeatureList.isEnabled(ChromeFeatureList.EXPLICIT_LANGUAGE_ASK)) return false;
|
||||
if (TranslateBridge.getExplicitLanguageAskPromptShown()) return false;
|
||||
|
||||
|
@ -214,7 +214,7 @@ public class LanguagesManager {
|
||||
* The current Accept-Languages are added to the front of the the list.
|
||||
* @return List of LanguageItems.
|
||||
*/
|
||||
private List<LanguageItem> getPotentialUiLanguages() {
|
||||
public List<LanguageItem> getPotentialUiLanguages() {
|
||||
LinkedHashSet<LanguageItem> results = new LinkedHashSet<>();
|
||||
LanguageItem currentUiLanguage = getLanguageItem(AppLocaleUtils.getAppLanguagePref());
|
||||
|
||||
|
@ -186,6 +186,16 @@ public class FakeTranslateBridgeJni implements TranslateBridge.Natives {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getAppLanguagePromptShown() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAppLanguagePromptShown() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIgnoreMissingKeyForTesting(boolean ignore) {
|
||||
throw new UnsupportedOperationException();
|
||||
|
20
chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
20
chrome/browser/language/android/java/src/org/chromium/chrome/browser/translate/TranslateBridge.java
@ -168,8 +168,8 @@ public class TranslateBridge {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A sorted list of LanguageItems representing the Chrome accept languages with details.
|
||||
* Languages that are not supported on Android have been filtered out.
|
||||
* @return A list of LanguageItems sorted by display name that represent all languages that can
|
||||
* be on the Chrome accept languages list.
|
||||
*/
|
||||
public static List<LanguageItem> getChromeLanguageList() {
|
||||
List<LanguageItem> list = new ArrayList<>();
|
||||
@ -265,6 +265,20 @@ public class TranslateBridge {
|
||||
TranslateBridgeJni.get().setExplicitLanguageAskPromptShown(shown);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Whether the app language prompt has been shown or not.
|
||||
*/
|
||||
public static boolean getAppLanguagePromptShown() {
|
||||
return TranslateBridgeJni.get().getAppLanguagePromptShown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pref indicating the app language prompt has been shown to the user.
|
||||
*/
|
||||
public static void setAppLanguagePromptShown() {
|
||||
TranslateBridgeJni.get().setAppLanguagePromptShown();
|
||||
}
|
||||
|
||||
public static void setIgnoreMissingKeyForTesting(boolean ignore) {
|
||||
TranslateBridgeJni.get().setIgnoreMissingKeyForTesting(ignore); // IN-TEST
|
||||
}
|
||||
@ -294,6 +308,8 @@ public class TranslateBridge {
|
||||
void setLanguageBlockedState(String language, boolean blocked);
|
||||
boolean getExplicitLanguageAskPromptShown();
|
||||
void setExplicitLanguageAskPromptShown(boolean shown);
|
||||
boolean getAppLanguagePromptShown();
|
||||
void setAppLanguagePromptShown();
|
||||
void setIgnoreMissingKeyForTesting(boolean ignore);
|
||||
}
|
||||
}
|
||||
|
@ -485,6 +485,18 @@ static void JNI_TranslateBridge_SetExplicitLanguageAskPromptShown(
|
||||
translate_prefs->SetExplicitLanguageAskPromptShown(shown);
|
||||
}
|
||||
|
||||
static jboolean JNI_TranslateBridge_GetAppLanguagePromptShown(JNIEnv* env) {
|
||||
std::unique_ptr<translate::TranslatePrefs> translate_prefs =
|
||||
ChromeTranslateClient::CreateTranslatePrefs(GetPrefService());
|
||||
return translate_prefs->GetAppLanguagePromptShown();
|
||||
}
|
||||
|
||||
static void JNI_TranslateBridge_SetAppLanguagePromptShown(JNIEnv* env) {
|
||||
std::unique_ptr<translate::TranslatePrefs> translate_prefs =
|
||||
ChromeTranslateClient::CreateTranslatePrefs(GetPrefService());
|
||||
translate_prefs->SetAppLanguagePromptShown();
|
||||
}
|
||||
|
||||
static void JNI_TranslateBridge_SetIgnoreMissingKeyForTesting( // IN-TEST
|
||||
JNIEnv* env,
|
||||
jboolean ignore) {
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/values.h"
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
#include "components/language/core/browser/pref_names.h"
|
||||
#include "components/language/core/common/language_util.h"
|
||||
@ -51,6 +52,11 @@ void LanguagePrefs::RegisterProfilePrefs(
|
||||
registry->RegisterListPref(language::prefs::kFluentLanguages,
|
||||
LanguagePrefs::GetDefaultFluentLanguages(),
|
||||
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
|
||||
#if defined(OS_ANDROID)
|
||||
registry->RegisterBooleanPref(
|
||||
language::prefs::kAppLanguagePromptShown, false,
|
||||
user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
|
||||
#endif
|
||||
}
|
||||
|
||||
LanguagePrefs::LanguagePrefs(PrefService* user_prefs) : prefs_(user_prefs) {
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "components/language/core/browser/pref_names.h"
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
|
||||
namespace language {
|
||||
@ -40,5 +41,9 @@ const char kApplicationLocale[] = "intl.app_locale";
|
||||
// Originally translate blocked languages from TranslatePrefs.
|
||||
const char kFluentLanguages[] = "translate_blocked_languages";
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
const char kAppLanguagePromptShown[] = "language.app_language_prompt_shown";
|
||||
#endif
|
||||
|
||||
} // namespace prefs
|
||||
} // namespace language
|
||||
|
@ -5,6 +5,7 @@
|
||||
#ifndef COMPONENTS_LANGUAGE_CORE_BROWSER_PREF_NAMES_H_
|
||||
#define COMPONENTS_LANGUAGE_CORE_BROWSER_PREF_NAMES_H_
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "build/chromeos_buildflags.h"
|
||||
|
||||
namespace language {
|
||||
@ -31,6 +32,10 @@ extern const char kApplicationLocale[];
|
||||
|
||||
extern const char kFluentLanguages[];
|
||||
|
||||
#if defined(OS_ANDROID)
|
||||
extern const char kAppLanguagePromptShown[];
|
||||
#endif
|
||||
|
||||
} // namespace prefs
|
||||
} // namespace language
|
||||
|
||||
|
@ -20,6 +20,10 @@ const base::Feature kOverrideTranslateTriggerInIndia{
|
||||
"OverrideTranslateTriggerInIndia", base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
const base::Feature kExplicitLanguageAsk{"ExplicitLanguageAsk",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
const base::Feature kAppLanguagePrompt{"AppLanguagePrompt",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
const base::Feature kForceAppLanguagePrompt{"ForceAppLanguagePrompt",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT};
|
||||
const base::Feature kUseFluentLanguageModel {
|
||||
"UseFluentLanguageModel",
|
||||
#if defined(OS_IOS)
|
||||
|
@ -13,9 +13,14 @@ namespace language {
|
||||
// the baseline model is used instead.
|
||||
extern const base::Feature kUseHeuristicLanguageModel;
|
||||
|
||||
// The feature that enables explicitly asking for user preferences on startup on
|
||||
// Android.
|
||||
// The feature that enables explicitly asking for user preferred
|
||||
// Accept-Languages on second run on Android. Replaced by kAppLanguagePrompt.
|
||||
extern const base::Feature kExplicitLanguageAsk;
|
||||
// The feature that enables a second run prompt to select the app UI language on
|
||||
// Android.
|
||||
extern const base::Feature kAppLanguagePrompt;
|
||||
// This feature forces the app UI prompt even if it has already been shown.
|
||||
extern const base::Feature kForceAppLanguagePrompt;
|
||||
|
||||
// This feature controls the activation of the experiment to trigger Translate
|
||||
// in India on English pages independent of the user's UI language. The params
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include "base/values.h"
|
||||
#include "build/build_config.h"
|
||||
#include "components/language/core/browser/language_prefs.h"
|
||||
#include "components/language/core/browser/pref_names.h"
|
||||
#include "components/language/core/common/language_experiments.h"
|
||||
#include "components/language/core/common/language_util.h"
|
||||
#include "components/language/core/common/locale_util.h"
|
||||
@ -722,6 +723,14 @@ bool TranslatePrefs::GetExplicitLanguageAskPromptShown() const {
|
||||
void TranslatePrefs::SetExplicitLanguageAskPromptShown(bool shown) {
|
||||
prefs_->SetBoolean(kPrefExplicitLanguageAskShown, shown);
|
||||
}
|
||||
|
||||
bool TranslatePrefs::GetAppLanguagePromptShown() const {
|
||||
return prefs_->GetBoolean(language::prefs::kAppLanguagePromptShown);
|
||||
}
|
||||
|
||||
void TranslatePrefs::SetAppLanguagePromptShown() {
|
||||
prefs_->SetBoolean(language::prefs::kAppLanguagePromptShown, true);
|
||||
}
|
||||
#endif // defined(OS_ANDROID)
|
||||
|
||||
void TranslatePrefs::GetLanguageList(
|
||||
|
@ -276,6 +276,11 @@ class TranslatePrefs {
|
||||
// prompt was displayed to the user already.
|
||||
bool GetExplicitLanguageAskPromptShown() const;
|
||||
void SetExplicitLanguageAskPromptShown(bool shown);
|
||||
|
||||
// These methods are used to determine whether the app language prompt was
|
||||
// displayed to the user already. Once shown it can not be unset.
|
||||
bool GetAppLanguagePromptShown() const;
|
||||
void SetAppLanguagePromptShown();
|
||||
#endif
|
||||
|
||||
// Gets the full (policy-forced and user selected) language list from language
|
||||
|
@ -45367,6 +45367,7 @@ from previous Chrome versions.
|
||||
<int value="-1740519217" label="disable-software-rasterizer"/>
|
||||
<int value="-1740093155" label="UnifiedMediaView:disabled"/>
|
||||
<int value="-1738416948" label="OptimizationHints:enabled"/>
|
||||
<int value="-1738015645" label="ForceAppLanguagePrompt:disabled"/>
|
||||
<int value="-1737769448" label="WebUITabStripTabDragIntegration:disabled"/>
|
||||
<int value="-1736075054" label="EnableFullscreenAppList:enabled"/>
|
||||
<int value="-1735643253" label="enable-display-list-2d-canvas"/>
|
||||
@ -49281,6 +49282,7 @@ from previous Chrome versions.
|
||||
<int value="1612446645" label="enable-weak-memorycache"/>
|
||||
<int value="1612871297" label="WebPayments:disabled"/>
|
||||
<int value="1612974229" label="allow-insecure-localhost"/>
|
||||
<int value="1613738524" label="ForceAppLanguagePrompt:enabled"/>
|
||||
<int value="1614309501" label="DataSaverLiteModeRebranding:enabled"/>
|
||||
<int value="1614596813" label="CloseButtonsInactiveTabs:disabled"/>
|
||||
<int value="1615988672" label="GrantNotificationsToDSE:enabled"/>
|
||||
|
Reference in New Issue
Block a user