• 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 =
82                 (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
83         mInputMethodManager =
84                 (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
85     }
86 
87     @Override
onCreateInternal()88     protected void onCreateInternal() {
89         super.onCreateInternal();
90 
91         ConfirmationDialogFragment dialogFragment = (ConfirmationDialogFragment)
92                 getFragmentController().findDialogByTag(DIRECT_BOOT_WARN_DIALOG_TAG);
93         ConfirmationDialogFragment.resetListeners(dialogFragment,
94                 mDirectBootWarnConfirmListener,
95                 mRejectListener,
96                 /* neutralListener= */ null);
97 
98         dialogFragment = (ConfirmationDialogFragment) getFragmentController()
99                 .findDialogByTag(SECURITY_WARN_DIALOG_TAG);
100         ConfirmationDialogFragment.resetListeners(dialogFragment,
101                 mSecurityWarnDialogConfirmListener,
102                 mRejectListener,
103                 /* neutralListener= */ null);
104     }
105 
106     @Override
getPreferenceType()107     protected Class<PreferenceGroup> getPreferenceType() {
108         return PreferenceGroup.class;
109     }
110 
111     @Override
updateState(PreferenceGroup preferenceGroup)112     protected void updateState(PreferenceGroup preferenceGroup) {
113         List<String> permittedInputMethods = mDevicePolicyManager
114                 .getPermittedInputMethodsForCurrentUser();
115         Set<String> permittedInputMethodsSet = permittedInputMethods == null ? null : new HashSet<>(
116                 permittedInputMethods);
117 
118         preferenceGroup.removeAll();
119 
120         List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getInputMethodList();
121         if (inputMethodInfos == null || inputMethodInfos.size() == 0) {
122             return;
123         }
124 
125         Collections.sort(inputMethodInfos, Comparator.comparing(
126                 (InputMethodInfo a) -> InputMethodUtil.getPackageLabel(mPackageManager, a))
127                 .thenComparing((InputMethodInfo a) -> InputMethodUtil.getSummaryString(getContext(),
128                         mInputMethodManager, a)));
129 
130         for (InputMethodInfo inputMethodInfo : inputMethodInfos) {
131             if (!isInputMethodAllowedByOrganization(permittedInputMethodsSet, inputMethodInfo)) {
132                 continue;
133             }
134             // Hide "Google voice typing" IME.
135             if (inputMethodInfo.getPackageName().equals(InputMethodUtil.GOOGLE_VOICE_TYPING)) {
136                 continue;
137             }
138 
139             preferenceGroup.addPreference(createSwitchPreference(inputMethodInfo));
140         }
141     }
142 
isInputMethodAllowedByOrganization(Set<String> permittedList, InputMethodInfo inputMethodInfo)143     private boolean isInputMethodAllowedByOrganization(Set<String> permittedList,
144             InputMethodInfo inputMethodInfo) {
145         // permittedList is null means that all input methods are allowed.
146         return (permittedList == null) || permittedList.contains(inputMethodInfo.getPackageName());
147     }
148 
isInputMethodEnabled(InputMethodInfo inputMethodInfo)149     private boolean isInputMethodEnabled(InputMethodInfo inputMethodInfo) {
150         return InputMethodUtil.isInputMethodEnabled(
151                 getContext().getContentResolver(), inputMethodInfo);
152     }
153 
154     /**
155      * Check if given input method is the only enabled input method that can be a default system
156      * input method.
157      *
158      * @return {@code true} if input method is the only input method that can be a default system
159      * input method.
160      */
isOnlyEnabledDefaultInputMethod(InputMethodInfo inputMethodInfo)161     private boolean isOnlyEnabledDefaultInputMethod(InputMethodInfo inputMethodInfo) {
162         if (!inputMethodInfo.isDefault(getContext())) {
163             return false;
164         }
165 
166         List<InputMethodInfo> inputMethodInfos = mInputMethodManager.getEnabledInputMethodList();
167 
168         for (InputMethodInfo imi : inputMethodInfos) {
169             if (!imi.isDefault(getContext())) {
170                 continue;
171             }
172 
173             if (!imi.getId().equals(inputMethodInfo.getId())) {
174                 return false;
175             }
176         }
177 
178         return true;
179     }
180 
181     /**
182      * Create a SwitchPreference to enable/disable an input method.
183      *
184      * @return {@code SwitchPreference} which allows a user to enable/disable an input method.
185      */
createSwitchPreference(InputMethodInfo inputMethodInfo)186     private SwitchPreference createSwitchPreference(InputMethodInfo inputMethodInfo) {
187         SwitchPreference switchPreference = new CarUiSwitchPreference(getContext());
188         switchPreference.setKey(String.valueOf(inputMethodInfo.getId()));
189         switchPreference.setIcon(InputMethodUtil.getPackageIcon(mPackageManager, inputMethodInfo));
190         switchPreference.setTitle(InputMethodUtil.getPackageLabel(mPackageManager,
191                 inputMethodInfo));
192         switchPreference.setChecked(InputMethodUtil.isInputMethodEnabled(getContext()
193                 .getContentResolver(), inputMethodInfo));
194         switchPreference.setSummary(InputMethodUtil.getSummaryString(getContext(),
195                 mInputMethodManager, inputMethodInfo));
196 
197         // A switch preference for any disabled IME should be enabled. This is due to the
198         // possibility of having only one default IME that is disabled, which would prevent the IME
199         // from being enabled without another default input method that is enabled being present.
200         if (!isInputMethodEnabled(inputMethodInfo)) {
201             switchPreference.setEnabled(true);
202         } else {
203             switchPreference.setEnabled(!isOnlyEnabledDefaultInputMethod(inputMethodInfo));
204         }
205 
206         switchPreference.setOnPreferenceChangeListener((switchPref, newValue) -> {
207             boolean enable = (boolean) newValue;
208             if (enable) {
209                 showSecurityWarnDialog(inputMethodInfo);
210             } else {
211                 InputMethodUtil.disableInputMethod(getContext(), mInputMethodManager,
212                         inputMethodInfo);
213                 refreshUi();
214             }
215             return false;
216         });
217         return switchPreference;
218     }
219 
showDirectBootWarnDialog(InputMethodInfo inputMethodInfo)220     private void showDirectBootWarnDialog(InputMethodInfo inputMethodInfo) {
221         ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
222                 .setMessage(getContext().getString(R.string.direct_boot_unaware_dialog_message_car))
223                 .setPositiveButton(android.R.string.ok, mDirectBootWarnConfirmListener)
224                 .setNegativeButton(android.R.string.cancel, mRejectListener)
225                 .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
226                 .build();
227 
228         getFragmentController().showDialog(dialog, DIRECT_BOOT_WARN_DIALOG_TAG);
229     }
230 
showSecurityWarnDialog(InputMethodInfo inputMethodInfo)231     private void showSecurityWarnDialog(InputMethodInfo inputMethodInfo) {
232         CharSequence label = inputMethodInfo.loadLabel(mPackageManager);
233 
234         ConfirmationDialogFragment dialog = new ConfirmationDialogFragment.Builder(getContext())
235                 .setTitle(android.R.string.dialog_alert_title)
236                 .setMessage(getContext().getString(R.string.ime_security_warning, label))
237                 .setPositiveButton(android.R.string.ok, mSecurityWarnDialogConfirmListener)
238                 .setNegativeButton(android.R.string.cancel, mRejectListener)
239                 .addArgumentParcelable(KEY_INPUT_METHOD_INFO, inputMethodInfo)
240                 .build();
241 
242         getFragmentController().showDialog(dialog, SECURITY_WARN_DIALOG_TAG);
243     }
244 }
245