• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static android.os.UserManager.DISALLOW_CONFIG_LOCALE;
20 
21 import android.app.Activity;
22 import android.app.settings.SettingsEnums;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.res.Resources;
27 import android.os.Bundle;
28 import android.os.LocaleList;
29 import android.view.LayoutInflater;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.TextView;
36 
37 import androidx.annotation.VisibleForTesting;
38 import androidx.appcompat.app.AlertDialog;
39 import androidx.recyclerview.widget.RecyclerView;
40 
41 import com.android.internal.app.LocalePicker;
42 import com.android.internal.app.LocaleStore;
43 import com.android.settings.R;
44 import com.android.settings.RestrictedSettingsFragment;
45 import com.android.settings.overlay.FeatureFactory;
46 import com.android.settings.search.BaseSearchIndexProvider;
47 import com.android.settingslib.search.SearchIndexable;
48 import com.android.settingslib.search.SearchIndexableRaw;
49 
50 import java.util.ArrayList;
51 import java.util.List;
52 import java.util.Locale;
53 
54 /**
55  * Drag-and-drop editor for the user-ordered locale lists.
56  */
57 @SearchIndexable
58 public class LocaleListEditor extends RestrictedSettingsFragment {
59 
60     protected static final String INTENT_LOCALE_KEY = "localeInfo";
61     private static final String CFGKEY_REMOVE_MODE = "localeRemoveMode";
62     private static final String CFGKEY_REMOVE_DIALOG = "showingLocaleRemoveDialog";
63     private static final int MENU_ID_REMOVE = Menu.FIRST + 1;
64     private static final int REQUEST_LOCALE_PICKER = 0;
65 
66     private static final String INDEX_KEY_ADD_LANGUAGE = "add_language";
67 
68     private LocaleDragAndDropAdapter mAdapter;
69     private Menu mMenu;
70     private View mAddLanguage;
71     private boolean mRemoveMode;
72     private boolean mShowingRemoveDialog;
73     private boolean mIsUiRestricted;
74 
LocaleListEditor()75     public LocaleListEditor() {
76         super(DISALLOW_CONFIG_LOCALE);
77     }
78 
79     @Override
getMetricsCategory()80     public int getMetricsCategory() {
81         return SettingsEnums.USER_LOCALE_LIST;
82     }
83 
84     @Override
onCreate(Bundle savedInstanceState)85     public void onCreate(Bundle savedInstanceState) {
86         super.onCreate(savedInstanceState);
87         setHasOptionsMenu(true);
88 
89         LocaleStore.fillCache(this.getContext());
90         final List<LocaleStore.LocaleInfo> feedsList = getUserLocaleList();
91         mAdapter = new LocaleDragAndDropAdapter(this.getContext(), feedsList);
92     }
93 
94     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState)95     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) {
96         final View result = super.onCreateView(inflater, container, savedInstState);
97         final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result);
98 
99         configureDragAndDrop(myLayout);
100         return result;
101     }
102 
103     @Override
onResume()104     public void onResume() {
105         super.onResume();
106 
107         final boolean previouslyRestricted = mIsUiRestricted;
108         mIsUiRestricted = isUiRestricted();
109         final TextView emptyView = getEmptyTextView();
110         if (mIsUiRestricted && !previouslyRestricted) {
111             // Lock it down.
112             emptyView.setText(R.string.language_empty_list_user_restricted);
113             emptyView.setVisibility(View.VISIBLE);
114             updateVisibilityOfRemoveMenu();
115         } else if (!mIsUiRestricted && previouslyRestricted) {
116             // Unlock it.
117             emptyView.setVisibility(View.GONE);
118             updateVisibilityOfRemoveMenu();
119         }
120     }
121 
122     @Override
onViewStateRestored(Bundle savedInstanceState)123     public void onViewStateRestored(Bundle savedInstanceState) {
124         super.onViewStateRestored(savedInstanceState);
125         if (savedInstanceState != null) {
126             mRemoveMode = savedInstanceState.getBoolean(CFGKEY_REMOVE_MODE, false);
127             mShowingRemoveDialog = savedInstanceState.getBoolean(CFGKEY_REMOVE_DIALOG, false);
128         }
129         setRemoveMode(mRemoveMode);
130         mAdapter.restoreState(savedInstanceState);
131 
132         if (mShowingRemoveDialog) {
133             showRemoveLocaleWarningDialog();
134         }
135     }
136 
137     @Override
onSaveInstanceState(Bundle outState)138     public void onSaveInstanceState(Bundle outState) {
139         super.onSaveInstanceState(outState);
140         outState.putBoolean(CFGKEY_REMOVE_MODE, mRemoveMode);
141         outState.putBoolean(CFGKEY_REMOVE_DIALOG, mShowingRemoveDialog);
142         mAdapter.saveState(outState);
143     }
144 
145     @Override
onOptionsItemSelected(MenuItem menuItem)146     public boolean onOptionsItemSelected(MenuItem menuItem) {
147         switch (menuItem.getItemId()) {
148             case MENU_ID_REMOVE:
149                 if (mRemoveMode) {
150                     showRemoveLocaleWarningDialog();
151                 } else {
152                     setRemoveMode(true);
153                 }
154                 return true;
155             case android.R.id.home:
156                 if (mRemoveMode) {
157                     setRemoveMode(false);
158                     return true;
159                 }
160                 break;
161         }
162         return super.onOptionsItemSelected(menuItem);
163     }
164 
165     @Override
onActivityResult(int requestCode, int resultCode, Intent data)166     public void onActivityResult(int requestCode, int resultCode, Intent data) {
167         if (requestCode == REQUEST_LOCALE_PICKER && resultCode == Activity.RESULT_OK
168                 && data != null) {
169             final LocaleStore.LocaleInfo locale =
170                     (LocaleStore.LocaleInfo) data.getSerializableExtra(
171                             INTENT_LOCALE_KEY);
172             mAdapter.addLocale(locale);
173             updateVisibilityOfRemoveMenu();
174         }
175         super.onActivityResult(requestCode, resultCode, data);
176     }
177 
setRemoveMode(boolean mRemoveMode)178     private void setRemoveMode(boolean mRemoveMode) {
179         this.mRemoveMode = mRemoveMode;
180         mAdapter.setRemoveMode(mRemoveMode);
181         mAddLanguage.setVisibility(mRemoveMode ? View.INVISIBLE : View.VISIBLE);
182         updateVisibilityOfRemoveMenu();
183     }
184 
185     // Show the appropriate warning when the user tries to remove locales.
186     // Shows no warning if there is no locale checked, shows a warning
187     // about removing all the locales if all of them are checked, and
188     // a "regular" warning otherwise.
189     @VisibleForTesting
showRemoveLocaleWarningDialog()190     void showRemoveLocaleWarningDialog() {
191         int checkedCount = mAdapter.getCheckedCount();
192 
193         // Nothing checked, just exit remove mode without a warning dialog
194         if (checkedCount == 0) {
195             setRemoveMode(!mRemoveMode);
196             return;
197         }
198 
199         // All locales selected, warning dialog, can't remove them all
200         if (checkedCount == mAdapter.getItemCount()) {
201             mShowingRemoveDialog = true;
202             new AlertDialog.Builder(getActivity())
203                     .setTitle(R.string.dlg_remove_locales_error_title)
204                     .setMessage(R.string.dlg_remove_locales_error_message)
205                     .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
206                         @Override
207                         public void onClick(DialogInterface dialog, int which) {
208                         }
209                     })
210                     .setOnDismissListener(new DialogInterface.OnDismissListener() {
211                         @Override
212                         public void onDismiss(DialogInterface dialog) {
213                             mShowingRemoveDialog = false;
214                         }
215                     })
216                     .create()
217                     .show();
218             return;
219         }
220 
221         final String title = getResources().getQuantityString(R.plurals.dlg_remove_locales_title,
222                 checkedCount);
223         mShowingRemoveDialog = true;
224 
225         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
226         if (mAdapter.isFirstLocaleChecked()) {
227             builder.setMessage(R.string.dlg_remove_locales_message);
228         }
229 
230         builder.setTitle(title)
231                 .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
232                     @Override
233                     public void onClick(DialogInterface dialog, int which) {
234                         setRemoveMode(false);
235                     }
236                 })
237                 .setPositiveButton(R.string.locale_remove_menu,
238                         new DialogInterface.OnClickListener() {
239                             @Override
240                             public void onClick(DialogInterface dialog, int which) {
241                                 // This is a sensitive area to change.
242                                 // removeChecked() triggers a system update and "kills" the frame.
243                                 // This means that saveState + restoreState are called before
244                                 // setRemoveMode is called.
245                                 // So we want that mRemoveMode and dialog status have the right
246                                 // values
247                                 // before that save.
248                                 // We can't just call setRemoveMode(false) before calling
249                                 // removeCheched
250                                 // because that unchecks all items and removeChecked would have
251                                 // nothing
252                                 // to remove.
253                                 mRemoveMode = false;
254                                 mShowingRemoveDialog = false;
255                                 mAdapter.removeChecked();
256                                 setRemoveMode(false);
257                             }
258                         })
259                 .setOnDismissListener(new DialogInterface.OnDismissListener() {
260                     @Override
261                     public void onDismiss(DialogInterface dialog) {
262                         mShowingRemoveDialog = false;
263                     }
264                 })
265                 .create()
266                 .show();
267     }
268 
269     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)270     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
271         final MenuItem menuItem =
272                 menu.add(Menu.NONE, MENU_ID_REMOVE, 0, R.string.locale_remove_menu);
273         menuItem.setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
274         menuItem.setIcon(R.drawable.ic_delete);
275         super.onCreateOptionsMenu(menu, inflater);
276         mMenu = menu;
277         updateVisibilityOfRemoveMenu();
278     }
279 
getUserLocaleList()280     private List<LocaleStore.LocaleInfo> getUserLocaleList() {
281         final List<LocaleStore.LocaleInfo> result = new ArrayList<>();
282         final LocaleList localeList = LocalePicker.getLocales();
283         for (int i = 0; i < localeList.size(); i++) {
284             Locale locale = localeList.get(i);
285             result.add(LocaleStore.getLocaleInfo(locale));
286         }
287         return result;
288     }
289 
configureDragAndDrop(View view)290     private void configureDragAndDrop(View view) {
291         final RecyclerView list = view.findViewById(R.id.dragList);
292         final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter);
293         llm.setAutoMeasureEnabled(true);
294         list.setLayoutManager(llm);
295 
296         list.setHasFixedSize(true);
297         mAdapter.setRecyclerView(list);
298         list.setAdapter(mAdapter);
299 
300         mAddLanguage = view.findViewById(R.id.add_language);
301         mAddLanguage.setOnClickListener(new View.OnClickListener() {
302             @Override
303             public void onClick(View v) {
304                 FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
305                         .logSettingsTileClick(INDEX_KEY_ADD_LANGUAGE, getMetricsCategory());
306 
307                 final Intent intent = new Intent(getActivity(),
308                         LocalePickerWithRegionActivity.class);
309                 startActivityForResult(intent, REQUEST_LOCALE_PICKER);
310             }
311         });
312     }
313 
314     // Hide the "Remove" menu if there is only one locale in the list, show it otherwise
315     // This is called when the menu is first created, and then one add / remove locale
updateVisibilityOfRemoveMenu()316     private void updateVisibilityOfRemoveMenu() {
317         if (mMenu == null) {
318             return;
319         }
320 
321         final MenuItem menuItemRemove = mMenu.findItem(MENU_ID_REMOVE);
322         if (menuItemRemove != null) {
323             menuItemRemove.setShowAsAction(
324                     mRemoveMode ? MenuItem.SHOW_AS_ACTION_ALWAYS : MenuItem.SHOW_AS_ACTION_NEVER);
325             final boolean hasMultipleLanguages = mAdapter.getItemCount() > 1;
326             menuItemRemove.setVisible(hasMultipleLanguages && !mIsUiRestricted);
327         }
328     }
329 
330     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
331             new BaseSearchIndexProvider() {
332                 @Override
333                 public List<SearchIndexableRaw> getRawDataToIndex(Context context,
334                         boolean enabled) {
335                     final Resources res = context.getResources();
336                     final List<SearchIndexableRaw> indexRaws = new ArrayList<>();
337                     final SearchIndexableRaw raw = new SearchIndexableRaw(context);
338                     raw.key = INDEX_KEY_ADD_LANGUAGE;
339                     raw.title = res.getString(R.string.add_a_language);
340                     raw.keywords = res.getString(R.string.keywords_add_language);
341                     indexRaws.add(raw);
342                     return indexRaws;
343                 }
344             };
345 }
346