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