diff --git a/res/xml/keyboard_layout_picker_fragment.xml b/res/xml/keyboard_layout_picker_fragment.xml new file mode 100644 index 00000000000..d1bf97171a0 --- /dev/null +++ b/res/xml/keyboard_layout_picker_fragment.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutPickerController.java b/src/com/android/settings/inputmethod/KeyboardLayoutPickerController.java new file mode 100644 index 00000000000..d3f7f17ccb0 --- /dev/null +++ b/src/com/android/settings/inputmethod/KeyboardLayoutPickerController.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2018 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.Fragment; +import android.content.Context; +import android.hardware.input.InputDeviceIdentifier; +import android.hardware.input.InputManager; +import android.hardware.input.KeyboardLayout; +import android.support.v14.preference.SwitchPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceScreen; +import android.view.InputDevice; + +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.core.lifecycle.events.OnStop; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + + +public class KeyboardLayoutPickerController extends BasePreferenceController implements + InputManager.InputDeviceListener, LifecycleObserver, OnStart, OnStop { + + private final InputManager mIm; + private final Map mPreferenceMap; + + private Fragment mParent; + private int mInputDeviceId; + private InputDeviceIdentifier mInputDeviceIdentifier; + private KeyboardLayout[] mKeyboardLayouts; + private PreferenceScreen mScreen; + + + public KeyboardLayoutPickerController(Context context, String key) { + super(context, key); + mIm = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + mInputDeviceId = -1; + mPreferenceMap = new HashMap<>(); + } + + public void initialize(Fragment parent, InputDeviceIdentifier inputDeviceIdentifier) { + mParent = parent; + mInputDeviceIdentifier = inputDeviceIdentifier; + mKeyboardLayouts = mIm.getKeyboardLayoutsForInputDevice(mInputDeviceIdentifier); + Arrays.sort(mKeyboardLayouts); + } + + @Override + public void onStart() { + mIm.registerInputDeviceListener(this, null); + + final InputDevice inputDevice = + mIm.getInputDeviceByDescriptor(mInputDeviceIdentifier.getDescriptor()); + if (inputDevice == null) { + mParent.getActivity().finish(); + return; + } + mInputDeviceId = inputDevice.getId(); + + updateCheckedState(); + } + + @Override + public void onStop() { + mIm.unregisterInputDeviceListener(this); + mInputDeviceId = -1; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mScreen = screen; + createPreferenceHierarchy(); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!(preference instanceof SwitchPreference)) { + return false; + } + + final SwitchPreference switchPref = (SwitchPreference) preference; + final KeyboardLayout layout = mPreferenceMap.get(switchPref); + if (layout != null) { + final boolean checked = switchPref.isChecked(); + if (checked) { + mIm.addKeyboardLayoutForInputDevice(mInputDeviceIdentifier, + layout.getDescriptor()); + } else { + mIm.removeKeyboardLayoutForInputDevice(mInputDeviceIdentifier, + layout.getDescriptor()); + } + } + return true; + } + + @Override + public void onInputDeviceAdded(int deviceId) { + + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + mParent.getActivity().finish(); + } + } + + @Override + public void onInputDeviceChanged(int deviceId) { + if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { + updateCheckedState(); + } + } + + private void updateCheckedState() { + final String[] enabledKeyboardLayouts = mIm.getEnabledKeyboardLayoutsForInputDevice( + mInputDeviceIdentifier); + Arrays.sort(enabledKeyboardLayouts); + + for (Map.Entry entry : mPreferenceMap.entrySet()) { + entry.getKey().setChecked(Arrays.binarySearch(enabledKeyboardLayouts, + entry.getValue().getDescriptor()) >= 0); + } + } + + private void createPreferenceHierarchy() { + for (KeyboardLayout layout : mKeyboardLayouts) { + final SwitchPreference pref = new SwitchPreference(mScreen.getContext()); + pref.setTitle(layout.getLabel()); + pref.setSummary(layout.getCollection()); + pref.setKey(layout.getDescriptor()); + mScreen.addPreference(pref); + mPreferenceMap.put(pref, layout); + } + } +} + diff --git a/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java index 4c9715ca467..2c3f89ba632 100644 --- a/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java +++ b/src/com/android/settings/inputmethod/KeyboardLayoutPickerFragment.java @@ -18,29 +18,15 @@ package com.android.settings.inputmethod; import android.content.Context; import android.hardware.input.InputDeviceIdentifier; -import android.hardware.input.InputManager; -import android.hardware.input.InputManager.InputDeviceListener; -import android.hardware.input.KeyboardLayout; -import android.os.Bundle; -import android.support.v7.preference.CheckBoxPreference; -import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceScreen; -import android.view.InputDevice; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -public class KeyboardLayoutPickerFragment extends SettingsPreferenceFragment - implements InputDeviceListener { - private InputDeviceIdentifier mInputDeviceIdentifier; - private int mInputDeviceId = -1; - private InputManager mIm; - private KeyboardLayout[] mKeyboardLayouts; - private HashMap mPreferenceMap = new HashMap<>(); +public class KeyboardLayoutPickerFragment extends DashboardFragment { + + private static final String TAG = "KeyboardLayoutPicker"; /** * Intent extra: The input device descriptor of the keyboard whose keyboard @@ -54,105 +40,25 @@ public class KeyboardLayoutPickerFragment extends SettingsPreferenceFragment } @Override - public void onCreate(Bundle icicle) { - super.onCreate(icicle); + public void onAttach(Context context) { + super.onAttach(context); - mInputDeviceIdentifier = getActivity().getIntent().getParcelableExtra( - EXTRA_INPUT_DEVICE_IDENTIFIER); - if (mInputDeviceIdentifier == null) { + final InputDeviceIdentifier inputDeviceIdentifier = getActivity().getIntent(). + getParcelableExtra(EXTRA_INPUT_DEVICE_IDENTIFIER); + if (inputDeviceIdentifier == null) { getActivity().finish(); } - mIm = (InputManager) getSystemService(Context.INPUT_SERVICE); - mKeyboardLayouts = mIm.getKeyboardLayoutsForInputDevice(mInputDeviceIdentifier); - Arrays.sort(mKeyboardLayouts); - setPreferenceScreen(createPreferenceHierarchy()); + use(KeyboardLayoutPickerController.class).initialize(this /*parent*/, + inputDeviceIdentifier); } @Override - public void onResume() { - super.onResume(); - - mIm.registerInputDeviceListener(this, null); - - InputDevice inputDevice = - mIm.getInputDeviceByDescriptor(mInputDeviceIdentifier.getDescriptor()); - if (inputDevice == null) { - getActivity().finish(); - return; - } - mInputDeviceId = inputDevice.getId(); - - updateCheckedState(); + protected String getLogTag() { + return TAG; } - @Override - public void onPause() { - mIm.unregisterInputDeviceListener(this); - mInputDeviceId = -1; - - super.onPause(); - } - - @Override - public boolean onPreferenceTreeClick(Preference preference) { - if (preference instanceof CheckBoxPreference) { - CheckBoxPreference checkboxPref = (CheckBoxPreference)preference; - KeyboardLayout layout = mPreferenceMap.get(checkboxPref); - if (layout != null) { - boolean checked = checkboxPref.isChecked(); - if (checked) { - mIm.addKeyboardLayoutForInputDevice(mInputDeviceIdentifier, - layout.getDescriptor()); - } else { - mIm.removeKeyboardLayoutForInputDevice(mInputDeviceIdentifier, - layout.getDescriptor()); - } - return true; - } - } - return super.onPreferenceTreeClick(preference); - } - - @Override - public void onInputDeviceAdded(int deviceId) { - } - - @Override - public void onInputDeviceChanged(int deviceId) { - if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { - updateCheckedState(); - } - } - - @Override - public void onInputDeviceRemoved(int deviceId) { - if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { - getActivity().finish(); - } - } - - private PreferenceScreen createPreferenceHierarchy() { - PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); - - for (KeyboardLayout layout : mKeyboardLayouts) { - CheckBoxPreference pref = new CheckBoxPreference(getPrefContext()); - pref.setTitle(layout.getLabel()); - pref.setSummary(layout.getCollection()); - root.addPreference(pref); - mPreferenceMap.put(pref, layout); - } - return root; - } - - private void updateCheckedState() { - String[] enabledKeyboardLayouts = mIm.getEnabledKeyboardLayoutsForInputDevice( - mInputDeviceIdentifier); - Arrays.sort(enabledKeyboardLayouts); - - for (Map.Entry entry : mPreferenceMap.entrySet()) { - entry.getKey().setChecked(Arrays.binarySearch(enabledKeyboardLayouts, - entry.getValue().getDescriptor()) >= 0); - } + protected int getPreferenceScreenResId() { + return R.xml.keyboard_layout_picker_fragment; } } diff --git a/tests/robotests/assets/grandfather_not_implementing_index_provider b/tests/robotests/assets/grandfather_not_implementing_index_provider index 4fa57f6ae6a..e6744ded37d 100644 --- a/tests/robotests/assets/grandfather_not_implementing_index_provider +++ b/tests/robotests/assets/grandfather_not_implementing_index_provider @@ -20,6 +20,7 @@ com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionLo com.android.settings.enterprise.ApplicationListFragment$AdminGrantedPermissionMicrophone com.android.settings.enterprise.ApplicationListFragment$EnterpriseInstalledPackages com.android.settings.enterprise.EnterpriseSetDefaultAppsListFragment +com.android.settings.inputmethod.KeyboardLayoutPickerFragment com.android.settings.wifi.tether.WifiTetherSettings com.android.settings.wifi.SavedAccessPointsWifiSettings com.android.settings.notification.ZenModeEventRuleSettings diff --git a/tests/robotests/assets/grandfather_not_implementing_indexable b/tests/robotests/assets/grandfather_not_implementing_indexable index a57de34f317..045841af2b3 100644 --- a/tests/robotests/assets/grandfather_not_implementing_indexable +++ b/tests/robotests/assets/grandfather_not_implementing_indexable @@ -1,7 +1,6 @@ com.android.settings.accessibility.ToggleScreenMagnificationPreferenceFragment com.android.settings.deviceinfo.PrivateVolumeForget com.android.settings.inputmethod.SpellCheckersSettings -com.android.settings.inputmethod.KeyboardLayoutPickerFragment com.android.settings.fuelgauge.InactiveApps com.android.settings.accessibility.CaptionPropertiesFragment com.android.settings.accessibility.AccessibilitySettingsForSetupWizard diff --git a/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java new file mode 100644 index 00000000000..a7df0a4a8bf --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/KeyboardLayoutPickerControllerTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2018 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.ArgumentMatchers.anyString; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.hardware.input.InputDeviceIdentifier; +import android.hardware.input.InputManager; +import android.hardware.input.KeyboardLayout; +import android.support.v7.preference.PreferenceManager; +import android.support.v7.preference.PreferenceScreen; +import android.view.InputDevice; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.testutils.SettingsRobolectricTestRunner; +import com.android.settings.testutils.shadow.ShadowInputDevice; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowApplication; + +@RunWith(SettingsRobolectricTestRunner.class) +public class KeyboardLayoutPickerControllerTest { + + @Mock + private Fragment mFragment; + @Mock + private InputManager mInputManager; + + private Context mContext; + private InputDeviceIdentifier mInputDeviceIdentifier; + private KeyboardLayoutPickerController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final ShadowApplication shadowContext = ShadowApplication.getInstance(); + shadowContext.setSystemService(Context.INPUT_SERVICE, mInputManager); + + mContext = RuntimeEnvironment.application; + mInputDeviceIdentifier = new InputDeviceIdentifier("descriptor", 1, 1); + mController = new KeyboardLayoutPickerController(mContext, "pref_key"); + + initializeOneLayout(); + } + + @Test + public void isAlwaysAvailable() { + assertThat(mController.getAvailabilityStatus()) + .isEqualTo(BasePreferenceController.AVAILABLE); + } + + @Test + public void testLifecycle_onStart_shouldRegisterInputManager() { + final Activity activity = Robolectric.setupActivity(Activity.class); + when(mFragment.getActivity()).thenReturn(activity); + + mController.onStart(); + + // Register is called, but unregister should not be called. + verify(mInputManager).registerInputDeviceListener(mController, null); + verify(mInputManager, never()).unregisterInputDeviceListener(mController); + } + + @Test + public void testLifecycle_onStart_NoInputDevice_shouldFinish() { + final Activity activity = Robolectric.setupActivity(Activity.class); + when(mInputManager.getInputDeviceByDescriptor(anyString())).thenReturn(null); + when(mFragment.getActivity()).thenReturn(activity); + + mController.onStart(); + assertThat(activity.isFinishing()).isTrue(); + } + + @Test + public void testLifecycle_onStop_shouldCancelRegisterInputManager() { + mController.onStop(); + + // Unregister is called, but register should not be called. + verify(mInputManager).unregisterInputDeviceListener(mController); + verify(mInputManager, never()).registerInputDeviceListener(mController, null); + } + + @Test + public void test_createPreferenceHierarchy_shouldAddOnePreference() { + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + + mController.displayPreference(screen); + + // We cretate a keyboard layouts in initializeOneLayout() + assertThat(screen.getPreferenceCount()).isEqualTo(1); + } + + @Test + public void test_createPreferenceHierarchy_shouldAddTwoPreference() { + initializeTwoLayouts(); + final PreferenceManager preferenceManager = new PreferenceManager(mContext); + final PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext); + + mController.displayPreference(screen); + + // We cretate two keyboard layouts in initializeOneLayout() + assertThat(screen.getPreferenceCount()).isEqualTo(2); + } + + @Test + @Config(shadows = ShadowInputDevice.class) + public void testOnDeviceRemove_getSameDevice_shouldFinish() { + final int TARGET_DEVICE_ID = 1; + final Activity activity = Robolectric.setupActivity(Activity.class); + final String[] enableKeyboardLayouts = {"layout1"}; + final InputDevice device = ShadowInputDevice.makeInputDevicebyId(TARGET_DEVICE_ID); + + when(mFragment.getActivity()).thenReturn(activity); + when(mInputManager.getInputDeviceByDescriptor(anyString())).thenReturn(device); + when(mInputManager.getEnabledKeyboardLayoutsForInputDevice( + any(InputDeviceIdentifier.class))).thenReturn(enableKeyboardLayouts); + + mController.onStart(); + mController.onInputDeviceRemoved(TARGET_DEVICE_ID); + + assertThat(activity.isFinishing()).isTrue(); + } + + @Test + @Config(shadows = ShadowInputDevice.class) + public void testOnDeviceRemove_getDifferentDevice_shouldNotFinish() { + final int TARGET_DEVICE_ID = 1; + final int ANOTHER_DEVICE_ID = 2; + final Activity activity = Robolectric.setupActivity(Activity.class); + final String[] enableKeyboardLayouts = {"layout1"}; + final InputDevice device = ShadowInputDevice.makeInputDevicebyId(TARGET_DEVICE_ID); + + when(mFragment.getActivity()).thenReturn(activity); + when(mInputManager.getInputDeviceByDescriptor(anyString())).thenReturn(device); + when(mInputManager.getEnabledKeyboardLayoutsForInputDevice( + any(InputDeviceIdentifier.class))).thenReturn(enableKeyboardLayouts); + + mController.onStart(); + mController.onInputDeviceRemoved(ANOTHER_DEVICE_ID); + + assertThat(activity.isFinishing()).isFalse(); + } + + private void initializeOneLayout() { + final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 1, 1)}; + when(mInputManager.getKeyboardLayoutsForInputDevice( + any(InputDeviceIdentifier.class))).thenReturn( + keyboardLayouts); + + mController.initialize(mFragment, mInputDeviceIdentifier); + } + + private void initializeTwoLayouts() { + final KeyboardLayout[] keyboardLayouts = {new KeyboardLayout("", "", "", 1, null, 1, 1), + new KeyboardLayout("", "", "", 2, null, 2, 2)}; + when(mInputManager.getKeyboardLayoutsForInputDevice(any(InputDeviceIdentifier.class))). + thenReturn(keyboardLayouts); + + mController.initialize(mFragment, mInputDeviceIdentifier); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java index ecb1b71e678..4dec502d483 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java @@ -21,6 +21,7 @@ import android.view.InputDevice; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; import org.robolectric.annotation.Resetter; +import org.robolectric.shadow.api.Shadow; import java.util.HashMap; import java.util.Map; @@ -32,6 +33,8 @@ public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice private static Map sDeviceMap = new HashMap<>(); + private int mDeviceId; + @Implementation public static int[] getDeviceIds() { return sDeviceIds; @@ -51,4 +54,22 @@ public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice sDeviceIds = null; sDeviceMap.clear(); } + + @Implementation + public int getId() { + return mDeviceId; + } + + public static InputDevice makeInputDevicebyId(int id) { + final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); + final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); + shadowInputDevice.setId(id); + return inputDevice; + } + + public void setId(int id) { + mDeviceId = id; + } + } +