KeyboardLayoutPickerFragment uses DashboardFragment

- Build a controller to generate a list of preferences and add to screen.
- Move some logic to controller.
- Add some test cases for controller.

Test: manual
Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.inputmethod
      make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.core
      make RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.dashboard
      atest UniquePreferenceTest
Change-Id: I4ebe486ade3439b9814b11866c402dcf881f21a7
This commit is contained in:
tmfang
2018-03-29 18:18:59 +08:00
committed by Fan Zhang
parent 435e4ad516
commit f1191f6bf9
7 changed files with 414 additions and 111 deletions

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:settings="http://schemas.android.com/apk/res-auto"
android:key="keyboard_layout_picker"
android:title="@string/keyboard_layout_picker_title"
settings:controller="com.android.settings.inputmethod.KeyboardLayoutPickerController">
</PreferenceScreen>

View File

@@ -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<SwitchPreference, KeyboardLayout> 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<SwitchPreference, KeyboardLayout> 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);
}
}
}

View File

@@ -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<CheckBoxPreference, KeyboardLayout> 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<CheckBoxPreference, KeyboardLayout> entry : mPreferenceMap.entrySet()) {
entry.getKey().setChecked(Arrays.binarySearch(enabledKeyboardLayouts,
entry.getValue().getDescriptor()) >= 0);
}
protected int getPreferenceScreenResId() {
return R.xml.keyboard_layout_picker_fragment;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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<Integer, InputDevice> 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;
}
}