• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.car.settings.inputmethod;
18 
19 import android.app.admin.DevicePolicyManager;
20 import android.car.drivingstate.CarUxRestrictions;
21 import android.content.Context;
22 import android.content.pm.PackageManager;
23 import android.view.inputmethod.InputMethodInfo;
24 import android.view.inputmethod.InputMethodManager;
25 
26 import androidx.annotation.VisibleForTesting;
27 import androidx.preference.PreferenceGroup;
28 import androidx.preference.SwitchPreference;
29 
30 import com.android.car.settings.R;
31 import com.android.car.settings.common.ConfirmationDialogFragment;
32 import com.android.car.settings.common.FragmentController;
33 import com.android.car.settings.common.PreferenceController;
34 import com.android.car.ui.preference.CarUiSwitchPreference;
35 
36 import java.util.Collections;
37 import java.util.Comparator;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Set;
41 
42 /** Updates the available keyboard list. */
43 public class KeyboardManagementPreferenceController extends
44         PreferenceController<PreferenceGroup> {
45     @VisibleForTesting
46     static final String DIRECT_BOOT_WARN_DIALOG_TAG = "DirectBootWarnDialog";
47     @VisibleForTesting
48     static final String SECURITY_WARN_DIALOG_TAG = "SecurityWarnDialog";
49     private static final String KEY_INPUT_METHOD_INFO = "INPUT_METHOD_INFO";
50     private final InputMethodManager mInputMethodManager;
51     private final DevicePolicyManager mDevicePolicyManager;
52     private final PackageManager mPackageManager;
53     private final ConfirmationDialogFragment.ConfirmListener mDirectBootWarnConfirmListener =
54             args -> {
55                 InputMethodInfo inputMethodInfo = args.getParcelable(KEY_INPUT_METHOD_INFO);
56                 InputMethodUtil.enableInputMethod(getContext().getContentResolver(),
57                         inputMethodInfo);
58                 refreshUi();
59             };
60     private final ConfirmationDialogFragment.RejectListener mRejectListener = args ->
61             refreshUi();
62     private final ConfirmationDialogFragment.ConfirmListener mSecurityWarnDialogConfirmListener =
63             args -> {
64                 InputMethodInfo inputMethodInfo = args.getParcelable(KEY_INPUT_METHOD_INFO);
65                 // The user confirmed to enable a 3rd party IME, but we might need to prompt if
66                 // it's not
67                 // Direct Boot aware.
68                 if (inputMethodInfo.getServiceInfo().directBootAware) {
69                     InputMethodUtil.enableInputMethod(getContext().getContentResolver(),
70                             inputMethodInfo);
71                     refreshUi();
72                 } else {
73                     showDirectBootWarnDialog(inputMethodInfo);
74                 }
75             };
76 
KeyboardManagementPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)77     public KeyboardManagementPreferenceController(Context context, String preferenceKey,
78             FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
79         super(context, preferenceKey, fragmentController, uxRestrictions);
80         mPackageManager = context.getPackageManager();
81         mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class);
82         mInputMethodManager = context.getSystemService(InputMethodManager.class);
83     }
84 
85     @Override
onCreateInternal()86     protected void onCreateInternal() {
87         super.onCreateInternal();
88 
89         ConfirmationDialogFragment dialogFragment = (ConfirmationDialogFragment)
90                 getFragmentController().findDialogByTag(DIRECT_BOOT_WARN_DIALOG_TAG);
91         ConfirmationDialogFragment.resetListeners(dialogFragment,
92                 mDirectBootWarnConfirmListener,
93                 mRejectListener,
94                 /* neutralListener= */ null);
95 
96         dialogFragment = (ConfirmationDialogFragment) getFragmentController()
97                 .findDialogByTag(SECURITY_WARN_DIALOG_TAG);
98         ConfirmationDialogFragment.resetListeners(dialogFragment,
99                 mSecurityWarnDialogConfirmListener,
100                 mRejectListener,
101                 /* neutralListener= */ null);
102     }
103 
104     @Override
getPreferenceType()105     protected Class<PreferenceGroup> getPreferenceType() {
106         return PreferenceGroup.class;
107     }
108 
109     @Override
updateState(PreferenceGroup preferenceGroup)110     protected void updateState(PreferenceGroup preferenceGroup) {
111         List<String> permittedInputMethods = mDevicePolicyManager
112                 .getPermittedInputMethodsForCurrentUser();
113         Set<String> permittedInputMethodsSet = permittedInputMethods == null ? null : new HashSet<>(
114                 permittedInputMethods);
115 
116         preferenceGroup.removeAll();
117 
118         List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getInputMethodList();
119         if (inputMethodInfos == null || inputMethodInfos.size() == 0) {
120             return;
121         }
122 
123         Collections.sort(inputMethodInfos, Comparator.comparing(
124                 (InputMethodInfo a) -> InputMethodUtil.getPackageLabel(mPackageManager, a))
125                 .thenComparing((InputMethodInfo a) -> InputMethodUtil.getSummaryString(getContext(),
126                         mInputMethodManager, a)));
127 
128         for (InputMethodInfo inputMethodInfo : inputMethodInfos) {
129             if (!isInputMethodAllowedByOrganization(permittedInputMethodsSet, inputMethodInfo)) {
130                 continue;
131             }
132             // Hide "Google voice typing" IME.
133             if (inputMethodInfo.getPackageName().equals(InputMethodUtil.GOOGLE_VOICE_TYPING)) {
134                 continue;
135             }
136 
137             preferenceGroup.addPreference(createSwitchPreference(inputMethodInfo));
138         }
139     }
140 
isInputMethodAllowedByOrganization(Set<String> permittedList, InputMethodInfo inputMethodInfo)141     private boolean isInputMethodAllowedByOrganization(Set<String> permittedList,
142             InputMethodInfo inputMethodInfo) {
143         // permittedList is null means that all input methods are allowed.
144         return (permittedList == null) || permittedList.contains(inputMethodInfo.getPackageName());
145     }
146 
isInputMethodEnabled(InputMethodInfo inputMethodInfo)147     private boolean isInputMethodEnabled(InputMethodInfo inputMethodInfo) {
148         return InputMethodUtil.isInputMethodEnabled(
149                 getContext().getContentResolver(), inputMethodInfo);
150     }
151 
152     /**
153      * Check if given input method is the only enabled input method that can be a default system
154      * input method.
155      *
156      * @return {@code true} if input method is the only input method that can be a default system
157      * input method.
158      */
isOnlyEnabledDefaultInputMethod(InputMethodInfo inputMethodInfo)159     private boolean isOnlyEnabledDefaultInputMethod(InputMethodInfo inputMethodInfo) {
160         if (!inputMethodInfo.isDefault(getContext())) {
161             return false;
162         }
163 
164         List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getEnabledInputMethodList();
165 
166         for (InputMethodInfo imi : inputMethodInfos) {
167             if (!imi.isDefault(getContext())) {
168                 continue;
169             }
170 
171             if (!imi.getId().equals(inputMethodInfo.getId())) {
172                 return false;
173             }
174         }
175 
176         return true;
177     }
178 
179     /**
180      * Create a SwitchPreference to enable/disable an input method.
181      *
182      * @return {@code SwitchPreference} which allows a user to enable/disable an input method.
183      */
createSwitchPreference(InputMethodInfo inputMethodInfo)184     private SwitchPreference createSwitchPreference(InputMethodInfo inputMethodInfo) {
185         SwitchPreference switchPreference = new CarUiSwitchPreference(getContext());
186         switchPreference.setKey(String.valueOf(inputMethodInfo.getId()));
187         switchPreference.setIcon(InputMethodUtil.getPackageIcon(mPackageManager, inputMethodInfo));
188         switchPreference.setTitle(InputMethodUtil.getPackageLabel(mPackageManager,
189                 inputMethodInfo));
190         switchPreference.setChecked(InputMethodUtil.isInputMethodEnabled(getContext()
191                 .getContentResolver(), inputMethodInfo));
192         switchPreference.setSummary(InputMethodUtil.getSummaryString(getContext(),
193                 mInputMethodManager, inputMethodInfo));
194 
195         // A switch preference for any disabled IME should be enabled. This is due to the
196         // possibility of having only one default IME that is disabled, which would prevent the IME
197         // from being enabled without another default input method that is enabled being present.
198         if (!isInputMethodEnabled(inputMethodInfo)) {
199             switchPreference.setEnabled(true);
200         } else {
201             switchPreference.setEnabled(!isOnlyEnabledDefaultInputMethod(inputMethodInfo));
202         }
203 
204         switchPreference.setOnPreferenceChangeListener((switchPref, newValue) -> {
205             boolean enable = (boolean) newValue;
206             if (enable) {
207                 showSecurityWarnDialog(inputMethodInfo);
208             } else {
209                 InputMethodUtil.disableInputMethod(getContext(), mInputMethodManager,
210                         inputMethodInfo);
211                 refreshUi();
212             }
213             return false;
214         });
215         return switchPreference;
216     }
217 
showDirectBootWarnDialog(InputMethodInfo inputMethodInfo)218     private void showDirectBootWarnDialog(InputMethodInfo inputMethodInfo) {
219         ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
220                 .setMessage(getContext().getString(R.string.direct_boot_unaware_dialog_message_car))
221                 .setPositiveButton(android.R.string.ok, mDirectBootWarnConfirmListener)
222                 .setNegativeButton(android.R.string.cancel, mRejectListener)
223                 .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
224                 .build();
225 
226         getFragmentController().showDialog(dialog, DIRECT_BOOT_WARN_DIALOG_TAG);
227     }
228 
showSecurityWarnDialog(InputMethodInfo inputMethodInfo)229     private void showSecurityWarnDialog(InputMethodInfo inputMethodInfo) {
230         CharSequence label = inputMethodInfo.loadLabel(mPackageManager);
231 
232         ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
233                 .setTitle(android.R.string.dialog_alert_title)
234                 .setMessage(getContext().getString(R.string.ime_security_warning, label))
235                 .setPositiveButton(android.R.string.ok, mSecurityWarnDialogConfirmListener)
236                 .setNegativeButton(android.R.string.cancel, mRejectListener)
237                 .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
238                 .build();
239 
240         getFragmentController().showDialog(dialog, SECURITY_WARN_DIALOG_TAG);
241     }
242 }
243