1 /*
2  * Copyright (C) 2016 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.admin.DevicePolicyManager;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.content.res.Configuration;
23 import android.os.Bundle;
24 import android.os.UserHandle;
25 import android.os.UserManager;
26 import android.provider.SearchIndexableResource;
27 import android.view.inputmethod.InputMethodInfo;
28 import android.view.inputmethod.InputMethodManager;
29 
30 import com.android.internal.annotations.VisibleForTesting;
31 import com.android.settings.R;
32 import com.android.settings.Utils;
33 import com.android.settings.dashboard.DashboardFragment;
34 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
35 import com.android.settings.search.BaseSearchIndexProvider;
36 import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtilCompat;
37 import com.android.settingslib.inputmethod.InputMethodPreference;
38 import com.android.settingslib.inputmethod.InputMethodSettingValuesWrapper;
39 import com.android.settingslib.search.SearchIndexable;
40 
41 import java.text.Collator;
42 import java.util.ArrayList;
43 import java.util.List;
44 
45 /**
46  * The fragment for on-screen keyboard settings which used to display user installed IMEs.
47  */
48 @SearchIndexable
49 public class AvailableVirtualKeyboardFragment extends DashboardFragment
50         implements InputMethodPreference.OnSavePreferenceListener {
51     private static final String TAG = "AvailableVirtualKeyboardFragment";
52 
53     @VisibleForTesting
54     final ArrayList<InputMethodPreference> mInputMethodPreferenceList = new ArrayList<>();
55 
56     @VisibleForTesting
57     InputMethodSettingValuesWrapper mInputMethodSettingValues;
58 
59     @VisibleForTesting
60     Context mUserAwareContext;
61 
62     private int mUserId;
63 
64     @Override
onCreatePreferences(Bundle bundle, String s)65     public void onCreatePreferences(Bundle bundle, String s) {
66         addPreferencesFromResource(R.xml.available_virtual_keyboard);
67         mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(mUserAwareContext);
68     }
69 
70     @Override
onAttach(Context context)71     public void onAttach(Context context) {
72         super.onAttach(context);
73         final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE);
74         final UserManager userManager = context.getSystemService(UserManager.class);
75         final int currentUserId = UserHandle.myUserId();
76         final int newUserId;
77         final Context newUserAwareContext;
78         switch (profileType) {
79             case ProfileSelectFragment.ProfileType.WORK: {
80                 // If the user is a managed profile user, use currentUserId directly. Or get the
81                 // managed profile userId instead.
82                 newUserId = userManager.isManagedProfile()
83                         ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId);
84                 newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0);
85                 break;
86             }
87             case ProfileSelectFragment.ProfileType.PRIVATE: {
88                 // If the user is a private profile user, use currentUserId directly. Or get the
89                 // private profile userId instead.
90                 newUserId = userManager.isPrivateProfile()
91                         ? currentUserId
92                         : Utils.getCurrentUserIdOfType(
93                                 userManager, ProfileSelectFragment.ProfileType.PRIVATE);
94                 newUserAwareContext = context.createContextAsUser(UserHandle.of(newUserId), 0);
95                 break;
96             }
97             case ProfileSelectFragment.ProfileType.PERSONAL: {
98                 // Use the parent user of the current user if the current user is profile.
99                 final UserHandle currentUser = UserHandle.of(currentUserId);
100                 final UserHandle userProfileParent = userManager.getProfileParent(currentUser);
101                 if (userProfileParent != null) {
102                     newUserId = userProfileParent.getIdentifier();
103                     newUserAwareContext = context.createContextAsUser(userProfileParent, 0);
104                 } else {
105                     newUserId = currentUserId;
106                     newUserAwareContext = context;
107                 }
108                 break;
109             }
110             default:
111                 newUserId = currentUserId;
112                 newUserAwareContext = context;
113         }
114         mUserId = newUserId;
115         mUserAwareContext = newUserAwareContext;
116     }
117 
118     @Override
onResume()119     public void onResume() {
120         super.onResume();
121         // Refresh internal states in mInputMethodSettingValues to keep the latest
122         // "InputMethodInfo"s and "InputMethodSubtype"s
123         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
124         updateInputMethodPreferenceViews();
125     }
126 
127     @Override
getPreferenceScreenResId()128     protected int getPreferenceScreenResId() {
129         return R.xml.available_virtual_keyboard;
130     }
131 
132     @Override
getLogTag()133     protected String getLogTag() {
134         return TAG;
135     }
136 
137     @Override
onSaveInputMethodPreference(final InputMethodPreference pref)138     public void onSaveInputMethodPreference(final InputMethodPreference pref) {
139         final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
140                 == Configuration.KEYBOARD_QWERTY;
141         InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeListForUser(this,
142                 mUserAwareContext.getContentResolver(),
143                 getContext().getSystemService(
144                         InputMethodManager.class).getInputMethodListAsUser(mUserId),
145                 hasHardwareKeyboard, mUserId);
146         // Update input method settings and preference list.
147         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
148         for (final InputMethodPreference p : mInputMethodPreferenceList) {
149             p.updatePreferenceViews();
150         }
151     }
152 
153     @Override
getMetricsCategory()154     public int getMetricsCategory() {
155         return SettingsEnums.ENABLE_VIRTUAL_KEYBOARDS;
156     }
157 
158     @VisibleForTesting
updateInputMethodPreferenceViews()159     void updateInputMethodPreferenceViews() {
160         mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
161         // Clear existing "InputMethodPreference"s
162         mInputMethodPreferenceList.clear();
163         final List<String> permittedList = mUserAwareContext.getSystemService(
164                 DevicePolicyManager.class).getPermittedInputMethods();
165         final Context prefContext = getPrefContext();
166         final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList();
167         final List<InputMethodInfo> enabledImis = getContext().getSystemService(
168                 InputMethodManager.class).getEnabledInputMethodListAsUser(UserHandle.of(mUserId));
169         final int numImis = (imis == null ? 0 : imis.size());
170         for (int i = 0; i < numImis; ++i) {
171             final InputMethodInfo imi = imis.get(i);
172             // TODO (b/182876800): Move this logic out of isAllowedByOrganization and
173             // into a new boolean.
174             // If an input method is enabled but not included in the permitted list, then set it as
175             // allowed by organization. Doing so will allow the user to disable the input method and
176             // remain complaint with the organization's policy. Once disabled, the input method
177             // cannot be re-enabled because it is not in the permitted list.
178             final boolean isAllowedByOrganization = permittedList == null
179                     || permittedList.contains(imi.getPackageName())
180                     || enabledImis.contains(imi);
181             final InputMethodPreference pref = new InputMethodPreference(prefContext, imi,
182                     isAllowedByOrganization, this, mUserId);
183             pref.setIcon(imi.loadIcon(mUserAwareContext.getPackageManager()));
184             mInputMethodPreferenceList.add(pref);
185         }
186         final Collator collator = Collator.getInstance();
187         mInputMethodPreferenceList.sort((lhs, rhs) -> lhs.compareTo(rhs, collator));
188         getPreferenceScreen().removeAll();
189         for (int i = 0; i < numImis; ++i) {
190             final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
191             pref.setOrder(i);
192             getPreferenceScreen().addPreference(pref);
193             InputMethodAndSubtypeUtilCompat.removeUnnecessaryNonPersistentPreference(pref);
194             pref.updatePreferenceViews();
195         }
196     }
197 
198     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
199             new BaseSearchIndexProvider() {
200                 @Override
201                 public List<SearchIndexableResource> getXmlResourcesToIndex(Context context,
202                         boolean enabled) {
203                     List<SearchIndexableResource> res = new ArrayList<>();
204                     SearchIndexableResource index = new SearchIndexableResource(context);
205                     index.xmlResId = R.xml.available_virtual_keyboard;
206                     res.add(index);
207                     return res;
208                 }
209             };
210 }
211