| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.inputmethod.latin.settings; |
| |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.preference.DialogPreference; |
| import android.preference.Preference; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.inputmethod.InputMethodInfo; |
| import android.view.inputmethod.InputMethodSubtype; |
| import android.widget.ArrayAdapter; |
| import android.widget.Spinner; |
| import android.widget.SpinnerAdapter; |
| |
| import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils; |
| import com.android.inputmethod.compat.ViewCompatUtils; |
| import com.android.inputmethod.latin.R; |
| import com.android.inputmethod.latin.RichInputMethodManager; |
| import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils; |
| import com.android.inputmethod.latin.utils.SubtypeLocaleUtils; |
| |
| import java.util.TreeSet; |
| |
| final class CustomInputStylePreference extends DialogPreference |
| implements DialogInterface.OnCancelListener { |
| private static final boolean DEBUG_SUBTYPE_ID = false; |
| |
| interface Listener { |
| public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref); |
| public void onSaveCustomInputStyle(CustomInputStylePreference stylePref); |
| public void onAddCustomInputStyle(CustomInputStylePreference stylePref); |
| public SubtypeLocaleAdapter getSubtypeLocaleAdapter(); |
| public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter(); |
| } |
| |
| private static final String KEY_PREFIX = "subtype_pref_"; |
| private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new"; |
| |
| private InputMethodSubtype mSubtype; |
| private InputMethodSubtype mPreviousSubtype; |
| |
| private final Listener mProxy; |
| private Spinner mSubtypeLocaleSpinner; |
| private Spinner mKeyboardLayoutSetSpinner; |
| |
| public static CustomInputStylePreference newIncompleteSubtypePreference( |
| final Context context, final Listener proxy) { |
| return new CustomInputStylePreference(context, null, proxy); |
| } |
| |
| public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype, |
| final Listener proxy) { |
| super(context, null); |
| setDialogLayoutResource(R.layout.additional_subtype_dialog); |
| setPersistent(false); |
| mProxy = proxy; |
| setSubtype(subtype); |
| } |
| |
| public void show() { |
| showDialog(null); |
| } |
| |
| public final boolean isIncomplete() { |
| return mSubtype == null; |
| } |
| |
| public InputMethodSubtype getSubtype() { |
| return mSubtype; |
| } |
| |
| public void setSubtype(final InputMethodSubtype subtype) { |
| mPreviousSubtype = mSubtype; |
| mSubtype = subtype; |
| if (isIncomplete()) { |
| setTitle(null); |
| setDialogTitle(R.string.add_style); |
| setKey(KEY_NEW_SUBTYPE); |
| } else { |
| final String displayName = |
| SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype); |
| setTitle(displayName); |
| setDialogTitle(displayName); |
| setKey(KEY_PREFIX + subtype.getLocale() + "_" |
| + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype)); |
| } |
| } |
| |
| public void revert() { |
| setSubtype(mPreviousSubtype); |
| } |
| |
| public boolean hasBeenModified() { |
| return mSubtype != null && !mSubtype.equals(mPreviousSubtype); |
| } |
| |
| @Override |
| protected View onCreateDialogView() { |
| final View v = super.onCreateDialogView(); |
| mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner); |
| mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter()); |
| mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner); |
| mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter()); |
| // All keyboard layout names are in the Latin script and thus left to right. That means |
| // the view would align them to the left even if the system locale is RTL, but that |
| // would look strange. To fix this, we align them to the view's start, which will be |
| // natural for any direction. |
| ViewCompatUtils.setTextAlignment( |
| mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START); |
| return v; |
| } |
| |
| @Override |
| protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) { |
| builder.setCancelable(true).setOnCancelListener(this); |
| if (isIncomplete()) { |
| builder.setPositiveButton(R.string.add, this) |
| .setNegativeButton(android.R.string.cancel, this); |
| } else { |
| builder.setPositiveButton(R.string.save, this) |
| .setNeutralButton(android.R.string.cancel, this) |
| .setNegativeButton(R.string.remove, this); |
| final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype); |
| final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype); |
| setSpinnerPosition(mSubtypeLocaleSpinner, localeItem); |
| setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem); |
| } |
| } |
| |
| private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) { |
| final SpinnerAdapter adapter = spinner.getAdapter(); |
| final int count = adapter.getCount(); |
| for (int i = 0; i < count; i++) { |
| final Object item = spinner.getItemAtPosition(i); |
| if (item.equals(itemToSelect)) { |
| spinner.setSelection(i); |
| return; |
| } |
| } |
| } |
| |
| @Override |
| public void onCancel(final DialogInterface dialog) { |
| if (isIncomplete()) { |
| mProxy.onRemoveCustomInputStyle(this); |
| } |
| } |
| |
| @Override |
| public void onClick(final DialogInterface dialog, final int which) { |
| super.onClick(dialog, which); |
| switch (which) { |
| case DialogInterface.BUTTON_POSITIVE: |
| final boolean isEditing = !isIncomplete(); |
| final SubtypeLocaleItem locale = |
| (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem(); |
| final KeyboardLayoutSetItem layout = |
| (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem(); |
| final InputMethodSubtype subtype = |
| AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype( |
| locale.mLocaleString, layout.mLayoutName); |
| setSubtype(subtype); |
| notifyChanged(); |
| if (isEditing) { |
| mProxy.onSaveCustomInputStyle(this); |
| } else { |
| mProxy.onAddCustomInputStyle(this); |
| } |
| break; |
| case DialogInterface.BUTTON_NEUTRAL: |
| // Nothing to do |
| break; |
| case DialogInterface.BUTTON_NEGATIVE: |
| mProxy.onRemoveCustomInputStyle(this); |
| break; |
| } |
| } |
| |
| @Override |
| protected Parcelable onSaveInstanceState() { |
| final Parcelable superState = super.onSaveInstanceState(); |
| final Dialog dialog = getDialog(); |
| if (dialog == null || !dialog.isShowing()) { |
| return superState; |
| } |
| |
| final SavedState myState = new SavedState(superState); |
| myState.mSubtype = mSubtype; |
| return myState; |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(final Parcelable state) { |
| if (!(state instanceof SavedState)) { |
| super.onRestoreInstanceState(state); |
| return; |
| } |
| |
| final SavedState myState = (SavedState) state; |
| super.onRestoreInstanceState(myState.getSuperState()); |
| setSubtype(myState.mSubtype); |
| } |
| |
| static final class SavedState extends Preference.BaseSavedState { |
| InputMethodSubtype mSubtype; |
| |
| public SavedState(final Parcelable superState) { |
| super(superState); |
| } |
| |
| @Override |
| public void writeToParcel(final Parcel dest, final int flags) { |
| super.writeToParcel(dest, flags); |
| dest.writeParcelable(mSubtype, 0); |
| } |
| |
| public SavedState(final Parcel source) { |
| super(source); |
| mSubtype = (InputMethodSubtype)source.readParcelable(null); |
| } |
| |
| @SuppressWarnings("hiding") |
| public static final Parcelable.Creator<SavedState> CREATOR = |
| new Parcelable.Creator<SavedState>() { |
| @Override |
| public SavedState createFromParcel(final Parcel source) { |
| return new SavedState(source); |
| } |
| |
| @Override |
| public SavedState[] newArray(final int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| |
| static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> { |
| public final String mLocaleString; |
| private final String mDisplayName; |
| |
| public SubtypeLocaleItem(final InputMethodSubtype subtype) { |
| mLocaleString = subtype.getLocale(); |
| mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale( |
| mLocaleString); |
| } |
| |
| // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} |
| // to get display name. |
| @Override |
| public String toString() { |
| return mDisplayName; |
| } |
| |
| @Override |
| public int compareTo(final SubtypeLocaleItem o) { |
| return mLocaleString.compareTo(o.mLocaleString); |
| } |
| } |
| |
| static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> { |
| private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName(); |
| |
| public SubtypeLocaleAdapter(final Context context) { |
| super(context, android.R.layout.simple_spinner_item); |
| setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| |
| final TreeSet<SubtypeLocaleItem> items = new TreeSet<>(); |
| final InputMethodInfo imi = RichInputMethodManager.getInstance() |
| .getInputMethodInfoOfThisIme(); |
| final int count = imi.getSubtypeCount(); |
| for (int i = 0; i < count; i++) { |
| final InputMethodSubtype subtype = imi.getSubtypeAt(i); |
| if (DEBUG_SUBTYPE_ID) { |
| Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s", |
| subtype.getLocale(), subtype.hashCode(), subtype.hashCode(), |
| SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype))); |
| } |
| if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) { |
| items.add(new SubtypeLocaleItem(subtype)); |
| } |
| } |
| // TODO: Should filter out already existing combinations of locale and layout. |
| addAll(items); |
| } |
| } |
| |
| static final class KeyboardLayoutSetItem { |
| public final String mLayoutName; |
| private final String mDisplayName; |
| |
| public KeyboardLayoutSetItem(final InputMethodSubtype subtype) { |
| mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype); |
| mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype); |
| } |
| |
| // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()} |
| // to get display name. |
| @Override |
| public String toString() { |
| return mDisplayName; |
| } |
| } |
| |
| static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> { |
| public KeyboardLayoutSetAdapter(final Context context) { |
| super(context, android.R.layout.simple_spinner_item); |
| setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
| |
| final String[] predefinedKeyboardLayoutSet = context.getResources().getStringArray( |
| R.array.predefined_layouts); |
| // TODO: Should filter out already existing combinations of locale and layout. |
| for (final String layout : predefinedKeyboardLayoutSet) { |
| // This is a dummy subtype with NO_LANGUAGE, only for display. |
| final InputMethodSubtype subtype = |
| AdditionalSubtypeUtils.createDummyAdditionalSubtype( |
| SubtypeLocaleUtils.NO_LANGUAGE, layout); |
| add(new KeyboardLayoutSetItem(subtype)); |
| } |
| } |
| } |
| } |