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