diff --git a/res/xml/virtual_keyboard_settings.xml b/res/xml/virtual_keyboard_settings.xml index 78b27f9a662..f248236d3a8 100644 --- a/res/xml/virtual_keyboard_settings.xml +++ b/res/xml/virtual_keyboard_settings.xml @@ -20,8 +20,10 @@ android:title="@string/virtual_keyboard_category"> + settings:controller="com.android.settings.inputmethod.InputMethodPreferenceController" /> diff --git a/src/com/android/settings/inputmethod/InputMethodPreferenceController.java b/src/com/android/settings/inputmethod/InputMethodPreferenceController.java new file mode 100644 index 00000000000..5c5d0fb0e0b --- /dev/null +++ b/src/com/android/settings/inputmethod/InputMethodPreferenceController.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.inputmethod; + +import android.app.admin.DevicePolicyManager; +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnStart; +import com.android.settingslib.inputmethod.InputMethodPreference; + +import java.text.Collator; +import java.util.ArrayList; +import java.util.List; +import com.google.common.annotations.VisibleForTesting; + +public class InputMethodPreferenceController extends BasePreferenceController implements + LifecycleObserver, OnStart { + + @VisibleForTesting + PreferenceScreen mScreen; + private Preference mPreference; + private InputMethodManager mImm; + private DevicePolicyManager mDpm; + + public InputMethodPreferenceController(Context context, String key) { + super(context, key); + mImm = context.getSystemService(InputMethodManager.class); + mDpm = context.getSystemService(DevicePolicyManager.class); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE_UNSEARCHABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; + mPreference = mScreen.findPreference(getPreferenceKey()); + } + + @Override + public void onStart() { + updateInputMethodPreferenceViews(); + } + + private void updateInputMethodPreferenceViews() { + final List preferenceList = new ArrayList<>(); + + final List permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); + final List imis = mImm.getEnabledInputMethodList(); + final int N = (imis == null ? 0 : imis.size()); + for (int i = 0; i < N; ++i) { + final InputMethodInfo imi = imis.get(i); + final boolean isAllowedByOrganization = permittedList == null + || permittedList.contains(imi.getPackageName()); + final Drawable icon = imi.loadIcon(mContext.getPackageManager()); + final InputMethodPreference pref = new InputMethodPreference( + mScreen.getContext(), + imi, + false, /* isImeEnabler */ + isAllowedByOrganization, + null /* this can be null since isImeEnabler is false */); + pref.setIcon(icon); + preferenceList.add(pref); + } + final Collator collator = Collator.getInstance(); + preferenceList.sort((lhs, rhs) -> lhs.compareTo(rhs, collator)); + mScreen.removeAll(); + for (int i = 0; i < N; ++i) { + final InputMethodPreference pref = preferenceList.get(i); + pref.setOrder(i); + mScreen.addPreference(pref); + pref.updatePreferenceViews(); + } + mScreen.addPreference(mPreference); + } +} diff --git a/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java b/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java index ef07d11d24b..61dbd31c259 100644 --- a/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/VirtualKeyboardFragment.java @@ -16,58 +16,32 @@ package com.android.settings.inputmethod; -import android.app.Activity; -import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.Context; -import android.graphics.drawable.Drawable; -import android.os.Bundle; import android.provider.SearchIndexableResource; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import androidx.preference.Preference; - -import com.android.internal.util.Preconditions; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; -import com.android.settingslib.inputmethod.InputMethodAndSubtypeUtilCompat; -import com.android.settingslib.inputmethod.InputMethodPreference; import com.android.settingslib.search.SearchIndexable; -import java.text.Collator; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @SearchIndexable -public final class VirtualKeyboardFragment extends SettingsPreferenceFragment implements Indexable { +public final class VirtualKeyboardFragment extends DashboardFragment { - private static final String ADD_VIRTUAL_KEYBOARD_SCREEN = "add_virtual_keyboard_screen"; - - private final ArrayList mInputMethodPreferenceList = new ArrayList<>(); - private InputMethodManager mImm; - private DevicePolicyManager mDpm; - private Preference mAddVirtualKeyboardScreen; + private static final String TAG = "VirtualKeyboardFragment"; @Override - public void onCreatePreferences(Bundle bundle, String s) { - Activity activity = Preconditions.checkNotNull(getActivity()); - addPreferencesFromResource(R.xml.virtual_keyboard_settings); - mImm = Preconditions.checkNotNull(activity.getSystemService(InputMethodManager.class)); - mDpm = Preconditions.checkNotNull(activity.getSystemService(DevicePolicyManager.class)); - mAddVirtualKeyboardScreen = Preconditions.checkNotNull( - findPreference(ADD_VIRTUAL_KEYBOARD_SCREEN)); + protected int getPreferenceScreenResId() { + return R.xml.virtual_keyboard_settings; } @Override - public void onResume() { - super.onResume(); - // Refresh internal states in mInputMethodSettingValues to keep the latest - // "InputMethodInfo"s and "InputMethodSubtype"s - updateInputMethodPreferenceViews(); + protected String getLogTag() { + return TAG; } @Override @@ -75,42 +49,6 @@ public final class VirtualKeyboardFragment extends SettingsPreferenceFragment im return SettingsEnums.VIRTUAL_KEYBOARDS; } - private void updateInputMethodPreferenceViews() { - // Clear existing "InputMethodPreference"s - mInputMethodPreferenceList.clear(); - List permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); - final Context context = getPrefContext(); - final List imis = mImm.getEnabledInputMethodList(); - final int N = (imis == null ? 0 : imis.size()); - for (int i = 0; i < N; ++i) { - final InputMethodInfo imi = imis.get(i); - final boolean isAllowedByOrganization = permittedList == null - || permittedList.contains(imi.getPackageName()); - final Drawable icon = imi.loadIcon(context.getPackageManager()); - final InputMethodPreference pref = new InputMethodPreference( - context, - imi, - false, /* isImeEnabler */ - isAllowedByOrganization, - null /* this can be null since isImeEnabler is false */); - pref.setIcon(icon); - mInputMethodPreferenceList.add(pref); - } - final Collator collator = Collator.getInstance(); - mInputMethodPreferenceList.sort((lhs, rhs) -> lhs.compareTo(rhs, collator)); - getPreferenceScreen().removeAll(); - for (int i = 0; i < N; ++i) { - final InputMethodPreference pref = mInputMethodPreferenceList.get(i); - pref.setOrder(i); - getPreferenceScreen().addPreference(pref); - InputMethodAndSubtypeUtilCompat.removeUnnecessaryNonPersistentPreference(pref); - pref.updatePreferenceViews(); - } - mAddVirtualKeyboardScreen.setIcon(R.drawable.ic_add_24dp); - mAddVirtualKeyboardScreen.setOrder(N); - getPreferenceScreen().addPreference(mAddVirtualKeyboardScreen); - } - public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { @Override diff --git a/tests/robotests/src/com/android/settings/inputmethod/InputMethodPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/InputMethodPreferenceControllerTest.java new file mode 100644 index 00000000000..c678f92c8fa --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/InputMethodPreferenceControllerTest.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + */ + +package com.android.settings.inputmethod; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.view.inputmethod.InputMethodInfo; + +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowInputMethodManagerWithMethodList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowInputMethodManagerWithMethodList.class) +public class InputMethodPreferenceControllerTest { + + private InputMethodPreferenceController mController; + private Context mContext; + private PreferenceScreen mScreen; + private Preference mPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + mScreen = spy(new PreferenceScreen(mContext, null)); + mPreference = new Preference(mContext); + mController = new InputMethodPreferenceController(mContext, "key"); + + when(mScreen.getPreferenceManager()).thenReturn(mock(PreferenceManager.class)); + when(mScreen.getContext()).thenReturn(mContext); + when(mScreen.findPreference(mController.getPreferenceKey())).thenReturn(mPreference); + + mController.displayPreference(mScreen); + } + + @Test + public void onStart_NoInputMethod_shouldHaveOnePreference() { + mController.onStart(); + + assertThat(mScreen.getPreferenceCount()).isEqualTo(1); + } + + @Test + public void onStart_hasInputMethod_shouldHaveCorrectPreferences() { + final List imis = new ArrayList<>(); + imis.add(mock(InputMethodInfo.class)); + imis.add(mock(InputMethodInfo.class)); + when(imis.get(0).getPackageName()).thenReturn("name1"); + when(imis.get(1).getPackageName()).thenReturn("name2"); + ShadowInputMethodManagerWithMethodList.getShadow().setEnabledInputMethodList(imis); + + mController.onStart(); + + assertThat(mScreen.getPreferenceCount()).isEqualTo(3); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputMethodManagerWithMethodList.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputMethodManagerWithMethodList.java index 98022477a6f..4ebb9da53d7 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputMethodManagerWithMethodList.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputMethodManagerWithMethodList.java @@ -35,6 +35,7 @@ import java.util.List; public class ShadowInputMethodManagerWithMethodList extends ShadowInputMethodManager { private List mInputMethodInfos = Collections.emptyList(); + private List mEnabledInputMethodInfos = Collections.emptyList(); @Implementation public static InputMethodManager getInstance() { @@ -46,11 +47,26 @@ public class ShadowInputMethodManagerWithMethodList extends ShadowInputMethodMan return mInputMethodInfos; } + @Implementation + protected List getEnabledInputMethodList() { + return mEnabledInputMethodInfos; + } + + @Implementation + protected List getEnabledInputMethodSubtypeList(InputMethodInfo imi, + boolean allowsImplicitlySelectedSubtypes) { + return Collections.emptyList(); + } + // Non-Android setter. public void setInputMethodList(List inputMethodInfos) { mInputMethodInfos = inputMethodInfos; } + public void setEnabledInputMethodList(List inputMethodInfos) { + mEnabledInputMethodInfos = inputMethodInfos; + } + public static ShadowInputMethodManagerWithMethodList getShadow() { return (ShadowInputMethodManagerWithMethodList) Shadow.extract( RuntimeEnvironment.application.getSystemService(InputMethodManager.class));