1 /* 2 * Copyright (C) 2012 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.inputmethod.latin.settings; 18 19 import android.app.AlertDialog; 20 import android.content.Context; 21 import android.content.DialogInterface; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.content.res.Resources; 25 import android.os.Bundle; 26 import android.preference.Preference; 27 import android.preference.PreferenceFragment; 28 import android.preference.PreferenceGroup; 29 import android.support.v4.view.ViewCompat; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.view.LayoutInflater; 33 import android.view.Menu; 34 import android.view.MenuInflater; 35 import android.view.MenuItem; 36 import android.view.View; 37 import android.view.ViewGroup; 38 import android.view.inputmethod.InputMethodSubtype; 39 import android.widget.Toast; 40 41 import com.android.inputmethod.latin.R; 42 import com.android.inputmethod.latin.RichInputMethodManager; 43 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; 44 import com.android.inputmethod.latin.utils.DialogUtils; 45 import com.android.inputmethod.latin.utils.IntentUtils; 46 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; 47 48 import java.util.ArrayList; 49 50 public final class CustomInputStyleSettingsFragment extends PreferenceFragment 51 implements CustomInputStylePreference.Listener { 52 private static final String TAG = CustomInputStyleSettingsFragment.class.getSimpleName(); 53 // Note: We would like to turn this debug flag true in order to see what input styles are 54 // defined in a bug-report. 55 private static final boolean DEBUG_CUSTOM_INPUT_STYLES = true; 56 57 private RichInputMethodManager mRichImm; 58 private SharedPreferences mPrefs; 59 private CustomInputStylePreference.SubtypeLocaleAdapter mSubtypeLocaleAdapter; 60 private CustomInputStylePreference.KeyboardLayoutSetAdapter mKeyboardLayoutSetAdapter; 61 62 private boolean mIsAddingNewSubtype; 63 private AlertDialog mSubtypeEnablerNotificationDialog; 64 private String mSubtypePreferenceKeyForSubtypeEnabler; 65 66 private static final String KEY_IS_ADDING_NEW_SUBTYPE = "is_adding_new_subtype"; 67 private static final String KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN = 68 "is_subtype_enabler_notification_dialog_open"; 69 private static final String KEY_SUBTYPE_FOR_SUBTYPE_ENABLER = "subtype_for_subtype_enabler"; 70 71 public CustomInputStyleSettingsFragment() { 72 // Empty constructor for fragment generation. 73 } 74 75 static void updateCustomInputStylesSummary(final Preference pref) { 76 // When we are called from the Settings application but we are not already running, some 77 // singleton and utility classes may not have been initialized. We have to call 78 // initialization method of these classes here. See {@link LatinIME#onCreate()}. 79 SubtypeLocaleUtils.init(pref.getContext()); 80 81 final Resources res = pref.getContext().getResources(); 82 final SharedPreferences prefs = pref.getSharedPreferences(); 83 final String prefSubtype = Settings.readPrefAdditionalSubtypes(prefs, res); 84 final InputMethodSubtype[] subtypes = 85 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtype); 86 final ArrayList<String> subtypeNames = new ArrayList<>(); 87 for (final InputMethodSubtype subtype : subtypes) { 88 subtypeNames.add(SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); 89 } 90 // TODO: A delimiter of custom input styles should be localized. 91 pref.setSummary(TextUtils.join(", ", subtypeNames)); 92 } 93 94 @Override 95 public void onCreate(final Bundle savedInstanceState) { 96 super.onCreate(savedInstanceState); 97 98 mPrefs = getPreferenceManager().getSharedPreferences(); 99 RichInputMethodManager.init(getActivity()); 100 mRichImm = RichInputMethodManager.getInstance(); 101 addPreferencesFromResource(R.xml.additional_subtype_settings); 102 setHasOptionsMenu(true); 103 } 104 105 @Override 106 public View onCreateView(final LayoutInflater inflater, final ViewGroup container, 107 final Bundle savedInstanceState) { 108 final View view = super.onCreateView(inflater, container, savedInstanceState); 109 // For correct display in RTL locales, we need to set the layout direction of the 110 // fragment's top view. 111 ViewCompat.setLayoutDirection(view, ViewCompat.LAYOUT_DIRECTION_LOCALE); 112 return view; 113 } 114 115 @Override 116 public void onActivityCreated(final Bundle savedInstanceState) { 117 final Context context = getActivity(); 118 mSubtypeLocaleAdapter = new CustomInputStylePreference.SubtypeLocaleAdapter(context); 119 mKeyboardLayoutSetAdapter = 120 new CustomInputStylePreference.KeyboardLayoutSetAdapter(context); 121 122 final String prefSubtypes = 123 Settings.readPrefAdditionalSubtypes(mPrefs, getResources()); 124 if (DEBUG_CUSTOM_INPUT_STYLES) { 125 Log.i(TAG, "Load custom input styles: " + prefSubtypes); 126 } 127 setPrefSubtypes(prefSubtypes, context); 128 129 mIsAddingNewSubtype = (savedInstanceState != null) 130 && savedInstanceState.containsKey(KEY_IS_ADDING_NEW_SUBTYPE); 131 if (mIsAddingNewSubtype) { 132 getPreferenceScreen().addPreference( 133 CustomInputStylePreference.newIncompleteSubtypePreference(context, this)); 134 } 135 136 super.onActivityCreated(savedInstanceState); 137 138 if (savedInstanceState != null && savedInstanceState.containsKey( 139 KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN)) { 140 mSubtypePreferenceKeyForSubtypeEnabler = savedInstanceState.getString( 141 KEY_SUBTYPE_FOR_SUBTYPE_ENABLER); 142 mSubtypeEnablerNotificationDialog = createDialog(); 143 mSubtypeEnablerNotificationDialog.show(); 144 } 145 } 146 147 @Override 148 public void onSaveInstanceState(final Bundle outState) { 149 super.onSaveInstanceState(outState); 150 if (mIsAddingNewSubtype) { 151 outState.putBoolean(KEY_IS_ADDING_NEW_SUBTYPE, true); 152 } 153 if (mSubtypeEnablerNotificationDialog != null 154 && mSubtypeEnablerNotificationDialog.isShowing()) { 155 outState.putBoolean(KEY_IS_SUBTYPE_ENABLER_NOTIFICATION_DIALOG_OPEN, true); 156 outState.putString( 157 KEY_SUBTYPE_FOR_SUBTYPE_ENABLER, mSubtypePreferenceKeyForSubtypeEnabler); 158 } 159 } 160 161 @Override 162 public void onRemoveCustomInputStyle(final CustomInputStylePreference stylePref) { 163 mIsAddingNewSubtype = false; 164 final PreferenceGroup group = getPreferenceScreen(); 165 group.removePreference(stylePref); 166 mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); 167 } 168 169 @Override 170 public void onSaveCustomInputStyle(final CustomInputStylePreference stylePref) { 171 final InputMethodSubtype subtype = stylePref.getSubtype(); 172 if (!stylePref.hasBeenModified()) { 173 return; 174 } 175 if (findDuplicatedSubtype(subtype) == null) { 176 mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); 177 return; 178 } 179 180 // Saved subtype is duplicated. 181 final PreferenceGroup group = getPreferenceScreen(); 182 group.removePreference(stylePref); 183 stylePref.revert(); 184 group.addPreference(stylePref); 185 showSubtypeAlreadyExistsToast(subtype); 186 } 187 188 @Override 189 public void onAddCustomInputStyle(final CustomInputStylePreference stylePref) { 190 mIsAddingNewSubtype = false; 191 final InputMethodSubtype subtype = stylePref.getSubtype(); 192 if (findDuplicatedSubtype(subtype) == null) { 193 mRichImm.setAdditionalInputMethodSubtypes(getSubtypes()); 194 mSubtypePreferenceKeyForSubtypeEnabler = stylePref.getKey(); 195 mSubtypeEnablerNotificationDialog = createDialog(); 196 mSubtypeEnablerNotificationDialog.show(); 197 return; 198 } 199 200 // Newly added subtype is duplicated. 201 final PreferenceGroup group = getPreferenceScreen(); 202 group.removePreference(stylePref); 203 showSubtypeAlreadyExistsToast(subtype); 204 } 205 206 @Override 207 public CustomInputStylePreference.SubtypeLocaleAdapter getSubtypeLocaleAdapter() { 208 return mSubtypeLocaleAdapter; 209 } 210 211 @Override 212 public CustomInputStylePreference.KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter() { 213 return mKeyboardLayoutSetAdapter; 214 } 215 216 private void showSubtypeAlreadyExistsToast(final InputMethodSubtype subtype) { 217 final Context context = getActivity(); 218 final Resources res = context.getResources(); 219 final String message = res.getString(R.string.custom_input_style_already_exists, 220 SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)); 221 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 222 } 223 224 private InputMethodSubtype findDuplicatedSubtype(final InputMethodSubtype subtype) { 225 final String localeString = subtype.getLocale(); 226 final String keyboardLayoutSetName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); 227 return mRichImm.findSubtypeByLocaleAndKeyboardLayoutSet( 228 localeString, keyboardLayoutSetName); 229 } 230 231 private AlertDialog createDialog() { 232 final String imeId = mRichImm.getInputMethodIdOfThisIme(); 233 final AlertDialog.Builder builder = new AlertDialog.Builder( 234 DialogUtils.getPlatformDialogThemeContext(getActivity())); 235 builder.setTitle(R.string.custom_input_styles_title) 236 .setMessage(R.string.custom_input_style_note_message) 237 .setNegativeButton(R.string.not_now, null) 238 .setPositiveButton(R.string.enable, new DialogInterface.OnClickListener() { 239 @Override 240 public void onClick(DialogInterface dialog, int which) { 241 final Intent intent = IntentUtils.getInputLanguageSelectionIntent( 242 imeId, 243 Intent.FLAG_ACTIVITY_NEW_TASK 244 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 245 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 246 // TODO: Add newly adding subtype to extra value of the intent as a hint 247 // for the input language selection activity. 248 // intent.putExtra("newlyAddedSubtype", subtypePref.getSubtype()); 249 startActivity(intent); 250 } 251 }); 252 253 return builder.create(); 254 } 255 256 private void setPrefSubtypes(final String prefSubtypes, final Context context) { 257 final PreferenceGroup group = getPreferenceScreen(); 258 group.removeAll(); 259 final InputMethodSubtype[] subtypesArray = 260 AdditionalSubtypeUtils.createAdditionalSubtypesArray(prefSubtypes); 261 for (final InputMethodSubtype subtype : subtypesArray) { 262 final CustomInputStylePreference pref = 263 new CustomInputStylePreference(context, subtype, this); 264 group.addPreference(pref); 265 } 266 } 267 268 private InputMethodSubtype[] getSubtypes() { 269 final PreferenceGroup group = getPreferenceScreen(); 270 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>(); 271 final int count = group.getPreferenceCount(); 272 for (int i = 0; i < count; i++) { 273 final Preference pref = group.getPreference(i); 274 if (pref instanceof CustomInputStylePreference) { 275 final CustomInputStylePreference subtypePref = (CustomInputStylePreference)pref; 276 // We should not save newly adding subtype to preference because it is incomplete. 277 if (subtypePref.isIncomplete()) continue; 278 subtypes.add(subtypePref.getSubtype()); 279 } 280 } 281 return subtypes.toArray(new InputMethodSubtype[subtypes.size()]); 282 } 283 284 @Override 285 public void onPause() { 286 super.onPause(); 287 final String oldSubtypes = Settings.readPrefAdditionalSubtypes(mPrefs, getResources()); 288 final InputMethodSubtype[] subtypes = getSubtypes(); 289 final String prefSubtypes = AdditionalSubtypeUtils.createPrefSubtypes(subtypes); 290 if (DEBUG_CUSTOM_INPUT_STYLES) { 291 Log.i(TAG, "Save custom input styles: " + prefSubtypes); 292 } 293 if (prefSubtypes.equals(oldSubtypes)) { 294 return; 295 } 296 Settings.writePrefAdditionalSubtypes(mPrefs, prefSubtypes); 297 mRichImm.setAdditionalInputMethodSubtypes(subtypes); 298 } 299 300 @Override 301 public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) { 302 inflater.inflate(R.menu.add_style, menu); 303 } 304 305 @Override 306 public boolean onOptionsItemSelected(final MenuItem item) { 307 final int itemId = item.getItemId(); 308 if (itemId == R.id.action_add_style) { 309 final CustomInputStylePreference newSubtype = 310 CustomInputStylePreference.newIncompleteSubtypePreference(getActivity(), this); 311 getPreferenceScreen().addPreference(newSubtype); 312 newSubtype.show(); 313 mIsAddingNewSubtype = true; 314 return true; 315 } 316 return super.onOptionsItemSelected(item); 317 } 318 } 319