1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.localepicker;
18 
19 import android.app.AlertDialog;
20 import android.app.FragmentTransaction;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.os.Bundle;
24 import android.os.LocaleList;
25 import android.support.v7.widget.RecyclerView;
26 import android.view.LayoutInflater;
27 import android.view.Menu;
28 import android.view.MenuInflater;
29 import android.view.MenuItem;
30 import android.view.View;
31 import android.view.ViewGroup;
32 
33 import com.android.internal.app.LocalePicker;
34 import com.android.internal.app.LocalePickerWithRegion;
35 import com.android.internal.app.LocaleStore;
36 import com.android.internal.logging.MetricsProto.MetricsEvent;
37 import com.android.settings.R;
38 import com.android.settings.SettingsPreferenceFragment;
39 
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Locale;
43 
44 /**
45  * Drag-and-drop editor for the user-ordered locale lists.
46  */
47 public class LocaleListEditor extends SettingsPreferenceFragment
48         implements LocalePickerWithRegion.LocaleSelectedListener {
49 
50     private static final String CFGKEY_REMOVE_MODE = "localeRemoveMode";
51     private static final String CFGKEY_REMOVE_DIALOG = "showingLocaleRemoveDialog";
52     private static final int MENU_ID_REMOVE = Menu.FIRST + 1;
53 
54     private LocaleDragAndDropAdapter mAdapter;
55     private Menu mMenu;
56     private View mAddLanguage;
57     private boolean mRemoveMode;
58     private boolean mShowingRemoveDialog;
59 
60     @Override
getMetricsCategory()61     protected int getMetricsCategory() {
62         return MetricsEvent.USER_LOCALE_LIST;
63     }
64 
65     @Override
onCreate(Bundle savedInstanceState)66     public void onCreate(Bundle savedInstanceState) {
67         super.onCreate(savedInstanceState);
68         setHasOptionsMenu(true);
69 
70         LocaleStore.fillCache(this.getContext());
71         final List<LocaleStore.LocaleInfo> feedsList = getUserLocaleList(this.getContext());
72         mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList);
73     }
74 
75     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState)76     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) {
77         final View result = super.onCreateView(inflater, container, savedInstState);
78         final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result);
79 
80         getActivity().setTitle(R.string.pref_title_lang_selection);
81 
82         configureDragAndDrop(myLayout);
83         return result;
84     }
85 
86     @Override
onViewStateRestored(Bundle savedInstanceState)87     public void onViewStateRestored(Bundle savedInstanceState) {
88         super.onViewStateRestored(savedInstanceState);
89         if (savedInstanceState != null) {
90             mRemoveMode = savedInstanceState.getBoolean(CFGKEY_REMOVE_MODE, false);
91             mShowingRemoveDialog = savedInstanceState.getBoolean(CFGKEY_REMOVE_DIALOG, false);
92         }
93         setRemoveMode(mRemoveMode);
94         mAdapter.restoreState(savedInstanceState);
95 
96         if (mShowingRemoveDialog) {
97             showRemoveLocaleWarningDialog();
98         }
99     }
100 
101     @Override
onSaveInstanceState(Bundle outState)102     public void onSaveInstanceState(Bundle outState) {
103         super.onSaveInstanceState(outState);
104         outState.putBoolean(CFGKEY_REMOVE_MODE, mRemoveMode);
105         outState.putBoolean(CFGKEY_REMOVE_DIALOG, mShowingRemoveDialog);
106         mAdapter.saveState(outState);
107     }
108 
109     @Override
onOptionsItemSelected(MenuItem menuItem)110     public boolean onOptionsItemSelected(MenuItem menuItem) {
111         switch (menuItem.getItemId()) {
112             case MENU_ID_REMOVE:
113                 if (mRemoveMode) {
114                     showRemoveLocaleWarningDialog();
115                 } else {
116                     setRemoveMode(true);
117                 }
118                 return true;
119             case android.R.id.home:
120                 if (mRemoveMode) {
121                     setRemoveMode(false);
122                     return true;
123                 }
124                 break;
125         }
126         return super.onOptionsItemSelected(menuItem);
127     }
128 
setRemoveMode(boolean mRemoveMode)129     private void setRemoveMode(boolean mRemoveMode) {
130         this.mRemoveMode = mRemoveMode;
131         mAdapter.setRemoveMode(mRemoveMode);
132         mAddLanguage.setVisibility(mRemoveMode ? View.INVISIBLE : View.VISIBLE);
133         updateVisibilityOfRemoveMenu();
134     }
135 
136     // Show the appropriate warning when the user tries to remove locales.
137     // Shows no warning if there is no locale checked, shows a warning
138     // about removing all the locales if all of them are checked, and
139     // a "regular" warning otherwise.
showRemoveLocaleWarningDialog()140     private void showRemoveLocaleWarningDialog() {
141         int checkedCount = mAdapter.getCheckedCount();
142 
143         // Nothing checked, just exit remove mode without a warning dialog
144         if (checkedCount == 0) {
145             setRemoveMode(!mRemoveMode);
146             return;
147         }
148 
149         // All locales selected, warning dialog, can't remove them all
150         if (checkedCount == mAdapter.getItemCount()) {
151             mShowingRemoveDialog = true;
152             new AlertDialog.Builder(getActivity())
153                     .setTitle(R.string.dlg_remove_locales_error_title)
154                     .setMessage(R.string.dlg_remove_locales_error_message)
155                     .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
156                         @Override
157                         public void onClick(DialogInterface dialog, int which) {
158                         }
159                     })
160                     .setOnDismissListener(new DialogInterface.OnDismissListener() {
161                         @Override
162                         public void onDismiss(DialogInterface dialog) {
163                             mShowingRemoveDialog = false;
164                         }
165                     })
166                     .create()
167                     .show();
168             return;
169         }
170 
171         final String title = getResources().getQuantityString(R.plurals.dlg_remove_locales_title,
172                 checkedCount);
173         mShowingRemoveDialog = true;
174         new AlertDialog.Builder(getActivity())
175                 .setTitle(title)
176                 .setMessage(R.string.dlg_remove_locales_message)
177                 .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
178                     @Override
179                     public void onClick(DialogInterface dialog, int which) {
180                         setRemoveMode(false);
181                     }
182                 })
183                 .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
184                     @Override
185                     public void onClick(DialogInterface dialog, int which) {
186                         // This is a sensitive area to change.
187                         // removeChecked() triggers a system update and "kills" the frame.
188                         // This means that saveState + restoreState are called before
189                         // setRemoveMode is called.
190                         // So we want that mRemoveMode and dialog status have the right values
191                         // before that save.
192                         // We can't just call setRemoveMode(false) before calling removeCheched
193                         // because that unchecks all items and removeChecked would have nothing
194                         // to remove.
195                         mRemoveMode = false;
196                         mShowingRemoveDialog = false;
197                         mAdapter.removeChecked();
198                         setRemoveMode(false);
199                     }
200                 })
201                 .setOnDismissListener(new DialogInterface.OnDismissListener() {
202                     @Override
203                     public void onDismiss(DialogInterface dialog) {
204                         mShowingRemoveDialog = false;
205                     }
206                 })
207                 .create()
208                 .show();
209     }
210 
211     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)212     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
213         final MenuItem menuItem =
214                 menu.add(Menu.NONE, MENU_ID_REMOVE, 0, R.string.locale_remove_menu);
215         menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
216         menuItem.setIcon(R.drawable.ic_delete);
217         super.onCreateOptionsMenu(menu, inflater);
218         mMenu = menu;
219         updateVisibilityOfRemoveMenu();
220     }
221 
getUserLocaleList(Context context)222     private static List<LocaleStore.LocaleInfo> getUserLocaleList(Context context) {
223         final List<LocaleStore.LocaleInfo> result = new ArrayList<>();
224 
225         final LocaleList localeList = LocalePicker.getLocales();
226         for (int i = 0; i < localeList.size(); i++) {
227             Locale locale = localeList.get(i);
228             result.add(LocaleStore.getLocaleInfo(locale));
229         }
230 
231         return result;
232     }
233 
configureDragAndDrop(View view)234     private void configureDragAndDrop(View view) {
235         final RecyclerView list = (RecyclerView) view.findViewById(R.id.dragList);
236         final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
237         llm.setAutoMeasureEnabled(true);
238         list.setLayoutManager(llm);
239 
240         list.setHasFixedSize(true);
241         mAdapter.setRecyclerView(list);
242         list.setAdapter(mAdapter);
243 
244         mAddLanguage = view.findViewById(R.id.add_language);
245         mAddLanguage.setOnClickListener(new View.OnClickListener() {
246             @Override
247             public void onClick(View v) {
248                 final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker(
249                         getContext(), LocaleListEditor.this, false /* translate only */);
250                 getFragmentManager()
251                         .beginTransaction()
252                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
253                         .replace(getId(), selector)
254                         .addToBackStack("localeListEditor")
255                         .commit();
256             }
257         });
258     }
259 
260     @Override
onLocaleSelected(LocaleStore.LocaleInfo locale)261     public void onLocaleSelected(LocaleStore.LocaleInfo locale) {
262         mAdapter.addLocale(locale);
263         updateVisibilityOfRemoveMenu();
264     }
265 
266     // Hide the "Remove" menu if there is only one locale in the list, show it otherwise
267     // This is called when the menu is first created, and then one add / remove locale
updateVisibilityOfRemoveMenu()268     private void updateVisibilityOfRemoveMenu() {
269         if (mMenu == null) {
270             return;
271         }
272 
273         final MenuItem menuItemRemove = mMenu.findItem(MENU_ID_REMOVE);
274         if (menuItemRemove != null) {
275             menuItemRemove.setShowAsAction(
276                     mRemoveMode ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
277             menuItemRemove.setVisible(mAdapter.getItemCount() > 1);
278         }
279     }
280 }
281