1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.accessibility; 18 19 import static com.android.settings.accessibility.AccessibilityUtil.State.ON; 20 21 import android.content.BroadcastReceiver; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.IntentFilter; 26 import android.database.ContentObserver; 27 import android.media.AudioManager; 28 import android.net.Uri; 29 import android.os.Handler; 30 import android.os.VibrationAttributes; 31 import android.os.VibrationEffect; 32 import android.os.Vibrator; 33 import android.provider.Settings; 34 35 import androidx.annotation.Nullable; 36 import androidx.preference.Preference; 37 38 import com.android.settings.R; 39 import com.android.settingslib.core.AbstractPreferenceController; 40 41 /** 42 * Vibration intensity settings configuration to be shared between different preference 43 * controllers that handle the same setting key. 44 */ 45 public abstract class VibrationPreferenceConfig { 46 47 /** 48 * SettingsProvider key for the main "Vibration & haptics" toggle preference, that can disable 49 * all device vibrations. 50 */ 51 public static final String MAIN_SWITCH_SETTING_KEY = Settings.System.VIBRATE_ON; 52 private static final VibrationEffect PREVIEW_VIBRATION_EFFECT = 53 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); 54 55 protected final ContentResolver mContentResolver; 56 private final AudioManager mAudioManager; 57 private final Vibrator mVibrator; 58 private final String mSettingKey; 59 private final String mRingerModeSilentSummary; 60 private final int mDefaultIntensity; 61 private final VibrationAttributes mPreviewVibrationAttributes; 62 63 /** Returns true if the user setting for enabling device vibrations is enabled. */ isMainVibrationSwitchEnabled(ContentResolver contentResolver)64 public static boolean isMainVibrationSwitchEnabled(ContentResolver contentResolver) { 65 return Settings.System.getInt(contentResolver, MAIN_SWITCH_SETTING_KEY, ON) == ON; 66 } 67 68 /** Play a vibration effect with intensity just selected by the user. */ playVibrationPreview(Vibrator vibrator, @VibrationAttributes.Usage int vibrationUsage)69 public static void playVibrationPreview(Vibrator vibrator, 70 @VibrationAttributes.Usage int vibrationUsage) { 71 vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, 72 createPreviewVibrationAttributes(vibrationUsage)); 73 } 74 VibrationPreferenceConfig(Context context, String settingKey, @VibrationAttributes.Usage int vibrationUsage)75 public VibrationPreferenceConfig(Context context, String settingKey, 76 @VibrationAttributes.Usage int vibrationUsage) { 77 mContentResolver = context.getContentResolver(); 78 mVibrator = context.getSystemService(Vibrator.class); 79 mAudioManager = context.getSystemService(AudioManager.class); 80 mRingerModeSilentSummary = context.getString( 81 R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary); 82 mSettingKey = settingKey; 83 mDefaultIntensity = mVibrator.getDefaultVibrationIntensity(vibrationUsage); 84 mPreviewVibrationAttributes = createPreviewVibrationAttributes(vibrationUsage); 85 } 86 87 /** Returns the setting key for this setting preference. */ getSettingKey()88 public String getSettingKey() { 89 return mSettingKey; 90 } 91 92 /** Returns the summary string for this setting preference. */ 93 @Nullable getSummary()94 public CharSequence getSummary() { 95 return isRestrictedByRingerModeSilent() && isRingerModeSilent() 96 ? mRingerModeSilentSummary : null; 97 } 98 99 /** Returns true if this setting preference is enabled for user update. */ isPreferenceEnabled()100 public boolean isPreferenceEnabled() { 101 return isMainVibrationSwitchEnabled(mContentResolver) 102 && (!isRestrictedByRingerModeSilent() || !isRingerModeSilent()); 103 } 104 105 /** 106 * Returns true if this setting preference should be disabled when the device is in silent mode. 107 */ isRestrictedByRingerModeSilent()108 public boolean isRestrictedByRingerModeSilent() { 109 return false; 110 } 111 112 /** Returns the default intensity to be displayed when the setting value is not set. */ getDefaultIntensity()113 public int getDefaultIntensity() { 114 return mDefaultIntensity; 115 } 116 117 /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */ readIntensity()118 public int readIntensity() { 119 return Settings.System.getInt(mContentResolver, mSettingKey, mDefaultIntensity); 120 } 121 122 /** Update setting value for corresponding {@link VibrationPreferenceConfig} */ updateIntensity(int intensity)123 public boolean updateIntensity(int intensity) { 124 return Settings.System.putInt(mContentResolver, mSettingKey, intensity); 125 } 126 127 /** Play a vibration effect with intensity just selected by the user. */ playVibrationPreview()128 public void playVibrationPreview() { 129 mVibrator.vibrate(PREVIEW_VIBRATION_EFFECT, mPreviewVibrationAttributes); 130 } 131 isRingerModeSilent()132 private boolean isRingerModeSilent() { 133 // AudioManager.isSilentMode() also returns true when ringer mode is VIBRATE. 134 // The vibration preferences are only disabled when the ringer mode is SILENT. 135 return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT; 136 } 137 createPreviewVibrationAttributes( @ibrationAttributes.Usage int vibrationUsage)138 private static VibrationAttributes createPreviewVibrationAttributes( 139 @VibrationAttributes.Usage int vibrationUsage) { 140 return new VibrationAttributes.Builder() 141 .setUsage(vibrationUsage) 142 // Enforce fresh settings to be applied for the preview vibration, as they 143 // are played immediately after the new user values are set. 144 .setFlags(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE) 145 .build(); 146 } 147 148 /** {@link ContentObserver} for a setting described by a {@link VibrationPreferenceConfig}. */ 149 public static final class SettingObserver extends ContentObserver { 150 private static final Uri MAIN_SWITCH_SETTING_URI = 151 Settings.System.getUriFor(MAIN_SWITCH_SETTING_KEY); 152 private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER = 153 new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 154 155 private final Uri mUri; 156 @Nullable 157 private final BroadcastReceiver mRingerModeChangeReceiver; 158 159 private AbstractPreferenceController mPreferenceController; 160 private Preference mPreference; 161 162 /** Creates observer for given preference. */ SettingObserver(VibrationPreferenceConfig preferenceConfig)163 public SettingObserver(VibrationPreferenceConfig preferenceConfig) { 164 super(new Handler(/* async= */ true)); 165 mUri = Settings.System.getUriFor(preferenceConfig.getSettingKey()); 166 167 if (preferenceConfig.isRestrictedByRingerModeSilent()) { 168 // If this preference is restricted by AudioManager.getRingerModeInternal() result 169 // for the device mode, then listen to changes in that value using the broadcast 170 // intent action INTERNAL_RINGER_MODE_CHANGED_ACTION. 171 mRingerModeChangeReceiver = new BroadcastReceiver() { 172 @Override 173 public void onReceive(Context context, Intent intent) { 174 final String action = intent.getAction(); 175 if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { 176 notifyChange(); 177 } 178 } 179 }; 180 } else { 181 // No need to register a receiver if this preference is not affected by ringer mode. 182 mRingerModeChangeReceiver = null; 183 } 184 } 185 186 @Override onChange(boolean selfChange, Uri uri)187 public void onChange(boolean selfChange, Uri uri) { 188 if (mUri.equals(uri) || MAIN_SWITCH_SETTING_URI.equals(uri)) { 189 notifyChange(); 190 } 191 } 192 notifyChange()193 private void notifyChange() { 194 if (mPreferenceController != null && mPreference != null) { 195 mPreferenceController.updateState(mPreference); 196 } 197 } 198 199 /** 200 * Register this observer to given {@link Context}, to be called from lifecycle 201 * {@code onStart} method. 202 */ register(Context context)203 public void register(Context context) { 204 if (mRingerModeChangeReceiver != null) { 205 context.registerReceiver(mRingerModeChangeReceiver, 206 INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); 207 } 208 context.getContentResolver().registerContentObserver( 209 mUri, /* notifyForDescendants= */ false, this); 210 context.getContentResolver().registerContentObserver( 211 MAIN_SWITCH_SETTING_URI, /* notifyForDescendants= */ false, this); 212 } 213 214 /** 215 * Unregister this observer from given {@link Context}, to be called from lifecycle 216 * {@code onStop} method. 217 */ unregister(Context context)218 public void unregister(Context context) { 219 if (mRingerModeChangeReceiver != null) { 220 context.unregisterReceiver(mRingerModeChangeReceiver); 221 } 222 context.getContentResolver().unregisterContentObserver(this); 223 } 224 225 /** 226 * Binds this observer to given controller and preference, once it has been displayed to the 227 * user. 228 */ onDisplayPreference(AbstractPreferenceController controller, Preference preference)229 public void onDisplayPreference(AbstractPreferenceController controller, 230 Preference preference) { 231 mPreferenceController = controller; 232 mPreference = preference; 233 } 234 } 235 } 236