blob: 1803a8bafe23bca4dcd7af0631cf8f1b646a1f3d [file] [log] [blame]
/*
* 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<String, VibrationIntensityCandidateInfo> 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<? extends CandidateInfo> getCandidates() {
List<VibrationIntensityCandidateInfo> 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();
}
}
}