Home | History | Annotate | Download | only in settings
      1 /*
      2  * Copyright (C) 2014 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.app.Dialog;
     21 import android.content.Context;
     22 import android.content.DialogInterface;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.preference.DialogPreference;
     26 import android.preference.Preference;
     27 import android.util.Log;
     28 import android.view.View;
     29 import android.view.inputmethod.InputMethodInfo;
     30 import android.view.inputmethod.InputMethodSubtype;
     31 import android.widget.ArrayAdapter;
     32 import android.widget.Spinner;
     33 import android.widget.SpinnerAdapter;
     34 
     35 import com.android.inputmethod.compat.InputMethodSubtypeCompatUtils;
     36 import com.android.inputmethod.compat.ViewCompatUtils;
     37 import com.android.inputmethod.latin.R;
     38 import com.android.inputmethod.latin.RichInputMethodManager;
     39 import com.android.inputmethod.latin.utils.AdditionalSubtypeUtils;
     40 import com.android.inputmethod.latin.utils.SubtypeLocaleUtils;
     41 
     42 import java.util.TreeSet;
     43 
     44 final class CustomInputStylePreference extends DialogPreference
     45         implements DialogInterface.OnCancelListener {
     46     private static final boolean DEBUG_SUBTYPE_ID = false;
     47 
     48     interface Listener {
     49         public void onRemoveCustomInputStyle(CustomInputStylePreference stylePref);
     50         public void onSaveCustomInputStyle(CustomInputStylePreference stylePref);
     51         public void onAddCustomInputStyle(CustomInputStylePreference stylePref);
     52         public SubtypeLocaleAdapter getSubtypeLocaleAdapter();
     53         public KeyboardLayoutSetAdapter getKeyboardLayoutSetAdapter();
     54     }
     55 
     56     private static final String KEY_PREFIX = "subtype_pref_";
     57     private static final String KEY_NEW_SUBTYPE = KEY_PREFIX + "new";
     58 
     59     private InputMethodSubtype mSubtype;
     60     private InputMethodSubtype mPreviousSubtype;
     61 
     62     private final Listener mProxy;
     63     private Spinner mSubtypeLocaleSpinner;
     64     private Spinner mKeyboardLayoutSetSpinner;
     65 
     66     public static CustomInputStylePreference newIncompleteSubtypePreference(
     67             final Context context, final Listener proxy) {
     68         return new CustomInputStylePreference(context, null, proxy);
     69     }
     70 
     71     public CustomInputStylePreference(final Context context, final InputMethodSubtype subtype,
     72             final Listener proxy) {
     73         super(context, null);
     74         setDialogLayoutResource(R.layout.additional_subtype_dialog);
     75         setPersistent(false);
     76         mProxy = proxy;
     77         setSubtype(subtype);
     78     }
     79 
     80     public void show() {
     81         showDialog(null);
     82     }
     83 
     84     public final boolean isIncomplete() {
     85         return mSubtype == null;
     86     }
     87 
     88     public InputMethodSubtype getSubtype() {
     89         return mSubtype;
     90     }
     91 
     92     public void setSubtype(final InputMethodSubtype subtype) {
     93         mPreviousSubtype = mSubtype;
     94         mSubtype = subtype;
     95         if (isIncomplete()) {
     96             setTitle(null);
     97             setDialogTitle(R.string.add_style);
     98             setKey(KEY_NEW_SUBTYPE);
     99         } else {
    100             final String displayName =
    101                     SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype);
    102             setTitle(displayName);
    103             setDialogTitle(displayName);
    104             setKey(KEY_PREFIX + subtype.getLocale() + "_"
    105                     + SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype));
    106         }
    107     }
    108 
    109     public void revert() {
    110         setSubtype(mPreviousSubtype);
    111     }
    112 
    113     public boolean hasBeenModified() {
    114         return mSubtype != null && !mSubtype.equals(mPreviousSubtype);
    115     }
    116 
    117     @Override
    118     protected View onCreateDialogView() {
    119         final View v = super.onCreateDialogView();
    120         mSubtypeLocaleSpinner = (Spinner) v.findViewById(R.id.subtype_locale_spinner);
    121         mSubtypeLocaleSpinner.setAdapter(mProxy.getSubtypeLocaleAdapter());
    122         mKeyboardLayoutSetSpinner = (Spinner) v.findViewById(R.id.keyboard_layout_set_spinner);
    123         mKeyboardLayoutSetSpinner.setAdapter(mProxy.getKeyboardLayoutSetAdapter());
    124         // All keyboard layout names are in the Latin script and thus left to right. That means
    125         // the view would align them to the left even if the system locale is RTL, but that
    126         // would look strange. To fix this, we align them to the view's start, which will be
    127         // natural for any direction.
    128         ViewCompatUtils.setTextAlignment(
    129                 mKeyboardLayoutSetSpinner, ViewCompatUtils.TEXT_ALIGNMENT_VIEW_START);
    130         return v;
    131     }
    132 
    133     @Override
    134     protected void onPrepareDialogBuilder(final AlertDialog.Builder builder) {
    135         builder.setCancelable(true).setOnCancelListener(this);
    136         if (isIncomplete()) {
    137             builder.setPositiveButton(R.string.add, this)
    138                     .setNegativeButton(android.R.string.cancel, this);
    139         } else {
    140             builder.setPositiveButton(R.string.save, this)
    141                     .setNeutralButton(android.R.string.cancel, this)
    142                     .setNegativeButton(R.string.remove, this);
    143             final SubtypeLocaleItem localeItem = new SubtypeLocaleItem(mSubtype);
    144             final KeyboardLayoutSetItem layoutItem = new KeyboardLayoutSetItem(mSubtype);
    145             setSpinnerPosition(mSubtypeLocaleSpinner, localeItem);
    146             setSpinnerPosition(mKeyboardLayoutSetSpinner, layoutItem);
    147         }
    148     }
    149 
    150     private static void setSpinnerPosition(final Spinner spinner, final Object itemToSelect) {
    151         final SpinnerAdapter adapter = spinner.getAdapter();
    152         final int count = adapter.getCount();
    153         for (int i = 0; i < count; i++) {
    154             final Object item = spinner.getItemAtPosition(i);
    155             if (item.equals(itemToSelect)) {
    156                 spinner.setSelection(i);
    157                 return;
    158             }
    159         }
    160     }
    161 
    162     @Override
    163     public void onCancel(final DialogInterface dialog) {
    164         if (isIncomplete()) {
    165             mProxy.onRemoveCustomInputStyle(this);
    166         }
    167     }
    168 
    169     @Override
    170     public void onClick(final DialogInterface dialog, final int which) {
    171         super.onClick(dialog, which);
    172         switch (which) {
    173         case DialogInterface.BUTTON_POSITIVE:
    174             final boolean isEditing = !isIncomplete();
    175             final SubtypeLocaleItem locale =
    176                     (SubtypeLocaleItem) mSubtypeLocaleSpinner.getSelectedItem();
    177             final KeyboardLayoutSetItem layout =
    178                     (KeyboardLayoutSetItem) mKeyboardLayoutSetSpinner.getSelectedItem();
    179             final InputMethodSubtype subtype =
    180                     AdditionalSubtypeUtils.createAsciiEmojiCapableAdditionalSubtype(
    181                             locale.mLocaleString, layout.mLayoutName);
    182             setSubtype(subtype);
    183             notifyChanged();
    184             if (isEditing) {
    185                 mProxy.onSaveCustomInputStyle(this);
    186             } else {
    187                 mProxy.onAddCustomInputStyle(this);
    188             }
    189             break;
    190         case DialogInterface.BUTTON_NEUTRAL:
    191             // Nothing to do
    192             break;
    193         case DialogInterface.BUTTON_NEGATIVE:
    194             mProxy.onRemoveCustomInputStyle(this);
    195             break;
    196         }
    197     }
    198 
    199     @Override
    200     protected Parcelable onSaveInstanceState() {
    201         final Parcelable superState = super.onSaveInstanceState();
    202         final Dialog dialog = getDialog();
    203         if (dialog == null || !dialog.isShowing()) {
    204             return superState;
    205         }
    206 
    207         final SavedState myState = new SavedState(superState);
    208         myState.mSubtype = mSubtype;
    209         return myState;
    210     }
    211 
    212     @Override
    213     protected void onRestoreInstanceState(final Parcelable state) {
    214         if (!(state instanceof SavedState)) {
    215             super.onRestoreInstanceState(state);
    216             return;
    217         }
    218 
    219         final SavedState myState = (SavedState) state;
    220         super.onRestoreInstanceState(myState.getSuperState());
    221         setSubtype(myState.mSubtype);
    222     }
    223 
    224     static final class SavedState extends Preference.BaseSavedState {
    225         InputMethodSubtype mSubtype;
    226 
    227         public SavedState(final Parcelable superState) {
    228             super(superState);
    229         }
    230 
    231         @Override
    232         public void writeToParcel(final Parcel dest, final int flags) {
    233             super.writeToParcel(dest, flags);
    234             dest.writeParcelable(mSubtype, 0);
    235         }
    236 
    237         public SavedState(final Parcel source) {
    238             super(source);
    239             mSubtype = (InputMethodSubtype)source.readParcelable(null);
    240         }
    241 
    242         @SuppressWarnings("hiding")
    243         public static final Parcelable.Creator<SavedState> CREATOR =
    244                 new Parcelable.Creator<SavedState>() {
    245                     @Override
    246                     public SavedState createFromParcel(final Parcel source) {
    247                         return new SavedState(source);
    248                     }
    249 
    250                     @Override
    251                     public SavedState[] newArray(final int size) {
    252                         return new SavedState[size];
    253                     }
    254                 };
    255     }
    256 
    257     static final class SubtypeLocaleItem implements Comparable<SubtypeLocaleItem> {
    258         public final String mLocaleString;
    259         private final String mDisplayName;
    260 
    261         public SubtypeLocaleItem(final InputMethodSubtype subtype) {
    262             mLocaleString = subtype.getLocale();
    263             mDisplayName = SubtypeLocaleUtils.getSubtypeLocaleDisplayNameInSystemLocale(
    264                     mLocaleString);
    265         }
    266 
    267         // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
    268         // to get display name.
    269         @Override
    270         public String toString() {
    271             return mDisplayName;
    272         }
    273 
    274         @Override
    275         public int compareTo(final SubtypeLocaleItem o) {
    276             return mLocaleString.compareTo(o.mLocaleString);
    277         }
    278     }
    279 
    280     static final class SubtypeLocaleAdapter extends ArrayAdapter<SubtypeLocaleItem> {
    281         private static final String TAG_SUBTYPE = SubtypeLocaleAdapter.class.getSimpleName();
    282 
    283         public SubtypeLocaleAdapter(final Context context) {
    284             super(context, android.R.layout.simple_spinner_item);
    285             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    286 
    287             final TreeSet<SubtypeLocaleItem> items = new TreeSet<>();
    288             final InputMethodInfo imi = RichInputMethodManager.getInstance()
    289                     .getInputMethodInfoOfThisIme();
    290             final int count = imi.getSubtypeCount();
    291             for (int i = 0; i < count; i++) {
    292                 final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    293                 if (DEBUG_SUBTYPE_ID) {
    294                     Log.d(TAG_SUBTYPE, String.format("%-6s 0x%08x %11d %s",
    295                             subtype.getLocale(), subtype.hashCode(), subtype.hashCode(),
    296                             SubtypeLocaleUtils.getSubtypeDisplayNameInSystemLocale(subtype)));
    297                 }
    298                 if (InputMethodSubtypeCompatUtils.isAsciiCapable(subtype)) {
    299                     items.add(new SubtypeLocaleItem(subtype));
    300                 }
    301             }
    302             // TODO: Should filter out already existing combinations of locale and layout.
    303             addAll(items);
    304         }
    305     }
    306 
    307     static final class KeyboardLayoutSetItem {
    308         public final String mLayoutName;
    309         private final String mDisplayName;
    310 
    311         public KeyboardLayoutSetItem(final InputMethodSubtype subtype) {
    312             mLayoutName = SubtypeLocaleUtils.getKeyboardLayoutSetName(subtype);
    313             mDisplayName = SubtypeLocaleUtils.getKeyboardLayoutSetDisplayName(subtype);
    314         }
    315 
    316         // {@link ArrayAdapter<T>} that hosts the instance of this class needs {@link #toString()}
    317         // to get display name.
    318         @Override
    319         public String toString() {
    320             return mDisplayName;
    321         }
    322     }
    323 
    324     static final class KeyboardLayoutSetAdapter extends ArrayAdapter<KeyboardLayoutSetItem> {
    325         public KeyboardLayoutSetAdapter(final Context context) {
    326             super(context, android.R.layout.simple_spinner_item);
    327             setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
    328 
    329             final String[] predefinedKeyboardLayoutSet = context.getResources().getStringArray(
    330                     R.array.predefined_layouts);
    331             // TODO: Should filter out already existing combinations of locale and layout.
    332             for (final String layout : predefinedKeyboardLayoutSet) {
    333                 // This is a dummy subtype with NO_LANGUAGE, only for display.
    334                 final InputMethodSubtype subtype =
    335                         AdditionalSubtypeUtils.createDummyAdditionalSubtype(
    336                                 SubtypeLocaleUtils.NO_LANGUAGE, layout);
    337                 add(new KeyboardLayoutSetItem(subtype));
    338             }
    339         }
    340     }
    341 }
    342