Add keyboard vibration settings

Introduce keyboard vibration preference settings
to control keyboard vibration state.

Bug: 289107579
Test: atest KeyboardVibrationTogglePreferenceControllerTest
Change-Id: I9cc5d53f1b22c460ad2acc7e4a412fefbca586df
This commit is contained in:
Wilson Wu
2023-08-16 10:37:56 +00:00
parent a1243188ce
commit e9eb8e65ce
7 changed files with 365 additions and 3 deletions

View File

@@ -226,6 +226,9 @@
Can be overridden for specific product builds if the target device does not support it --> Can be overridden for specific product builds if the target device does not support it -->
<bool name="config_media_vibration_supported">true</bool> <bool name="config_media_vibration_supported">true</bool>
<!-- Whether to show Keyboard vibration settings in the vibration and haptics screen. -->
<bool name="config_keyboard_vibration_supported">false</bool>
<!-- <!--
Whether or not the homepage should be powered by legacy suggestion (versus contextual cards) Whether or not the homepage should be powered by legacy suggestion (versus contextual cards)
Default to true as not all devices support contextual cards. Default to true as not all devices support contextual cards.

View File

@@ -4808,6 +4808,8 @@
<string name="accessibility_alarm_vibration_title">Alarm vibration</string> <string name="accessibility_alarm_vibration_title">Alarm vibration</string>
<!-- Title for preference for configuring media vibrations (e.g. vibrations played together with animations, music, videos, etc). [CHAR LIMIT=NONE] --> <!-- Title for preference for configuring media vibrations (e.g. vibrations played together with animations, music, videos, etc). [CHAR LIMIT=NONE] -->
<string name="accessibility_media_vibration_title">Media vibration</string> <string name="accessibility_media_vibration_title">Media vibration</string>
<!-- Toggle for keyboard vibration. [CHAR LIMIT=NONE]-->
<string name="accessibility_keyboard_vibration_title">Keyboard vibration</string>
<!-- Title for accessibility preference for configuring ring vibrations. [CHAR LIMIT=NONE] --> <!-- Title for accessibility preference for configuring ring vibrations. [CHAR LIMIT=NONE] -->
<string name="accessibility_ring_vibration_title">Ring vibration</string> <string name="accessibility_ring_vibration_title">Ring vibration</string>
<!-- Title for accessibility preference for configuring notification vibrations. --> <!-- Title for accessibility preference for configuring notification vibrations. -->
@@ -7339,6 +7341,9 @@
<!-- List of synonyms for the nfc tag apps control [CHAR LIMIT=NONE] --> <!-- List of synonyms for the nfc tag apps control [CHAR LIMIT=NONE] -->
<string name="keywords_change_nfc_tag_apps_state">nfc, tag, reader</string> <string name="keywords_change_nfc_tag_apps_state">nfc, tag, reader</string>
<!-- List of synonyms for keyboard vibration settings search [CHAR LIMIT=NONE] -->
<string name="keywords_keyboard_vibration">keyboard, haptics, vibrate,</string>
<!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]--> <!-- Summary for sound settings, explaining a few important settings under it [CHAR LIMIT=NONE]-->
<string name="sound_dashboard_summary">Volume, vibration, Do Not Disturb</string> <string name="sound_dashboard_summary">Volume, vibration, Do Not Disturb</string>

View File

@@ -77,6 +77,12 @@
app:keywords="@string/keywords_media_vibration" app:keywords="@string/keywords_media_vibration"
app:controller="com.android.settings.accessibility.MediaVibrationIntensityPreferenceController" /> app:controller="com.android.settings.accessibility.MediaVibrationIntensityPreferenceController" />
<SwitchPreference
android:key="vibration_intensity_preference_keyboard"
android:title="@string/accessibility_keyboard_vibration_title"
app:keywords="@string/keywords_keyboard_vibration"
app:controller="com.android.settings.accessibility.KeyboardVibrationTogglePreferenceController"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -77,6 +77,12 @@
app:keywords="@string/keywords_media_vibration" app:keywords="@string/keywords_media_vibration"
app:controller="com.android.settings.accessibility.MediaVibrationTogglePreferenceController" /> app:controller="com.android.settings.accessibility.MediaVibrationTogglePreferenceController" />
<SwitchPreference
android:key="vibration_preference_keyboard"
android:title="@string/accessibility_keyboard_vibration_title"
app:keywords="@string/keywords_keyboard_vibration"
app:controller="com.android.settings.accessibility.KeyboardVibrationTogglePreferenceController"/>
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 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.accessibility;
import static android.provider.Settings.System.KEYBOARD_VIBRATION_ENABLED;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Handler;
import android.os.VibrationAttributes;
import android.os.Vibrator;
import android.os.vibrator.Flags;
import android.provider.Settings;
import android.util.Log;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import com.android.settings.R;
import com.android.settings.core.TogglePreferenceController;
/**
* A preference controller to turn on/off keyboard vibration state with a single toggle.
*/
public class KeyboardVibrationTogglePreferenceController extends TogglePreferenceController
implements DefaultLifecycleObserver {
private static final String TAG = "KeyboardVibrateControl";
private static final Uri MAIN_VIBRATION_SWITCH_URI =
Settings.System.getUriFor(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY);
private final ContentObserver mContentObserver;
private final Vibrator mVibrator;
@Nullable
private SwitchPreference mPreference;
public KeyboardVibrationTogglePreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mVibrator = context.getSystemService(Vibrator.class);
mContentObserver = new ContentObserver(new Handler(/* async= */ true)) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (uri.equals(MAIN_VIBRATION_SWITCH_URI)) {
updateState(mPreference);
} else {
Log.w(TAG, "Unexpected uri change:" + uri);
}
}
};
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
mContext.getContentResolver().registerContentObserver(MAIN_VIBRATION_SWITCH_URI,
/* notifyForDescendants= */ false, mContentObserver);
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPreference = screen.findPreference(getPreferenceKey());
}
@Override
public void updateState(@Nullable Preference preference) {
if (preference != null) {
super.updateState(preference);
preference.setEnabled(isPreferenceEnabled());
}
}
@Override
public int getAvailabilityStatus() {
if (Flags.keyboardCategoryEnabled()
&& mContext.getResources().getBoolean(R.bool.config_keyboard_vibration_supported)) {
return AVAILABLE;
}
return UNSUPPORTED_ON_DEVICE;
}
@Override
public boolean isChecked() {
// Always unchecked if the preference disabled
return isPreferenceEnabled() && isKeyboardVibrationSwitchEnabled();
}
@Override
public boolean setChecked(boolean isChecked) {
final boolean success = updateKeyboardVibrationSetting(isChecked);
if (success && isChecked) {
// Play the preview vibration effect when the toggle is on.
final VibrationAttributes touchAttrs =
VibrationPreferenceConfig.createPreviewVibrationAttributes(
VibrationAttributes.USAGE_TOUCH);
final VibrationAttributes keyboardAttrs =
new VibrationAttributes.Builder(touchAttrs)
.setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
.build();
VibrationPreferenceConfig.playVibrationPreview(mVibrator, keyboardAttrs);
}
return true;
}
@Override
public int getSliceHighlightMenuRes() {
return R.string.menu_key_accessibility;
}
private boolean isPreferenceEnabled() {
return VibrationPreferenceConfig.isMainVibrationSwitchEnabled(
mContext.getContentResolver());
}
private boolean isKeyboardVibrationSwitchEnabled() {
return Settings.System.getInt(mContext.getContentResolver(), KEYBOARD_VIBRATION_ENABLED,
mVibrator.isDefaultKeyboardVibrationEnabled() ? ON : OFF) == ON;
}
private boolean updateKeyboardVibrationSetting(boolean enable) {
final boolean success = Settings.System.putInt(mContext.getContentResolver(),
KEYBOARD_VIBRATION_ENABLED, enable ? ON : OFF);
if (!success) {
Log.w(TAG, "Update settings database error!");
}
return success;
}
}

View File

@@ -68,8 +68,19 @@ public abstract class VibrationPreferenceConfig {
/** Play a vibration effect with intensity just selected by the user. */ /** Play a vibration effect with intensity just selected by the user. */
public static void playVibrationPreview(Vibrator vibrator, public static void playVibrationPreview(Vibrator vibrator,
@VibrationAttributes.Usage int vibrationUsage) { @VibrationAttributes.Usage int vibrationUsage) {
vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, playVibrationPreview(vibrator, createPreviewVibrationAttributes(vibrationUsage));
createPreviewVibrationAttributes(vibrationUsage)); }
/**
* Play a vibration effect with intensity just selected by the user.
*
* @param vibrator The {@link Vibrator} used to play the vibration.
* @param vibrationAttributes The {@link VibrationAttributes} to indicate the
* vibration information.
*/
public static void playVibrationPreview(Vibrator vibrator,
VibrationAttributes vibrationAttributes) {
vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, vibrationAttributes);
} }
public VibrationPreferenceConfig(Context context, String settingKey, public VibrationPreferenceConfig(Context context, String settingKey,
@@ -135,7 +146,7 @@ public abstract class VibrationPreferenceConfig {
return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT; return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT;
} }
private static VibrationAttributes createPreviewVibrationAttributes( static VibrationAttributes createPreviewVibrationAttributes(
@VibrationAttributes.Usage int vibrationUsage) { @VibrationAttributes.Usage int vibrationUsage) {
return new VibrationAttributes.Builder() return new VibrationAttributes.Builder()
.setUsage(vibrationUsage) .setUsage(vibrationUsage)

View File

@@ -0,0 +1,172 @@
/*
* Copyright (C) 2023 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.accessibility;
import static com.android.settings.accessibility.AccessibilityUtil.State.OFF;
import static com.android.settings.accessibility.AccessibilityUtil.State.ON;
import static com.android.settings.core.BasePreferenceController.AVAILABLE;
import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.os.vibrator.Flags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import androidx.preference.PreferenceScreen;
import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.R;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/** Tests for {@link KeyboardVibrationTogglePreferenceController}. */
@RunWith(RobolectricTestRunner.class)
public class KeyboardVibrationTogglePreferenceControllerTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
private PreferenceScreen mPreferenceScreen;
private Context mContext;
private Resources mResources;
private KeyboardVibrationTogglePreferenceController mController;
private SwitchPreference mPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(ApplicationProvider.getApplicationContext());
mResources = spy(mContext.getResources());
when(mContext.getResources()).thenReturn(mResources);
mController = new KeyboardVibrationTogglePreferenceController(mContext, "preferenceKey");
mPreference = new SwitchPreference(mContext);
when(mPreferenceScreen.findPreference(
mController.getPreferenceKey())).thenReturn(mPreference);
mController.displayPreference(mPreferenceScreen);
}
@Test
public void getAvailabilityStatus_featureSupported_available() {
mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE);
}
@Test
public void getAvailabilityStatus_featureNotSupported_unavailable() {
mSetFlagsRule.enableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(false);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void getAvailabilityStatus_keyboardCategoryDisabled_unavailable() {
mSetFlagsRule.disableFlags(Flags.FLAG_KEYBOARD_CATEGORY_ENABLED);
when(mResources.getBoolean(R.bool.config_keyboard_vibration_supported)).thenReturn(true);
assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE);
}
@Test
public void updateState_mainVibrateDisabled_shouldReturnFalseForCheckedAndEnabled() {
updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, OFF);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isFalse();
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void updateState_mainVibrateEnabled_shouldReturnTrueForEnabled() {
updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
mController.updateState(mPreference);
assertThat(mPreference.isEnabled()).isTrue();
}
@Test
public void isChecked_keyboardVibrateEnabled_shouldReturnTrue() {
updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, ON);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isTrue();
}
@Test
public void isChecked_keyboardVibrateDisabled_shouldReturnFalse() {
updateSystemSetting(VibrationPreferenceConfig.MAIN_SWITCH_SETTING_KEY, ON);
updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, OFF);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isFalse();
}
@Test
public void setChecked_checked_updateSettings() throws Settings.SettingNotFoundException {
// set an off state initially
updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, OFF);
assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(OFF);
mController.setChecked(true);
assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(ON);
}
@Test
public void setChecked_unchecked_updateSettings() throws Settings.SettingNotFoundException {
// set an on state initially
updateSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED, ON);
assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(ON);
mController.setChecked(false);
assertThat(readSystemSetting(Settings.System.KEYBOARD_VIBRATION_ENABLED)).isEqualTo(OFF);
}
private void updateSystemSetting(String key, int value) {
Settings.System.putInt(mContext.getContentResolver(), key, value);
}
private int readSystemSetting(String key) throws Settings.SettingNotFoundException {
return Settings.System.getInt(mContext.getContentResolver(), key);
}
}