1 /*
2  * Copyright (C) 2011 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.app.settings.SettingsEnums;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.pm.ApplicationInfo;
23 import android.os.Bundle;
24 import android.provider.Settings;
25 import android.util.Log;
26 import android.view.textservice.SpellCheckerInfo;
27 import android.view.textservice.SpellCheckerSubtype;
28 import android.view.textservice.TextServicesManager;
29 import android.widget.Switch;
30 
31 import androidx.appcompat.app.AlertDialog;
32 import androidx.preference.Preference;
33 import androidx.preference.Preference.OnPreferenceChangeListener;
34 import androidx.preference.Preference.OnPreferenceClickListener;
35 import androidx.preference.PreferenceScreen;
36 
37 import com.android.settings.R;
38 import com.android.settings.SettingsActivity;
39 import com.android.settings.SettingsPreferenceFragment;
40 import com.android.settings.widget.SwitchBar;
41 import com.android.settings.widget.SwitchBar.OnSwitchChangeListener;
42 
43 public class SpellCheckersSettings extends SettingsPreferenceFragment
44         implements OnSwitchChangeListener, OnPreferenceClickListener, OnPreferenceChangeListener {
45     private static final String TAG = SpellCheckersSettings.class.getSimpleName();
46     private static final boolean DBG = false;
47 
48     private static final String KEY_SPELL_CHECKER_LANGUAGE = "spellchecker_language";
49     private static final String KEY_DEFAULT_SPELL_CHECKER = "default_spellchecker";
50     private static final int ITEM_ID_USE_SYSTEM_LANGUAGE = 0;
51 
52     private SwitchBar mSwitchBar;
53     private Preference mSpellCheckerLanaguagePref;
54     private AlertDialog mDialog = null;
55     private SpellCheckerInfo mCurrentSci;
56     private SpellCheckerInfo[] mEnabledScis;
57     private TextServicesManager mTsm;
58 
59     @Override
getMetricsCategory()60     public int getMetricsCategory() {
61         return SettingsEnums.INPUTMETHOD_SPELL_CHECKERS;
62     }
63 
64     @Override
onCreate(final Bundle icicle)65     public void onCreate(final Bundle icicle) {
66         super.onCreate(icicle);
67 
68         addPreferencesFromResource(R.xml.spellchecker_prefs);
69         mSpellCheckerLanaguagePref = findPreference(KEY_SPELL_CHECKER_LANGUAGE);
70         mSpellCheckerLanaguagePref.setOnPreferenceClickListener(this);
71 
72         mTsm = (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
73         mCurrentSci = mTsm.getCurrentSpellChecker();
74         mEnabledScis = mTsm.getEnabledSpellCheckers();
75         populatePreferenceScreen();
76     }
77 
populatePreferenceScreen()78     private void populatePreferenceScreen() {
79         final SpellCheckerPreference pref = new SpellCheckerPreference(getPrefContext(),
80                 mEnabledScis);
81         pref.setTitle(R.string.default_spell_checker);
82         final int count = (mEnabledScis == null) ? 0 : mEnabledScis.length;
83         if (count > 0) {
84             pref.setSummary("%s");
85         } else {
86             pref.setSummary(R.string.spell_checker_not_selected);
87         }
88         pref.setKey(KEY_DEFAULT_SPELL_CHECKER);
89         pref.setOnPreferenceChangeListener(this);
90         getPreferenceScreen().addPreference(pref);
91     }
92 
93     @Override
onResume()94     public void onResume() {
95         super.onResume();
96         mSwitchBar = ((SettingsActivity) getActivity()).getSwitchBar();
97         mSwitchBar.setSwitchBarText(
98                 R.string.spell_checker_master_switch_title,
99                 R.string.spell_checker_master_switch_title);
100         mSwitchBar.show();
101         mSwitchBar.addOnSwitchChangeListener(this);
102         updatePreferenceScreen();
103     }
104 
105     @Override
onPause()106     public void onPause() {
107         super.onPause();
108         mSwitchBar.removeOnSwitchChangeListener(this);
109     }
110 
111     @Override
onSwitchChanged(final Switch switchView, final boolean isChecked)112     public void onSwitchChanged(final Switch switchView, final boolean isChecked) {
113         Settings.Secure.putInt(getContentResolver(), Settings.Secure.SPELL_CHECKER_ENABLED,
114                 isChecked ? 1 : 0);
115         updatePreferenceScreen();
116     }
117 
updatePreferenceScreen()118     private void updatePreferenceScreen() {
119         mCurrentSci = mTsm.getCurrentSpellChecker();
120         final boolean isSpellCheckerEnabled = mTsm.isSpellCheckerEnabled();
121         mSwitchBar.setChecked(isSpellCheckerEnabled);
122 
123         final SpellCheckerSubtype currentScs;
124         if (mCurrentSci != null) {
125             currentScs = mTsm.getCurrentSpellCheckerSubtype(
126                     false /* allowImplicitlySelectedSubtype */);
127         } else {
128             currentScs = null;
129         }
130         mSpellCheckerLanaguagePref.setSummary(getSpellCheckerSubtypeLabel(mCurrentSci, currentScs));
131 
132         final PreferenceScreen screen = getPreferenceScreen();
133         final int count = screen.getPreferenceCount();
134         for (int index = 0; index < count; index++) {
135             final Preference preference = screen.getPreference(index);
136             preference.setEnabled(isSpellCheckerEnabled);
137             if (preference instanceof SpellCheckerPreference) {
138                 final SpellCheckerPreference pref = (SpellCheckerPreference) preference;
139                 pref.setSelected(mCurrentSci);
140             }
141         }
142         mSpellCheckerLanaguagePref.setEnabled(isSpellCheckerEnabled && mCurrentSci != null);
143     }
144 
getSpellCheckerSubtypeLabel(final SpellCheckerInfo sci, final SpellCheckerSubtype subtype)145     private CharSequence getSpellCheckerSubtypeLabel(final SpellCheckerInfo sci,
146             final SpellCheckerSubtype subtype) {
147         if (sci == null) {
148             return getString(R.string.spell_checker_not_selected);
149         }
150         if (subtype == null) {
151             return getString(R.string.use_system_language_to_select_input_method_subtypes);
152         }
153         return subtype.getDisplayName(
154                 getActivity(), sci.getPackageName(), sci.getServiceInfo().applicationInfo);
155     }
156 
157     @Override
onPreferenceClick(final Preference pref)158     public boolean onPreferenceClick(final Preference pref) {
159         if (pref == mSpellCheckerLanaguagePref) {
160             showChooseLanguageDialog();
161             return true;
162         }
163         return false;
164     }
165 
166     @Override
onPreferenceChange(Preference preference, Object newValue)167     public boolean onPreferenceChange(Preference preference, Object newValue) {
168         final SpellCheckerInfo sci = (SpellCheckerInfo) newValue;
169         final boolean isSystemApp =
170                 (sci.getServiceInfo().applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
171         if (isSystemApp) {
172             changeCurrentSpellChecker(sci);
173             return true;
174         } else {
175             showSecurityWarnDialog(sci);
176             return false;
177         }
178     }
179 
convertSubtypeIndexToDialogItemId(final int index)180     private static int convertSubtypeIndexToDialogItemId(final int index) {
181         return index + 1;
182     }
183 
convertDialogItemIdToSubtypeIndex(final int item)184     private static int convertDialogItemIdToSubtypeIndex(final int item) {
185         return item - 1;
186     }
187 
showChooseLanguageDialog()188     private void showChooseLanguageDialog() {
189         if (mDialog != null && mDialog.isShowing()) {
190             mDialog.dismiss();
191         }
192         final SpellCheckerInfo currentSci = mTsm.getCurrentSpellChecker();
193         if (currentSci == null) {
194             // This can happen in some situations.  One example is that the package that the current
195             // spell checker belongs to was uninstalled or being in background.
196             return;
197         }
198         final SpellCheckerSubtype currentScs = mTsm.getCurrentSpellCheckerSubtype(
199                 false /* allowImplicitlySelectedSubtype */);
200         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
201         builder.setTitle(R.string.phone_language);
202         final int subtypeCount = currentSci.getSubtypeCount();
203         final CharSequence[] items = new CharSequence[subtypeCount + 1 /* default */];
204         items[ITEM_ID_USE_SYSTEM_LANGUAGE] = getSpellCheckerSubtypeLabel(currentSci, null);
205         int checkedItemId = ITEM_ID_USE_SYSTEM_LANGUAGE;
206         for (int index = 0; index < subtypeCount; ++index) {
207             final SpellCheckerSubtype subtype = currentSci.getSubtypeAt(index);
208             final int itemId = convertSubtypeIndexToDialogItemId(index);
209             items[itemId] = getSpellCheckerSubtypeLabel(currentSci, subtype);
210             if (subtype.equals(currentScs)) {
211                 checkedItemId = itemId;
212             }
213         }
214         builder.setSingleChoiceItems(items, checkedItemId, new AlertDialog.OnClickListener() {
215             @Override
216             public void onClick(final DialogInterface dialog, final int item) {
217                 final int subtypeId;
218                 if (item == ITEM_ID_USE_SYSTEM_LANGUAGE) {
219                     subtypeId = SpellCheckerSubtype.SUBTYPE_ID_NONE;
220                 } else {
221                     final int index = convertDialogItemIdToSubtypeIndex(item);
222                     subtypeId = currentSci.getSubtypeAt(index).hashCode();
223                 }
224 
225                 Settings.Secure.putInt(getContentResolver(),
226                         Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE, subtypeId);
227 
228                 if (DBG) {
229                     final SpellCheckerSubtype subtype = mTsm.getCurrentSpellCheckerSubtype(
230                             true /* allowImplicitlySelectedSubtype */);
231                     Log.d(TAG, "Current spell check locale is "
232                             + subtype == null ? "null" : subtype.getLocale());
233                 }
234                 dialog.dismiss();
235                 updatePreferenceScreen();
236             }
237         });
238         mDialog = builder.create();
239         mDialog.show();
240     }
241 
showSecurityWarnDialog(final SpellCheckerInfo sci)242     private void showSecurityWarnDialog(final SpellCheckerInfo sci) {
243         if (mDialog != null && mDialog.isShowing()) {
244             mDialog.dismiss();
245         }
246         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
247         builder.setTitle(android.R.string.dialog_alert_title);
248         builder.setMessage(getString(R.string.spellchecker_security_warning,
249                 sci.loadLabel(getPackageManager())));
250         builder.setCancelable(true);
251         builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
252             @Override
253             public void onClick(final DialogInterface dialog, final int which) {
254                 changeCurrentSpellChecker(sci);
255             }
256         });
257         builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
258             @Override
259             public void onClick(final DialogInterface dialog, final int which) {
260             }
261         });
262         mDialog = builder.create();
263         mDialog.show();
264     }
265 
changeCurrentSpellChecker(final SpellCheckerInfo sci)266     private void changeCurrentSpellChecker(final SpellCheckerInfo sci) {
267         Settings.Secure.putString(getContentResolver(), Settings.Secure.SELECTED_SPELL_CHECKER,
268                 sci.getId());
269         // Reset the spell checker subtype
270         Settings.Secure.putInt(getContentResolver(), Settings.Secure.SELECTED_SPELL_CHECKER_SUBTYPE,
271                 SpellCheckerSubtype.SUBTYPE_ID_NONE);
272         if (DBG) {
273             Log.d(TAG, "Current spell check is " + mTsm.getCurrentSpellChecker().getId());
274         }
275         updatePreferenceScreen();
276     }
277 }
278