blob: c25c38e5a6d97a8db794f80abc96b8c4bf0afd3f [file] [log] [blame]
/*
* Copyright (C) 2022 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.ON;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.ContentObserver;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settingslib.core.AbstractPreferenceController;
/**
* Vibration intensity settings configuration to be shared between different preference
* controllers that handle the same setting key.
*/
public abstract class VibrationPreferenceConfig {
/**
* SettingsProvider key for the main "Vibration & haptics" toggle preference, that can disable
* all device vibrations.
*/
public static final String MAIN_SWITCH_SETTING_KEY = Settings.System.VIBRATE_ON;
private static final VibrationEffect PREVIEW_VIBRATION_EFFECT =
VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
protected final ContentResolver mContentResolver;
private final AudioManager mAudioManager;
private final Vibrator mVibrator;
private final String mSettingKey;
private final String mRingerModeSilentSummary;
private final int mDefaultIntensity;
private final VibrationAttributes mPreviewVibrationAttributes;
/** Returns true if the user setting for enabling device vibrations is enabled. */
public static boolean isMainVibrationSwitchEnabled(ContentResolver contentResolver) {
return Settings.System.getInt(contentResolver, MAIN_SWITCH_SETTING_KEY, ON) == ON;
}
/** Play a vibration effect with intensity just selected by the user. */
public static void playVibrationPreview(Vibrator vibrator,
@VibrationAttributes.Usage int vibrationUsage) {
playVibrationPreview(vibrator, 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,
@VibrationAttributes.Usage int vibrationUsage) {
mContentResolver = context.getContentResolver();
mVibrator = context.getSystemService(Vibrator.class);
mAudioManager = context.getSystemService(AudioManager.class);
mRingerModeSilentSummary = context.getString(
R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary);
mSettingKey = settingKey;
mDefaultIntensity = mVibrator.getDefaultVibrationIntensity(vibrationUsage);
mPreviewVibrationAttributes = createPreviewVibrationAttributes(vibrationUsage);
}
/** Returns the setting key for this setting preference. */
public String getSettingKey() {
return mSettingKey;
}
/** Returns the summary string for this setting preference. */
@Nullable
public CharSequence getSummary() {
return isRestrictedByRingerModeSilent() && isRingerModeSilent()
? mRingerModeSilentSummary : null;
}
/** Returns true if this setting preference is enabled for user update. */
public boolean isPreferenceEnabled() {
return isMainVibrationSwitchEnabled(mContentResolver)
&& (!isRestrictedByRingerModeSilent() || !isRingerModeSilent());
}
/**
* Returns true if this setting preference should be disabled when the device is in silent mode.
*/
public boolean isRestrictedByRingerModeSilent() {
return false;
}
/** Returns the default intensity to be displayed when the setting value is not set. */
public int getDefaultIntensity() {
return mDefaultIntensity;
}
/** Reads setting value for corresponding {@link VibrationPreferenceConfig} */
public int readIntensity() {
return Settings.System.getInt(mContentResolver, mSettingKey, mDefaultIntensity);
}
/** Update setting value for corresponding {@link VibrationPreferenceConfig} */
public boolean updateIntensity(int intensity) {
return Settings.System.putInt(mContentResolver, mSettingKey, intensity);
}
/** Play a vibration effect with intensity just selected by the user. */
public void playVibrationPreview() {
mVibrator.vibrate(PREVIEW_VIBRATION_EFFECT, mPreviewVibrationAttributes);
}
private boolean isRingerModeSilent() {
// AudioManager.isSilentMode() also returns true when ringer mode is VIBRATE.
// The vibration preferences are only disabled when the ringer mode is SILENT.
return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT;
}
static VibrationAttributes createPreviewVibrationAttributes(
@VibrationAttributes.Usage int vibrationUsage) {
return new VibrationAttributes.Builder()
.setUsage(vibrationUsage)
// Enforce fresh settings to be applied for the preview vibration, as they
// are played immediately after the new user values are set.
.setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)
.build();
}
/** {@link ContentObserver} for a setting described by a {@link VibrationPreferenceConfig}. */
public static final class SettingObserver extends ContentObserver {
private static final Uri MAIN_SWITCH_SETTING_URI =
Settings.System.getUriFor(MAIN_SWITCH_SETTING_KEY);
private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER =
new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION);
private final Uri mUri;
@Nullable
private final BroadcastReceiver mRingerModeChangeReceiver;
private AbstractPreferenceController mPreferenceController;
private Preference mPreference;
/** Creates observer for given preference. */
public SettingObserver(VibrationPreferenceConfig preferenceConfig) {
super(new Handler(/* async= */ true));
mUri = Settings.System.getUriFor(preferenceConfig.getSettingKey());
if (preferenceConfig.isRestrictedByRingerModeSilent()) {
// If this preference is restricted by AudioManager.getRingerModeInternal() result
// for the device mode, then listen to changes in that value using the broadcast
// intent action INTERNAL_RINGER_MODE_CHANGED_ACTION.
mRingerModeChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) {
notifyChange();
}
}
};
} else {
// No need to register a receiver if this preference is not affected by ringer mode.
mRingerModeChangeReceiver = null;
}
}
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mUri.equals(uri) || MAIN_SWITCH_SETTING_URI.equals(uri)) {
notifyChange();
}
}
private void notifyChange() {
if (mPreferenceController != null && mPreference != null) {
mPreferenceController.updateState(mPreference);
}
}
/**
* Register this observer to given {@link Context}, to be called from lifecycle
* {@code onStart} method.
*/
public void register(Context context) {
if (mRingerModeChangeReceiver != null) {
context.registerReceiver(mRingerModeChangeReceiver,
INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER);
}
context.getContentResolver().registerContentObserver(
mUri, /* notifyForDescendants= */ false, this);
context.getContentResolver().registerContentObserver(
MAIN_SWITCH_SETTING_URI, /* notifyForDescendants= */ false, this);
}
/**
* Unregister this observer from given {@link Context}, to be called from lifecycle
* {@code onStop} method.
*/
public void unregister(Context context) {
if (mRingerModeChangeReceiver != null) {
context.unregisterReceiver(mRingerModeChangeReceiver);
}
context.getContentResolver().unregisterContentObserver(this);
}
/**
* Binds this observer to given controller and preference, once it has been displayed to the
* user.
*/
public void onDisplayPreference(AbstractPreferenceController controller,
Preference preference) {
mPreferenceController = controller;
mPreference = preference;
}
}
}