diff --git a/res/values/strings.xml b/res/values/strings.xml index 130b9cd343c..b252e33cccc 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -3859,7 +3859,7 @@ Keyboard & inputs - Virtual keyboards + Virtual keyboard Available virtual keyboard @@ -6319,10 +6319,10 @@ Automatic rules - Set Do Not Disturb schedule + Set Do Not Disturb rules - Silence your device at certain times + Limit sounds & vibrations at certain times Priority only diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index 79f441f7b7d..17bd576f32d 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -28,10 +28,13 @@ diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java index ad6e0417ff7..42eff159747 100644 --- a/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardFragment.java @@ -136,7 +136,7 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment unregisterShowVirtualKeyboardSettingsObserver(); } - public void onLoadFinishedInternal( + private void onLoadFinishedInternal( final int loaderId, @NonNull final List keyboardsList) { if (!mLoaderIDs.remove(loaderId)) { // Already destroyed loader. Ignore. @@ -198,8 +198,8 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment } @NonNull - private static ArrayList getHardKeyboards() { - final ArrayList keyboards = new ArrayList<>(); + public static List getHardKeyboards() { + final List keyboards = new ArrayList<>(); final int[] devicesIds = InputDevice.getDeviceIds(); for (int deviceId : devicesIds) { final InputDevice device = InputDevice.getDevice(deviceId); @@ -211,7 +211,7 @@ public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment } private void updateHardKeyboards() { - final ArrayList newHardKeyboards = getHardKeyboards(); + final List newHardKeyboards = getHardKeyboards(); if (!Objects.equals(newHardKeyboards, mLastHardKeyboards)) { clearLoader(); mLastHardKeyboards.clear(); diff --git a/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java new file mode 100644 index 00000000000..aa9f5ad7780 --- /dev/null +++ b/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceController.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2017 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.content.Context; +import android.hardware.input.InputManager; +import android.support.v7.preference.Preference; + +import com.android.settings.R; +import com.android.settings.core.PreferenceController; +import com.android.settings.core.lifecycle.Lifecycle; +import com.android.settings.core.lifecycle.LifecycleObserver; +import com.android.settings.core.lifecycle.events.OnPause; +import com.android.settings.core.lifecycle.events.OnResume; +import com.android.settings.inputmethod.PhysicalKeyboardFragment.HardKeyboardDeviceInfo; + +import java.util.List; + +public class PhysicalKeyboardPreferenceController extends PreferenceController implements + LifecycleObserver, OnResume, OnPause, InputManager.InputDeviceListener { + + private final InputManager mIm; + + private Preference mPreference; + + public PhysicalKeyboardPreferenceController(Context context, Lifecycle lifecycle) { + super(context); + mIm = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + + if (lifecycle != null) { + lifecycle.addObserver(this); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void updateState(Preference preference) { + mPreference = preference; + updateSummary(); + } + + @Override + public String getPreferenceKey() { + return "physical_keyboard_pref"; + } + + @Override + public void onPause() { + mIm.registerInputDeviceListener(this, null); + } + + @Override + public void onResume() { + mIm.unregisterInputDeviceListener(this); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + updateSummary(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + updateSummary(); + } + + @Override + public void onInputDeviceChanged(int deviceId) { + updateSummary(); + } + + private void updateSummary() { + if (mPreference == null) { + return; + } + final List keyboards = + PhysicalKeyboardFragment.getHardKeyboards(); + if (keyboards.isEmpty()) { + mPreference.setSummary(R.string.disconnected); + return; + } + String summary = null; + for (HardKeyboardDeviceInfo info : keyboards) { + if (summary == null) { + summary = info.mDeviceName; + } else { + summary = mContext.getString(R.string.join_many_items_middle, summary, + info.mDeviceName); + } + } + mPreference.setSummary(summary); + } +} diff --git a/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceController.java b/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceController.java new file mode 100644 index 00000000000..b7bf3dc34f9 --- /dev/null +++ b/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceController.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2017 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.content.pm.PackageManager; +import android.support.v7.preference.Preference; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import com.android.settings.R; +import com.android.settings.core.PreferenceController; + +import java.util.ArrayList; +import java.util.List; + +public class VirtualKeyboardPreferenceController extends PreferenceController { + + private final InputMethodManager mImm; + private final DevicePolicyManager mDpm; + private final PackageManager mPm; + + public VirtualKeyboardPreferenceController(Context context) { + super(context); + mPm = mContext.getPackageManager(); + mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); + mImm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return "virtual_keyboard_pref"; + } + + @Override + public void updateState(Preference preference) { + final List imis = mImm.getEnabledInputMethodList(); + if (imis == null) { + preference.setSummary(R.string.summary_empty); + return; + } + + final List permittedList = mDpm.getPermittedInputMethodsForCurrentUser(); + final List labels = new ArrayList<>(); + + for (InputMethodInfo imi : imis) { + final boolean isAllowedByOrganization = permittedList == null + || permittedList.contains(imi.getPackageName()); + if (!isAllowedByOrganization) { + continue; + } + labels.add(imi.loadLabel(mPm).toString()); + } + if (labels.isEmpty()) { + preference.setSummary(R.string.summary_empty); + return; + } + + String summary = null; + for (String label : labels) { + if (summary == null) { + summary = label; + } else { + summary = mContext.getString(R.string.join_many_items_middle, summary, label); + } + } + preference.setSummary(summary); + } +} diff --git a/src/com/android/settings/language/LanguageAndInputSettings.java b/src/com/android/settings/language/LanguageAndInputSettings.java index 5a2c0c7bd73..86ce118fb98 100644 --- a/src/com/android/settings/language/LanguageAndInputSettings.java +++ b/src/com/android/settings/language/LanguageAndInputSettings.java @@ -43,7 +43,9 @@ import com.android.settings.gestures.DoubleTwistPreferenceController; import com.android.settings.gestures.PickupGesturePreferenceController; import com.android.settings.gestures.SwipeToNotificationPreferenceController; import com.android.settings.inputmethod.GameControllerPreferenceController; +import com.android.settings.inputmethod.PhysicalKeyboardPreferenceController; import com.android.settings.inputmethod.SpellCheckerPreferenceController; +import com.android.settings.inputmethod.VirtualKeyboardPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import java.util.ArrayList; @@ -87,9 +89,11 @@ public class LanguageAndInputSettings extends DashboardFragment { controllers.add(new UserDictionaryPreferenceController(context)); controllers.add(new TtsPreferenceController(context, new TtsEngines(context))); // Input + controllers.add(new VirtualKeyboardPreferenceController(context)); + controllers.add(new PhysicalKeyboardPreferenceController(context, lifecycle)); final GameControllerPreferenceController gameControllerPreferenceController = new GameControllerPreferenceController(context); - getLifecycle().addObserver(gameControllerPreferenceController); + lifecycle.addObserver(gameControllerPreferenceController); if (mAmbientDisplayConfig == null) { mAmbientDisplayConfig = new AmbientDisplayConfiguration(context); diff --git a/tests/robotests/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceControllerTest.java new file mode 100644 index 00000000000..5fa2c0d66b3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/PhysicalKeyboardPreferenceControllerTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 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.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.support.v7.preference.Preference; +import android.view.InputDevice; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.testutils.shadow.ShadowInputDevice; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class PhysicalKeyboardPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private InputManager mIm; + @Mock + private Preference mPreference; + + private PhysicalKeyboardPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.INPUT_SERVICE)).thenReturn(mIm); + mController = new PhysicalKeyboardPreferenceController(mContext, null /* lifecycle */); + } + + @After + public void tearDown() { + ShadowInputDevice.reset(); + } + + @Test + public void shouldAlwaysBeAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + @Config(shadows = { + ShadowInputDevice.class, + }) + public void updateState_noKeyboard_setDisconnectedSummary() { + ShadowInputDevice.sDeviceIds = new int[]{}; + mController.updateState(mPreference); + + verify(mPreference).setSummary(R.string.disconnected); + } + + @Test + @Config(shadows = ShadowInputDevice.class) + public void updateState_hasKeyboard_setSummaryToKeyboardName() { + final InputDevice device = mock(InputDevice.class); + when(device.isVirtual()).thenReturn(false); + when(device.isFullKeyboard()).thenReturn(true); + when(device.getName()).thenReturn("test_keyboard"); + ShadowInputDevice.sDeviceIds = new int[]{0}; + ShadowInputDevice.addDevice(0, device); + + mController.updateState(mPreference); + + verify(mPreference).setSummary(device.getName()); + } + +} diff --git a/tests/robotests/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceControllerTest.java new file mode 100644 index 00000000000..3256950c923 --- /dev/null +++ b/tests/robotests/src/com/android/settings/inputmethod/VirtualKeyboardPreferenceControllerTest.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2017 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.verify; +import static org.mockito.Mockito.when; + +import android.app.admin.DevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.support.v7.preference.Preference; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; + +import com.android.settings.R; +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class VirtualKeyboardPreferenceControllerTest { + + @Mock + private Context mContext; + @Mock + private InputMethodManager mImm; + @Mock + private DevicePolicyManager mDpm; + @Mock + private PackageManager mPm; + @Mock + private Preference mPreference; + + private VirtualKeyboardPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm); + when(mContext.getSystemService(Context.INPUT_METHOD_SERVICE)).thenReturn(mImm); + when(mContext.getPackageManager()).thenReturn(mPm); + mController = new VirtualKeyboardPreferenceController(mContext); + } + + @Test + public void shouldAlwaysBeAvailable() { + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void updateState_noEnabledIMEs_setEmptySummary() { + mController.updateState(mPreference); + + verify(mPreference).setSummary(R.string.summary_empty); + } + + @Test + public void updateState_singleIme_setImeLabelToSummary() { + when(mDpm.getPermittedInputMethodsForCurrentUser()).thenReturn(null); + final ComponentName componentName = new ComponentName("pkg", "cls"); + final List imis = new ArrayList<>(); + imis.add(mock(InputMethodInfo.class)); + when(imis.get(0).getPackageName()).thenReturn(componentName.getPackageName()); + when(mImm.getEnabledInputMethodList()).thenReturn(imis); + when(imis.get(0).loadLabel(mPm)).thenReturn("label"); + + mController.updateState(mPreference); + + verify(mPreference).setSummary("label"); + } +} diff --git a/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java b/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java index 026264e6021..643e3051e7b 100644 --- a/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java +++ b/tests/robotests/src/com/android/settings/language/LanguageAndInputSettingsTest.java @@ -16,7 +16,15 @@ package com.android.settings.language; +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import android.app.Activity; +import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; @@ -48,13 +56,6 @@ import org.robolectric.annotation.Config; import java.util.ArrayList; import java.util.List; -import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class LanguageAndInputSettingsTest { @@ -64,6 +65,12 @@ public class LanguageAndInputSettingsTest { @Mock private PackageManager mPackageManager; @Mock + private InputManager mIm; + @Mock + private InputMethodManager mImm; + @Mock + private DevicePolicyManager mDpm; + @Mock private InputMethodManager mInputMethodManager; private TestFragment mFragment; @@ -72,8 +79,11 @@ public class LanguageAndInputSettingsTest { MockitoAnnotations.initMocks(this); when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mock(UserManager.class)); when(mContext.getSystemService(Context.INPUT_SERVICE)).thenReturn(mock(InputManager.class)); + when(mContext.getSystemService(Context.INPUT_SERVICE)).thenReturn(mIm); when(mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE)) .thenReturn(mock(TextServicesManager.class)); + when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(mDpm); + when(mContext.getSystemService(Context.INPUT_METHOD_SERVICE)).thenReturn(mImm); mFragment = new TestFragment(mContext); } @@ -96,6 +106,7 @@ public class LanguageAndInputSettingsTest { } @Test + public void testGetPreferenceControllers_shouldAllBeCreated() { final List controllers = mFragment.getPreferenceControllers(mContext); diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java new file mode 100644 index 00000000000..ecb1b71e678 --- /dev/null +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 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.testutils.shadow; + +import android.view.InputDevice; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +import java.util.HashMap; +import java.util.Map; + +@Implements(InputDevice.class) +public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice { + + public static int[] sDeviceIds; + + private static Map sDeviceMap = new HashMap<>(); + + @Implementation + public static int[] getDeviceIds() { + return sDeviceIds; + } + + @Implementation + public static InputDevice getDevice(int id) { + return sDeviceMap.get(id); + } + + public static void addDevice(int id, InputDevice device) { + sDeviceMap.put(id, device); + } + + @Resetter + public static void reset() { + sDeviceIds = null; + sDeviceMap.clear(); + } +}