Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2010 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.inputmethod;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.pm.PackageManager;
     22 import android.content.res.Configuration;
     23 import android.os.Bundle;
     24 import android.preference.Preference;
     25 import android.preference.Preference.OnPreferenceChangeListener;
     26 import android.preference.PreferenceCategory;
     27 import android.preference.PreferenceScreen;
     28 import android.preference.TwoStatePreference;
     29 import android.text.TextUtils;
     30 import android.view.inputmethod.InputMethodInfo;
     31 import android.view.inputmethod.InputMethodManager;
     32 import android.view.inputmethod.InputMethodSubtype;
     33 
     34 import com.android.settings.R;
     35 import com.android.settings.SettingsPreferenceFragment;
     36 
     37 import java.text.Collator;
     38 import java.util.ArrayList;
     39 import java.util.Collections;
     40 import java.util.Comparator;
     41 import java.util.HashMap;
     42 import java.util.List;
     43 
     44 public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment
     45         implements OnPreferenceChangeListener {
     46     private boolean mHaveHardKeyboard;
     47     private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
     48             new HashMap<>();
     49     private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
     50     private InputMethodManager mImm;
     51     // TODO: Change mInputMethodInfoList to Map
     52     private List<InputMethodInfo> mInputMethodInfoList;
     53     private Collator mCollator;
     54 
     55     @Override
     56     public void onCreate(final Bundle icicle) {
     57         super.onCreate(icicle);
     58         mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
     59         final Configuration config = getResources().getConfiguration();
     60         mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
     61 
     62         // Input method id should be available from an Intent when this preference is launched as a
     63         // single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available
     64         // from a preference argument when the preference is launched as a part of the other
     65         // Activity (like a right pane of 2-pane Settings app)
     66         final String targetImi = getStringExtraFromIntentOrArguments(
     67                 android.provider.Settings.EXTRA_INPUT_METHOD_ID);
     68 
     69         mInputMethodInfoList = mImm.getInputMethodList();
     70         mCollator = Collator.getInstance();
     71 
     72         final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity());
     73         final int imiCount = mInputMethodInfoList.size();
     74         for (int index = 0; index < imiCount; ++index) {
     75             final InputMethodInfo imi = mInputMethodInfoList.get(index);
     76             // Add subtype preferences of this IME when it is specified or no IME is specified.
     77             if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
     78                 addInputMethodSubtypePreferences(imi, root);
     79             }
     80         }
     81         setPreferenceScreen(root);
     82     }
     83 
     84     private String getStringExtraFromIntentOrArguments(final String name) {
     85         final Intent intent = getActivity().getIntent();
     86         final String fromIntent = intent.getStringExtra(name);
     87         if (fromIntent != null) {
     88             return fromIntent;
     89         }
     90         final Bundle arguments = getArguments();
     91         return (arguments == null) ? null : arguments.getString(name);
     92     }
     93 
     94     @Override
     95     public void onActivityCreated(final Bundle icicle) {
     96         super.onActivityCreated(icicle);
     97         final String title = getStringExtraFromIntentOrArguments(Intent.EXTRA_TITLE);
     98         if (!TextUtils.isEmpty(title)) {
     99             getActivity().setTitle(title);
    100         }
    101     }
    102 
    103     @Override
    104     public void onResume() {
    105         super.onResume();
    106         // Refresh internal states in mInputMethodSettingValues to keep the latest
    107         // "InputMethodInfo"s and "InputMethodSubtype"s
    108         InputMethodSettingValuesWrapper
    109                 .getInstance(getActivity()).refreshAllInputMethodAndSubtypes();
    110         InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(
    111                 this, getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
    112         updateAutoSelectionPreferences();
    113     }
    114 
    115     @Override
    116     public void onPause() {
    117         super.onPause();
    118         // Clear all subtypes of all IMEs to make sure
    119         updateImplicitlyEnabledSubtypes(null /* targetImiId */, false /* check */);
    120         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
    121                 mInputMethodInfoList, mHaveHardKeyboard);
    122     }
    123 
    124     @Override
    125     public boolean onPreferenceChange(final Preference pref, final Object newValue) {
    126         if (!(newValue instanceof Boolean)) {
    127             return true; // Invoke default behavior.
    128         }
    129         final boolean isChecking = (Boolean) newValue;
    130         for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
    131             // An auto select subtype preference is changing.
    132             if (mAutoSelectionPrefsMap.get(imiId) == pref) {
    133                 final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
    134                 autoSelectionPref.setChecked(isChecking);
    135                 // Enable or disable subtypes depending on the auto selection preference.
    136                 setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
    137                 return false;
    138             }
    139         }
    140         // A subtype preference is changing.
    141         if (pref instanceof InputMethodSubtypePreference) {
    142             final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
    143             subtypePref.setChecked(isChecking);
    144             if (!subtypePref.isChecked()) {
    145                 // It takes care of the case where no subtypes are explicitly enabled then the auto
    146                 // selection preference is going to be checked.
    147                 updateAutoSelectionPreferences();
    148             }
    149             return false;
    150         }
    151         return true; // Invoke default behavior.
    152     }
    153 
    154     private void addInputMethodSubtypePreferences(final InputMethodInfo imi,
    155             final PreferenceScreen root) {
    156         final Context context = getActivity();
    157         final int subtypeCount = imi.getSubtypeCount();
    158         if (subtypeCount <= 1) {
    159             return;
    160         }
    161         final String imiId = imi.getId();
    162         final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context);
    163         root.addPreference(keyboardSettingsCategory);
    164         final PackageManager pm = getPackageManager();
    165         final CharSequence label = imi.loadLabel(pm);
    166 
    167         keyboardSettingsCategory.setTitle(label);
    168         keyboardSettingsCategory.setKey(imiId);
    169         // TODO: Use toggle Preference if images are ready.
    170         final TwoStatePreference autoSelectionPref = new SwitchWithNoTextPreference(context);
    171         mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
    172         keyboardSettingsCategory.addPreference(autoSelectionPref);
    173         autoSelectionPref.setOnPreferenceChangeListener(this);
    174 
    175         final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context);
    176         activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
    177         root.addPreference(activeInputMethodsCategory);
    178 
    179         CharSequence autoSubtypeLabel = null;
    180         final ArrayList<Preference> subtypePreferences = new ArrayList<>();
    181         for (int index = 0; index < subtypeCount; ++index) {
    182             final InputMethodSubtype subtype = imi.getSubtypeAt(index);
    183             if (subtype.overridesImplicitlyEnabledSubtype()) {
    184                 if (autoSubtypeLabel == null) {
    185                     autoSubtypeLabel = subtype.getDisplayName(
    186                             context, imi.getPackageName(), imi.getServiceInfo().applicationInfo);
    187                 }
    188             } else {
    189                 final Preference subtypePref = new InputMethodSubtypePreference(
    190                         context, subtype, imi);
    191                 subtypePreferences.add(subtypePref);
    192             }
    193         }
    194         Collections.sort(subtypePreferences, new Comparator<Preference>() {
    195             @Override
    196             public int compare(final Preference lhs, final Preference rhs) {
    197                 if (lhs instanceof InputMethodSubtypePreference) {
    198                     return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
    199                 }
    200                 return lhs.compareTo(rhs);
    201             }
    202         });
    203         final int prefCount = subtypePreferences.size();
    204         for (int index = 0; index < prefCount; ++index) {
    205             final Preference pref = subtypePreferences.get(index);
    206             activeInputMethodsCategory.addPreference(pref);
    207             pref.setOnPreferenceChangeListener(this);
    208             InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
    209         }
    210         mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
    211         if (TextUtils.isEmpty(autoSubtypeLabel)) {
    212             autoSelectionPref.setTitle(
    213                     R.string.use_system_language_to_select_input_method_subtypes);
    214         } else {
    215             autoSelectionPref.setTitle(autoSubtypeLabel);
    216         }
    217     }
    218 
    219     private boolean isNoSubtypesExplicitlySelected(final String imiId) {
    220         final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
    221         for (final Preference pref : subtypePrefs) {
    222             if (pref instanceof TwoStatePreference && ((TwoStatePreference)pref).isChecked()) {
    223                 return false;
    224             }
    225         }
    226         return true;
    227     }
    228 
    229     private void setAutoSelectionSubtypesEnabled(final String imiId,
    230             final boolean autoSelectionEnabled) {
    231         final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
    232         if (autoSelectionPref == null) {
    233             return;
    234         }
    235         autoSelectionPref.setChecked(autoSelectionEnabled);
    236         final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
    237         for (final Preference pref : subtypePrefs) {
    238             if (pref instanceof TwoStatePreference) {
    239                 // When autoSelectionEnabled is true, all subtype prefs need to be disabled with
    240                 // implicitly checked subtypes. In case of false, all subtype prefs need to be
    241                 // enabled.
    242                 pref.setEnabled(!autoSelectionEnabled);
    243                 if (autoSelectionEnabled) {
    244                     ((TwoStatePreference)pref).setChecked(false);
    245                 }
    246             }
    247         }
    248         if (autoSelectionEnabled) {
    249             InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
    250                     this, getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
    251             updateImplicitlyEnabledSubtypes(imiId, true /* check */);
    252         }
    253     }
    254 
    255     private void updateImplicitlyEnabledSubtypes(final String targetImiId, final boolean check) {
    256         // When targetImiId is null, apply to all subtypes of all IMEs
    257         for (final InputMethodInfo imi : mInputMethodInfoList) {
    258             final String imiId = imi.getId();
    259             final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
    260             // No need to update implicitly enabled subtypes when the user has unchecked the
    261             // "subtype auto selection".
    262             if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
    263                 continue;
    264             }
    265             if (imiId.equals(targetImiId) || targetImiId == null) {
    266                 updateImplicitlyEnabledSubtypesOf(imi, check);
    267             }
    268         }
    269     }
    270 
    271     private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi, final boolean check) {
    272         final String imiId = imi.getId();
    273         final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
    274         final List<InputMethodSubtype> implicitlyEnabledSubtypes =
    275                 mImm.getEnabledInputMethodSubtypeList(imi, true);
    276         if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
    277             return;
    278         }
    279         for (final Preference pref : subtypePrefs) {
    280             if (!(pref instanceof TwoStatePreference)) {
    281                 continue;
    282             }
    283             final TwoStatePreference subtypePref = (TwoStatePreference)pref;
    284             subtypePref.setChecked(false);
    285             if (check) {
    286                 for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
    287                     final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
    288                     if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
    289                         subtypePref.setChecked(true);
    290                         break;
    291                     }
    292                 }
    293             }
    294         }
    295     }
    296 
    297     private void updateAutoSelectionPreferences() {
    298         for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
    299             setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
    300         }
    301         updateImplicitlyEnabledSubtypes(null /* targetImiId */, true /* check */);
    302     }
    303 }
    304