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