Implement multiple locales and sublocales in Settings
Does drag-and-drop reordering, supports adding / removing locales,
suggestions, search, removes locales already selected in the
user preferences.
Bug: 25800339
Change-Id: Iffe7b9810c77ec93a84d848ab20b2ba405249676
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3281ade..ad783d3 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -567,7 +567,7 @@
<category android:name="android.intent.category.VOICE_LAUNCH" />
</intent-filter>
<meta-data android:name="com.android.settings.FRAGMENT_CLASS"
- android:value="com.android.settings.LocalePicker" />
+ android:value="com.android.settings.localepicker.LocaleListEditor" />
<meta-data android:name="com.android.settings.PRIMARY_PROFILE_CONTROLLED"
android:value="true" />
</activity>
diff --git a/res/drawable/drag_handle.xml b/res/drawable/drag_handle.xml
new file mode 100644
index 0000000..c01ee1c
--- /dev/null
+++ b/res/drawable/drag_handle.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="50dp"
+ android:width="50dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100"
+ android:tint="#757575" >
+
+ <path android:fillColor="@android:color/white"
+ android:pathData="M29,48 l 0,-4 42,0 0,4 z" />
+ <path android:fillColor="@android:color/white"
+ android:pathData="M29,56 l 0,-4 42,0 0,4 z" />
+
+</vector>
diff --git a/res/layout/locale_drag_cell.xml b/res/layout/locale_drag_cell.xml
new file mode 100644
index 0000000..978e0ad
--- /dev/null
+++ b/res/layout/locale_drag_cell.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.settings.localepicker.LocaleDragCell
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:baselineAligned="false"
+ android:layoutDirection="locale"
+ android:textDirection="locale">
+
+ <TextView
+ android:id="@+id/miniLabel"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="12dp"
+ android:layout_marginRight="12dp"
+ tools:text="22"
+ android:textColor="?android:attr/colorAccent"
+ android:minWidth="20sp"
+ android:textAlignment="textEnd"
+ android:gravity="center_vertical|end"
+ android:background="?android:colorBackground"/>
+
+ <CheckBox
+ android:id="@+id/checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="20sp"
+ android:layout_marginLeft="6dp"
+ android:layout_marginRight="6dp"
+ android:visibility="gone"
+ android:background="?android:colorBackground"/>
+
+ <TextView
+ android:id="@+id/label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:padding="12dp"
+ tools:text="France"
+ android:textAppearance="?android:attr/textAppearanceListItem"
+ android:background="?android:colorBackground"/>
+
+ <ImageView
+ android:id="@+id/l10nWarn"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:src="@android:drawable/stat_sys_warning"
+ android:focusableInTouchMode="false"
+ android:focusable="false"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp"
+ android:tint="?android:attr/colorAccent"/>
+
+ <ImageView
+ android:id="@+id/dragHandle"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:src="@drawable/drag_handle"/>
+
+</com.android.settings.localepicker.LocaleDragCell>
diff --git a/res/layout/locale_order_list.xml b/res/layout/locale_order_list.xml
new file mode 100644
index 0000000..97ba9a3
--- /dev/null
+++ b/res/layout/locale_order_list.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutDirection="locale"
+ android:orientation="vertical"
+ android:textDirection="locale">
+
+ <com.android.settings.localepicker.LocaleRecyclerView
+ android:id="@+id/dragList"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"/>
+
+ <TextView
+ android:id="@+id/add_language"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:contextClickable="true"
+ android:drawableStart="@drawable/ic_add_24dp"
+ android:drawablePadding="12dp"
+ android:gravity="center_vertical"
+ android:measureWithLargestChild="false"
+ android:padding="16dp"
+ android:text="@string/add_a_language"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index db68d71..7a5d768 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -513,8 +513,27 @@
<string name="next_label">Next</string>
<!-- LocalePicker -->
- <!-- Title for the locale picker activity -->
- <string name="language_picker_title">Language</string>
+
+ <!-- Title for the language selection screen [CHAR LIMIT=25] -->
+ <string name="pref_title_lang_selection">Language preference</string>
+
+ <!-- Menu item in the locale menu. Will remove the selected locales. [CHAR LIMIT=30] -->
+ <string name="locale_remove_menu">Remove</string>
+
+ <!-- "Button" that opens a language picker. The selected language gets added to the language list. [CHAR LIMIT=30] -->
+ <string name="add_a_language">Add a language</string>
+
+ <!-- The title of the confirmation dialog shown when the user selected several languages and tries to remove them [CHAR LIMIT=60] -->
+ <string name="dlg_remove_locales_title">Remove from your language preference?</string>
+ <!-- TODO: come up with a good string -->
+ <!-- The text of the confirmation dialog shown when the user selected several languages and tries to remove them [CHAR LIMIT=NONE] -->
+ <string name="dlg_remove_locales_message" translatable="false">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla egestas porta aliquet. Ut laoreet orci tellus, id cursus enim volutpat in.</string>
+
+ <!-- The title of the error dialog shown when the user selected all the languages and tries to remove them [CHAR LIMIT=60] -->
+ <string name="dlg_remove_locales_error_title">Language removal error</string>
+ <!-- The text of the error dialog shown when the user selected all the languages and tries to remove them [CHAR LIMIT=NONE] -->
+ <string name="dlg_remove_locales_error_message">Can’t remove all the languages, you should leave at least one.</string>
+
<!-- The title of the dialog to pick an activity. This is shown when there are multiple activities that can do a particular action. For example, suppose you click on the "Share" menu item in the Browser. Since you can share the webpage URL via many communication methods, this dialog would come up with choices like "Email", "IM", etc. This is a generic message, and the previous example is a single possible scenario (so please don't assume it's for the browser or anything :) ). -->
<string name="activity_picker_label">Choose activity</string>
diff --git a/res/xml/language_settings.xml b/res/xml/language_settings.xml
index fa96a56..83a0985 100644
--- a/res/xml/language_settings.xml
+++ b/res/xml/language_settings.xml
@@ -4,9 +4,9 @@
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
-
+
http://www.apache.org/licenses/LICENSE-2.0
-
+
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -22,7 +22,7 @@
<PreferenceScreen
android:key="phone_language"
android:title="@string/phone_language"
- android:fragment="com.android.settings.LocalePicker"
+ android:fragment="com.android.settings.localepicker.LocaleListEditor"
/>
<!-- Spell checker preference title, summary and fragment will be set programmatically. -->
diff --git a/src/com/android/settings/InstrumentedFragment.java b/src/com/android/settings/InstrumentedFragment.java
index 58b8eb0..bb2f948 100644
--- a/src/com/android/settings/InstrumentedFragment.java
+++ b/src/com/android/settings/InstrumentedFragment.java
@@ -37,6 +37,7 @@
public static final int DATA_USAGE_LIST = UNDECLARED + 7;
public static final int BILLING_CYCLE = UNDECLARED + 8;
public static final int APP_DATA_USAGE = UNDECLARED + 9;
+ public static final int USER_LOCALE_LIST = UNDECLARED + 10;
/**
* Declare the view of this category.
diff --git a/src/com/android/settings/LocalePicker.java b/src/com/android/settings/LocalePicker.java
deleted file mode 100644
index 950b79c..0000000
--- a/src/com/android/settings/LocalePicker.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settings;
-
-import android.app.Dialog;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ListView;
-
-import com.android.settings.SettingsPreferenceFragment.SettingsDialogFragment;
-
-import java.util.Locale;
-
-public class LocalePicker extends com.android.internal.app.LocalePickerWithRegion
- implements com.android.internal.app.LocalePickerWithRegion.LocaleSelectionListener,
- DialogCreatable {
-
- private static final String TAG = "LocalePicker";
-
- private SettingsDialogFragment mDialogFragment;
- private static final int DLG_SHOW_GLOBAL_WARNING = 1;
- private static final String SAVE_TARGET_LOCALE = "locale";
-
- private Locale mTargetLocale;
-
- public LocalePicker() {
- super();
- setLocaleSelectionListener(this);
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (savedInstanceState != null && savedInstanceState.containsKey(SAVE_TARGET_LOCALE)) {
- mTargetLocale = new Locale(savedInstanceState.getString(SAVE_TARGET_LOCALE));
- }
- }
-
- @Override
- public View onCreateView(
- LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- final View view = super.onCreateView(inflater, container, savedInstanceState);
- final ListView list = (ListView) view.findViewById(android.R.id.list);
- Utils.forcePrepareCustomPreferencesList(container, view, list, false);
- return view;
- }
-
- @Override
- public void onLocaleSelected(final Locale locale) {
- if (Utils.hasMultipleUsers(getActivity())) {
- mTargetLocale = locale;
- showDialog(DLG_SHOW_GLOBAL_WARNING);
- } else {
- getActivity().onBackPressed();
- com.android.internal.app.LocalePicker.updateLocale(locale);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
-
- if (mTargetLocale != null) {
- outState.putString(SAVE_TARGET_LOCALE, mTargetLocale.toString());
- }
- }
-
- protected void showDialog(int dialogId) {
- if (mDialogFragment != null) {
- Log.e(TAG, "Old dialog fragment not null!");
- }
- mDialogFragment = new SettingsDialogFragment(this, dialogId);
- mDialogFragment.show(getActivity().getFragmentManager(), Integer.toString(dialogId));
- }
-
- public Dialog onCreateDialog(final int dialogId) {
- return Utils.buildGlobalChangeWarningDialog(getActivity(),
- R.string.global_locale_change_title,
- new Runnable() {
- public void run() {
- removeDialog(dialogId);
- getActivity().onBackPressed();
- com.android.internal.app.LocalePicker.updateLocale(mTargetLocale);
- }
- }
- );
- }
-
- protected void removeDialog(int dialogId) {
- // mDialogFragment may not be visible yet in parent fragment's onResume().
- // To be able to dismiss dialog at that time, don't check
- // mDialogFragment.isVisible().
- if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
- mDialogFragment.dismiss();
- }
- mDialogFragment = null;
- }
-}
diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java
index dab2cb7..88cfc74 100644
--- a/src/com/android/settings/SettingsActivity.java
+++ b/src/com/android/settings/SettingsActivity.java
@@ -80,6 +80,7 @@
import com.android.settings.inputmethod.KeyboardLayoutPickerFragment;
import com.android.settings.inputmethod.SpellCheckersSettings;
import com.android.settings.inputmethod.UserDictionaryList;
+import com.android.settings.localepicker.LocaleListEditor;
import com.android.settings.location.LocationSettings;
import com.android.settings.nfc.AndroidBeam;
import com.android.settings.nfc.PaymentSettings;
@@ -246,7 +247,7 @@
WifiP2pSettings.class.getName(),
VpnSettings.class.getName(),
DateTimeSettings.class.getName(),
- LocalePicker.class.getName(),
+ LocaleListEditor.class.getName(),
InputMethodAndLanguageSettings.class.getName(),
SpellCheckersSettings.class.getName(),
UserDictionaryList.class.getName(),
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index aa2d68a..9022538 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -43,6 +43,7 @@
import android.support.v7.preference.PreferenceManager;
import android.support.v7.preference.PreferenceScreen;
import android.text.TextUtils;
+import android.util.LocaleList;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
@@ -50,6 +51,7 @@
import android.view.textservice.SpellCheckerInfo;
import android.view.textservice.TextServicesManager;
+import com.android.internal.app.LocaleHelper;
import com.android.internal.app.LocalePicker;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.R;
@@ -266,8 +268,8 @@
if (!mShowsOnlyFullImeAndKeyboardList) {
if (mLanguagePref != null) {
- String localeName = getLocaleName(getActivity());
- mLanguagePref.setSummary(localeName);
+ String localeNames = getLocaleNames(getActivity());
+ mLanguagePref.setSummary(localeNames);
}
updateUserDictionaryPreference(findPreference(KEY_USER_DICTIONARY_SETTINGS));
@@ -340,19 +342,9 @@
return super.onPreferenceTreeClick(preference);
}
- private static String getLocaleName(Context context) {
- // We want to show the same string that the LocalePicker used.
- // TODO: should this method be in LocalePicker instead?
- Locale currentLocale = context.getResources().getConfiguration().locale;
- List<LocalePicker.LocaleInfo> locales = LocalePicker.getAllAssetLocales(context, true);
- for (LocalePicker.LocaleInfo locale : locales) {
- if (locale.getLocale().equals(currentLocale)) {
- return locale.getLabel();
- }
- }
- // This can't happen as long as the locale was one set by Settings.
- // Fall back in case a developer is testing an unsupported locale.
- return currentLocale.getDisplayName(currentLocale);
+ private static String getLocaleNames(Context context) {
+ final LocaleList locales = LocalePicker.getLocales();
+ return LocaleHelper.getDisplayLocaleList(locales, Locale.getDefault());
}
private void saveInputMethodSelectorVisibility(String value) {
@@ -665,8 +657,8 @@
@Override
public void setListening(boolean listening) {
if (listening) {
- String localeName = getLocaleName(mContext);
- mSummaryLoader.setSummary(this, localeName);
+ String localeNames = getLocaleNames(mContext);
+ mSummaryLoader.setSummary(this, localeNames);
}
}
}
@@ -690,12 +682,12 @@
// Locale picker.
if (context.getAssets().getLocales().length > 1) {
- String localeName = getLocaleName(context);
+ String localeNames = getLocaleNames(context);
SearchIndexableRaw indexable = new SearchIndexableRaw(context);
indexable.key = KEY_PHONE_LANGUAGE;
indexable.title = context.getString(R.string.phone_language);
- indexable.summaryOn = localeName;
- indexable.summaryOff = localeName;
+ indexable.summaryOn = localeNames;
+ indexable.summaryOff = localeNames;
indexable.screenTitle = screenTitle;
indexables.add(indexable);
}
diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
new file mode 100644
index 0000000..b1f68b9
--- /dev/null
+++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.localepicker;
+
+import android.content.Context;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.LocaleList;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CompoundButton;
+
+import com.android.settings.R;
+
+import com.android.internal.app.LocalePicker;
+import com.android.internal.app.LocaleStore;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+
+class LocaleDragAndDropAdapter
+ extends RecyclerView.Adapter<LocaleDragAndDropAdapter.CustomViewHolder> {
+
+ private final Context mContext;
+ private final List<LocaleStore.LocaleInfo> mFeedItemList;
+ private final ItemTouchHelper mItemTouchHelper;
+ private boolean mRemoveMode = false;
+ private boolean mDragEnabled = true;
+
+ class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnTouchListener {
+ private final LocaleDragCell mLocaleDragCell;
+
+ public CustomViewHolder(LocaleDragCell view) {
+ super(view);
+ mLocaleDragCell = view;
+ mLocaleDragCell.getDragHandle().setOnTouchListener(this);
+ mLocaleDragCell.getTextLabel().setOnTouchListener(this);
+ mLocaleDragCell.getTranslateableLabel().setOnTouchListener(this);
+ }
+
+ public LocaleDragCell getLocaleDragCell() {
+ return mLocaleDragCell;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mDragEnabled) {
+ switch (MotionEventCompat.getActionMasked(event)) {
+ case MotionEvent.ACTION_DOWN:
+ mItemTouchHelper.startDrag(this);
+ }
+ }
+ return false;
+ }
+ }
+
+ public LocaleDragAndDropAdapter(Context context, List<LocaleStore.LocaleInfo> feedItemList) {
+ this.mFeedItemList = feedItemList;
+
+ this.mContext = context;
+ this.mItemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
+ ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0 /* no swipe */) {
+
+ @Override
+ public boolean onMove(RecyclerView view, RecyclerView.ViewHolder source,
+ RecyclerView.ViewHolder target) {
+ onItemMove(source.getAdapterPosition(), target.getAdapterPosition());
+ return true;
+ }
+
+ @Override
+ public void onSwiped(RecyclerView.ViewHolder viewHolder, int i) {
+ // Swipe is disabled, this is intentionally empty.
+ }
+ });
+ }
+
+ public void setRecyclerView(RecyclerView rv) {
+ mItemTouchHelper.attachToRecyclerView(rv);
+ }
+
+ @Override
+ public CustomViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
+ final LocaleDragCell item = (LocaleDragCell) LayoutInflater.from(mContext)
+ .inflate(R.layout.locale_drag_cell, viewGroup, false);
+ return new CustomViewHolder(item);
+ }
+
+ @Override
+ public void onBindViewHolder(final CustomViewHolder holder, int i) {
+ final LocaleStore.LocaleInfo feedItem = mFeedItemList.get(i);
+ final LocaleDragCell dragCell = holder.getLocaleDragCell();
+
+ String label = feedItem.getFullNameNative();
+ dragCell.setLabel(label);
+ dragCell.setLocalized(feedItem.isTranslated());
+ dragCell.setMiniLabel(Integer.toString(i + 1));
+ dragCell.setShowCheckbox(mRemoveMode);
+ dragCell.setShowMiniLabel(!mRemoveMode);
+ dragCell.setShowHandle(!mRemoveMode);
+ dragCell.setChecked(false);
+ dragCell.setTag(feedItem);
+ dragCell.getCheckbox()
+ .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ LocaleStore.LocaleInfo feedItem =
+ (LocaleStore.LocaleInfo) holder.getLocaleDragCell().getTag();
+ feedItem.setChecked(isChecked);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ int itemCount = (null != mFeedItemList ? mFeedItemList.size() : 0);
+ if (itemCount < 2 || mRemoveMode) {
+ setDragEnabled(false);
+ } else {
+ setDragEnabled(true);
+ }
+ return itemCount;
+ }
+
+ private void onItemMove(int fromPosition, int toPosition) {
+ Collections.swap(mFeedItemList, fromPosition, toPosition);
+ notifyItemChanged(fromPosition); // to update the numbers
+ notifyItemChanged(toPosition);
+ notifyItemMoved(fromPosition, toPosition);
+ }
+
+ void setRemoveMode(boolean removeMode) {
+ mRemoveMode = removeMode;
+ int itemCount = mFeedItemList.size();
+ for (int i = 0; i < itemCount; i++) {
+ mFeedItemList.get(i).setChecked(false);
+ notifyItemChanged(i);
+ }
+ }
+
+ void removeChecked() {
+ int itemCount = mFeedItemList.size();
+ for (int i = itemCount - 1; i >= 0; i--) {
+ if (mFeedItemList.get(i).getChecked()) {
+ mFeedItemList.remove(i);
+ }
+ }
+ notifyDataSetChanged();
+ doTheUpdate();
+ }
+
+ int getCheckedCount() {
+ int result = 0;
+ for (LocaleStore.LocaleInfo li : mFeedItemList) {
+ if (li.getChecked()) {
+ result++;
+ }
+ }
+ return result;
+ }
+
+ void addLocale(LocaleStore.LocaleInfo li) {
+ mFeedItemList.add(li);
+ notifyItemInserted(mFeedItemList.size() - 1);
+ doTheUpdate();
+ }
+
+ public void doTheUpdate() {
+ int count = mFeedItemList.size();
+ Locale[] newList = new Locale[count];
+
+ for (int i = 0; i < count; i++) {
+ LocaleStore.LocaleInfo li = mFeedItemList.get(i);
+ newList[i] = li.getLocale();
+ }
+
+ LocaleList ll = new LocaleList(newList);
+ LocalePicker.updateLocales(ll);
+ }
+
+ private void setDragEnabled(boolean enabled) {
+ mDragEnabled = enabled;
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleDragCell.java b/src/com/android/settings/localepicker/LocaleDragCell.java
new file mode 100644
index 0000000..8b1fa3c
--- /dev/null
+++ b/src/com/android/settings/localepicker/LocaleDragCell.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.localepicker;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.settings.R;
+
+class LocaleDragCell extends LinearLayout {
+ private TextView mLabel;
+ private TextView mMiniLabel;
+ private ImageView mLocalized;
+ private CheckBox mCheckbox;
+ private ImageView mDragHandle;
+
+ public LocaleDragCell(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mLabel = (TextView) findViewById(R.id.label);
+ mLocalized = (ImageView) findViewById(R.id.l10nWarn);
+ mMiniLabel = (TextView) findViewById(R.id.miniLabel);
+ mCheckbox = (CheckBox) findViewById(R.id.checkbox);
+ mDragHandle = (ImageView) findViewById(R.id.dragHandle);
+ }
+
+ public void setShowHandle(boolean showHandle) {
+ mDragHandle.setVisibility(showHandle ? VISIBLE : GONE);
+ invalidate();
+ requestLayout();
+ }
+
+ public void setShowCheckbox(boolean showCheckbox) {
+ mCheckbox.setVisibility(showCheckbox ? VISIBLE : GONE);
+ invalidate();
+ requestLayout();
+ }
+
+ public void setChecked(boolean checked) {
+ mCheckbox.setChecked(checked);
+ }
+
+ public void setShowMiniLabel(boolean showMiniLabel) {
+ mMiniLabel.setVisibility(showMiniLabel ? VISIBLE : GONE);
+ invalidate();
+ requestLayout();
+ }
+
+ public void setMiniLabel(String miniLabelText) {
+ mMiniLabel.setText(miniLabelText);
+ invalidate();
+ }
+
+ public void setLabel(String labelText) {
+ mLabel.setText(labelText);
+ invalidate();
+ }
+
+ public void setLocalized(boolean localized) {
+ mLocalized.setVisibility(localized ? INVISIBLE : VISIBLE);
+ invalidate();
+ }
+
+ public ImageView getDragHandle() {
+ return mDragHandle;
+ }
+
+ public ImageView getTranslateableLabel() {
+ return mLocalized;
+ }
+
+ public TextView getTextLabel() {
+ return mLabel;
+ }
+
+ public CheckBox getCheckbox() {
+ return mCheckbox;
+ }
+}
diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java
new file mode 100644
index 0000000..24e5e53
--- /dev/null
+++ b/src/com/android/settings/localepicker/LocaleListEditor.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.localepicker;
+
+import android.app.AlertDialog;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.LocaleList;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import com.android.settings.R;
+
+import com.android.internal.app.LocalePicker;
+import com.android.internal.app.LocalePickerWithRegion;
+import com.android.internal.app.LocaleStore;
+import com.android.settings.InstrumentedFragment;
+import com.android.settings.SettingsPreferenceFragment;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Drag-and-drop editor for the user-ordered locale lists.
+ */
+public class LocaleListEditor extends SettingsPreferenceFragment
+ implements LocalePickerWithRegion.LocaleSelectedListener {
+
+ private static final int MENU_ID_REMOVE = Menu.FIRST + 1;
+
+ private LocaleDragAndDropAdapter mAdapter;
+ private Menu mMenu;
+ private boolean mRemoveMode;
+ private View mAddLanguage;
+
+ @Override
+ protected int getMetricsCategory() {
+ return InstrumentedFragment.USER_LOCALE_LIST;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+
+ LocaleStore.fillCache(this.getContext());
+ List<LocaleStore.LocaleInfo> feedsList = getUserLocaleList(this.getContext());
+ mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) {
+ View result = super.onCreateView(inflater, container, savedInstState);
+ LinearLayout ll = (LinearLayout) result;
+ View myLayout = inflater.inflate(R.layout.locale_order_list, ll);
+
+ getActivity().setTitle(R.string.pref_title_lang_selection);
+
+ configureDragAndDrop(myLayout);
+ return result;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ if (menuItem.getItemId() == MENU_ID_REMOVE) {
+ if (mRemoveMode) {
+ removeLocaleWarningDialog();
+ } else {
+ setRemoveMode(true);
+ }
+ return true;
+ }
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ private void setRemoveMode(boolean mRemoveMode) {
+ this.mRemoveMode = mRemoveMode;
+ mAdapter.setRemoveMode(mRemoveMode);
+ mMenu.findItem(MENU_ID_REMOVE).setShowAsAction(
+ mRemoveMode ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
+ mAddLanguage.setVisibility(mRemoveMode ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ private void removeLocaleWarningDialog() {
+ int checked = mAdapter.getCheckedCount();
+
+ // Nothing checked, just exit remove mode without a warning dialog
+ if (checked == 0) {
+ setRemoveMode(!mRemoveMode);
+ return;
+ }
+
+ // All locales selected, warning dialog, can't remove them all
+ if (checked == mAdapter.getItemCount()) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.dlg_remove_locales_error_title)
+ .setMessage(R.string.dlg_remove_locales_error_message)
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ })
+ .create()
+ .show();
+ return;
+ }
+
+ new AlertDialog.Builder(getActivity())
+ .setTitle(R.string.dlg_remove_locales_title)
+ .setMessage(R.string.dlg_remove_locales_message)
+ .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ setRemoveMode(!mRemoveMode);
+ }
+ })
+ .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAdapter.removeChecked();
+ setRemoveMode(!mRemoveMode);
+ }
+ })
+ .create()
+ .show();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ final MenuItem menuItem =
+ menu.add(Menu.NONE, MENU_ID_REMOVE, 0, R.string.locale_remove_menu);
+ menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ menuItem.setIcon(android.R.drawable.ic_menu_delete);
+ super.onCreateOptionsMenu(menu, inflater);
+ mMenu = menu;
+ }
+
+ private static List<LocaleStore.LocaleInfo> getUserLocaleList(Context context) {
+ List<LocaleStore.LocaleInfo> result = new ArrayList<>();
+
+ LocaleList localeList = LocalePicker.getLocales();
+ for (int i = 0; i < localeList.size(); i++) {
+ Locale locale = localeList.get(i);
+ result.add(LocaleStore.getLocaleInfo(locale));
+ }
+
+ return result;
+ }
+
+ private void configureDragAndDrop(View view) {
+ RecyclerView list = (RecyclerView) view.findViewById(R.id.dragList);
+ list.setLayoutManager(new LinearLayoutManager(this.getContext()));
+
+ list.setHasFixedSize(true);
+ mAdapter.setRecyclerView(list);
+ list.setAdapter(mAdapter);
+
+ mAddLanguage = view.findViewById(R.id.add_language);
+ mAddLanguage.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker(
+ getContext(), LocaleListEditor.this, false /* translate only */);
+ getFragmentManager()
+ .beginTransaction()
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .replace(getId(), selector)
+ .addToBackStack("localeListEditor")
+ .commit();
+ }
+ });
+ }
+
+ @Override
+ public void onLocaleSelected(LocaleStore.LocaleInfo locale) {
+ mAdapter.addLocale(locale);
+ }
+
+}
diff --git a/src/com/android/settings/localepicker/LocaleRecyclerView.java b/src/com/android/settings/localepicker/LocaleRecyclerView.java
new file mode 100644
index 0000000..b9c2b96
--- /dev/null
+++ b/src/com/android/settings/localepicker/LocaleRecyclerView.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.localepicker;
+
+import android.content.Context;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+class LocaleRecyclerView extends RecyclerView {
+ public LocaleRecyclerView(Context context) {
+ super(context);
+ }
+
+ public LocaleRecyclerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LocaleRecyclerView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent e) {
+ if (e.getAction() == MotionEvent.ACTION_UP) {
+ LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this.getAdapter();
+ if (adapter != null) {
+ adapter.doTheUpdate();
+ }
+ }
+ return super.onTouchEvent(e);
+ }
+}