/* * 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.accessibility; import static android.os.Vibrator.VibrationIntensity; import android.content.Context; import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.media.AudioAttributes; import android.net.Uri; import android.os.Handler; import android.os.VibrationEffect; import android.os.Vibrator; import android.provider.Settings; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Log; import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.widget.RadioButtonPickerFragment; import com.android.settingslib.widget.CandidateInfo; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; /** * Fragment for changing vibration settings. */ public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragment { private static final String TAG = "VibrationPreferenceFragment"; @VisibleForTesting final static String KEY_INTENSITY_OFF = "intensity_off"; @VisibleForTesting final static String KEY_INTENSITY_LOW = "intensity_low"; @VisibleForTesting final static String KEY_INTENSITY_MEDIUM = "intensity_medium"; @VisibleForTesting final static String KEY_INTENSITY_HIGH = "intensity_high"; // KEY_INTENSITY_ON is only used when the device doesn't support multiple intensity levels. @VisibleForTesting final static String KEY_INTENSITY_ON = "intensity_on"; private final Map mCandidates; private final SettingsObserver mSettingsObserver; public VibrationPreferenceFragment() { mCandidates = new ArrayMap<>(); mSettingsObserver = new SettingsObserver(); } @Override public void onAttach(Context context) { super.onAttach(context); mSettingsObserver.register(); if (mCandidates.isEmpty()) { loadCandidates(context); } } private void loadCandidates(Context context) { final boolean supportsMultipleIntensities = context.getResources().getBoolean( R.bool.config_vibration_supports_multiple_intensities); if (supportsMultipleIntensities) { mCandidates.put(KEY_INTENSITY_OFF, new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, R.string.accessibility_vibration_intensity_off, Vibrator.VIBRATION_INTENSITY_OFF)); mCandidates.put(KEY_INTENSITY_LOW, new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, R.string.accessibility_vibration_intensity_low, Vibrator.VIBRATION_INTENSITY_LOW)); mCandidates.put(KEY_INTENSITY_MEDIUM, new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, R.string.accessibility_vibration_intensity_medium, Vibrator.VIBRATION_INTENSITY_MEDIUM)); mCandidates.put(KEY_INTENSITY_HIGH, new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, R.string.accessibility_vibration_intensity_high, Vibrator.VIBRATION_INTENSITY_HIGH)); } else { mCandidates.put(KEY_INTENSITY_OFF, new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, R.string.switch_off_text, Vibrator.VIBRATION_INTENSITY_OFF)); mCandidates.put(KEY_INTENSITY_ON, new VibrationIntensityCandidateInfo(KEY_INTENSITY_ON, R.string.switch_on_text, getDefaultVibrationIntensity())); } } private boolean hasVibrationEnabledSetting() { return !TextUtils.isEmpty(getVibrationEnabledSetting()); } private void updateSettings(VibrationIntensityCandidateInfo candidate) { boolean vibrationEnabled = candidate.getIntensity() != Vibrator.VIBRATION_INTENSITY_OFF; if (hasVibrationEnabledSetting()) { // Update vibration enabled setting final String vibrationEnabledSetting = getVibrationEnabledSetting(); final boolean wasEnabled = TextUtils.equals( vibrationEnabledSetting, Settings.Global.APPLY_RAMPING_RINGER) ? true : (Settings.System.getInt( getContext().getContentResolver(), vibrationEnabledSetting, 1) == 1); if (vibrationEnabled != wasEnabled) { if (vibrationEnabledSetting.equals(Settings.Global.APPLY_RAMPING_RINGER)) { Settings.Global.putInt(getContext().getContentResolver(), vibrationEnabledSetting, 0); } else { Settings.System.putInt(getContext().getContentResolver(), vibrationEnabledSetting, vibrationEnabled ? 1 : 0); } int previousIntensity = Settings.System.getInt(getContext().getContentResolver(), getVibrationIntensitySetting(), 0); if (vibrationEnabled && previousIntensity == candidate.getIntensity()) { // We can't play preview effect here for all cases because that causes a data // race (VibratorService may access intensity settings before these settings // are updated). But we can't just play it in intensity settings update // observer, because the intensity settings are not changed if we turn the // vibration off, then on. // // In this case we sould play the preview here. // To be refactored in b/132952771 playVibrationPreview(); } } } // There are two conditions that need to change the intensity. // First: Vibration is enabled and we are changing its strength. // Second: There is no setting to enable this vibration, change the intensity directly. if (vibrationEnabled || !hasVibrationEnabledSetting()) { // Update vibration intensity setting Settings.System.putInt(getContext().getContentResolver(), getVibrationIntensitySetting(), candidate.getIntensity()); } } @Override public void onDetach() { super.onDetach(); mSettingsObserver.unregister(); } /** * Get the setting string of the vibration intensity setting this preference is dealing with. */ protected abstract String getVibrationIntensitySetting(); /** * Get the setting string of the vibration enabledness setting this preference is dealing with. */ protected abstract String getVibrationEnabledSetting(); /** * Get the default intensity for the desired setting. */ protected abstract int getDefaultVibrationIntensity(); /** * When a new vibration intensity is selected by the user. */ protected void onVibrationIntensitySelected(int intensity) { } /** * Play a vibration effect with intensity just selected by user */ protected void playVibrationPreview() { Vibrator vibrator = getContext().getSystemService(Vibrator.class); VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); AudioAttributes.Builder builder = new AudioAttributes.Builder(); builder.setUsage(getPreviewVibrationAudioAttributesUsage()); vibrator.vibrate(effect, builder.build()); } /** * Get the AudioAttributes usage for vibration preview. */ protected int getPreviewVibrationAudioAttributesUsage() { return AudioAttributes.USAGE_UNKNOWN; } @Override protected List getCandidates() { List candidates = new ArrayList<>(mCandidates.values()); candidates.sort( Comparator.comparing(VibrationIntensityCandidateInfo::getIntensity).reversed()); return candidates; } @Override protected String getDefaultKey() { int vibrationIntensity = Settings.System.getInt(getContext().getContentResolver(), getVibrationIntensitySetting(), getDefaultVibrationIntensity()); final String vibrationEnabledSetting = getVibrationEnabledSetting(); final boolean vibrationEnabled = TextUtils.equals( vibrationEnabledSetting, Settings.Global.APPLY_RAMPING_RINGER) ? true : (Settings.System.getInt( getContext().getContentResolver(), vibrationEnabledSetting, 1) == 1); if (!vibrationEnabled) { vibrationIntensity = Vibrator.VIBRATION_INTENSITY_OFF; } for (VibrationIntensityCandidateInfo candidate : mCandidates.values()) { final boolean matchesIntensity = candidate.getIntensity() == vibrationIntensity; final boolean matchesOn = candidate.getKey().equals(KEY_INTENSITY_ON) && vibrationIntensity != Vibrator.VIBRATION_INTENSITY_OFF; if (matchesIntensity || matchesOn) { return candidate.getKey(); } } return null; } @Override protected boolean setDefaultKey(String key) { VibrationIntensityCandidateInfo candidate = mCandidates.get(key); if (candidate == null) { Log.e(TAG, "Tried to set unknown intensity (key=" + key + ")!"); return false; } updateSettings(candidate); onVibrationIntensitySelected(candidate.getIntensity()); return true; } @VisibleForTesting class VibrationIntensityCandidateInfo extends CandidateInfo { private String mKey; private int mLabelId; @VibrationIntensity private int mIntensity; public VibrationIntensityCandidateInfo(String key, int labelId, int intensity) { super(true /* enabled */); mKey = key; mLabelId = labelId; mIntensity = intensity; } @Override public CharSequence loadLabel() { return getContext().getString(mLabelId); } @Override public Drawable loadIcon() { return null; } @Override public String getKey() { return mKey; } public int getIntensity() { return mIntensity; } } private class SettingsObserver extends ContentObserver { public SettingsObserver() { super(new Handler()); } public void register() { getContext().getContentResolver().registerContentObserver( Settings.System.getUriFor(getVibrationIntensitySetting()), false, this); } public void unregister() { getContext().getContentResolver().unregisterContentObserver(this); } @Override public void onChange(boolean selfChange, Uri uri) { updateCandidates(); playVibrationPreview(); } } }