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.Looper; 31 import android.os.VibrationAttributes; 32 import android.os.VibrationEffect; 33 import android.os.Vibrator; 34 import android.provider.Settings; 35 36 import androidx.annotation.Nullable; 37 import androidx.preference.Preference; 38 39 import com.android.settings.R; 40 import com.android.settingslib.core.AbstractPreferenceController; 41 42 /** 43 * Vibration intensity settings configuration to be shared between different preference 44 * controllers that handle the same setting key. 45 */ 46 public abstract class VibrationPreferenceConfig { 47 48 /** 49 * SettingsProvider key for the main "Vibration & haptics" toggle preference, that can disable 50 * all device vibrations. 51 */ 52 public static final String MAIN_SWITCH_SETTING_KEY = Settings.System.VIBRATE_ON; 53 private static final VibrationEffect PREVIEW_VIBRATION_EFFECT = 54 VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK); 55 56 protected final ContentResolver mContentResolver; 57 private final AudioManager mAudioManager; 58 private final Vibrator mVibrator; 59 private final String mSettingKey; 60 private final String mRingerModeSilentSummary; 61 private final int mDefaultIntensity; 62 private final VibrationAttributes mPreviewVibrationAttributes; 63 64 /** Returns true if the user setting for enabling device vibrations is enabled. */ isMainVibrationSwitchEnabled(ContentResolver contentResolver)65 public static boolean isMainVibrationSwitchEnabled(ContentResolver contentResolver) { 66 return Settings.System.getInt(contentResolver, MAIN_SWITCH_SETTING_KEY, ON) == ON; 67 } 68 69 /** Play a vibration effect with intensity just selected by the user. */ playVibrationPreview(Vibrator vibrator, @VibrationAttributes.Usage int vibrationUsage)70 public static void playVibrationPreview(Vibrator vibrator, 71 @VibrationAttributes.Usage int vibrationUsage) { 72 vibrator.vibrate(PREVIEW_VIBRATION_EFFECT, 73 createPreviewVibrationAttributes(vibrationUsage)); 74 } 75 VibrationPreferenceConfig(Context context, String settingKey, @VibrationAttributes.Usage int vibrationUsage)76 public VibrationPreferenceConfig(Context context, String settingKey, 77 @VibrationAttributes.Usage int vibrationUsage) { 78 mContentResolver = context.getContentResolver(); 79 mVibrator = context.getSystemService(Vibrator.class); 80 mAudioManager = context.getSystemService(AudioManager.class); 81 mRingerModeSilentSummary = context.getString( 82 R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary); 83 mSettingKey = settingKey; 84 mDefaultIntensity = mVibrator.getDefaultVibrationIntensity(vibrationUsage); 85 mPreviewVibrationAttributes = createPreviewVibrationAttributes(vibrationUsage); 86 } 87 88 /** Returns the setting key for this setting preference. */ getSettingKey()89 public String getSettingKey() { 90 return mSettingKey; 91 } 92 93 /** Returns the summary string for this setting preference. */ 94 @Nullable getSummary()95 public CharSequence getSummary() { 96 return isRestrictedByRingerModeSilent() && isRingerModeSilent() 97 ? mRingerModeSilentSummary : null; 98 } 99 100 /** Returns true if this setting preference is enabled for user update. */ isPreferenceEnabled()101 public boolean isPreferenceEnabled() { 102 return isMainVibrationSwitchEnabled(mContentResolver) 103 && (!isRestrictedByRingerModeSilent() || !isRingerModeSilent()); 104 } 105 106 /** 107 * Returns true if this setting preference should be disabled when the device is in silent mode. 108 */ isRestrictedByRingerModeSilent()109 public boolean isRestrictedByRingerModeSilent() { 110 return false; 111 } 112 113 /** Returns the default intensity to be displayed when the setting value is not set. */ getDefaultIntensity()114 public int getDefaultIntensity() { 115 return mDefaultIntensity; 116 } 117 118 /** Reads setting value for corresponding {@link VibrationPreferenceConfig} */ readIntensity()119 public int readIntensity() { 120 return Settings.System.getInt(mContentResolver, mSettingKey, mDefaultIntensity); 121 } 122 123 /** Update setting value for corresponding {@link VibrationPreferenceConfig} */ updateIntensity(int intensity)124 public boolean updateIntensity(int intensity) { 125 return Settings.System.putInt(mContentResolver, mSettingKey, intensity); 126 } 127 128 /** Play a vibration effect with intensity just selected by the user. */ playVibrationPreview()129 public void playVibrationPreview() { 130 mVibrator.vibrate(PREVIEW_VIBRATION_EFFECT, mPreviewVibrationAttributes); 131 } 132 isRingerModeSilent()133 private boolean isRingerModeSilent() { 134 // AudioManager.isSilentMode() also returns true when ringer mode is VIBRATE. 135 // The vibration preferences are only disabled when the ringer mode is SILENT. 136 return mAudioManager.getRingerModeInternal() == AudioManager.RINGER_MODE_SILENT; 137 } 138 createPreviewVibrationAttributes( @ibrationAttributes.Usage int vibrationUsage)139 static VibrationAttributes createPreviewVibrationAttributes( 140 @VibrationAttributes.Usage int vibrationUsage) { 141 return new VibrationAttributes.Builder() 142 .setUsage(vibrationUsage) 143 .setFlags( 144 // Enforce fresh settings to be applied for the preview vibration, as they 145 // are played immediately after the new user values are set. 146 VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE 147 // Bypass user settings to allow vibration previews to be played while in 148 // limited interruptions' mode, e.g. zen mode. 149 | VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) 150 .build(); 151 } 152 153 /** {@link ContentObserver} for a setting described by a {@link VibrationPreferenceConfig}. */ 154 public static final class SettingObserver extends ContentObserver { 155 private static final Uri MAIN_SWITCH_SETTING_URI = 156 Settings.System.getUriFor(MAIN_SWITCH_SETTING_KEY); 157 private static final IntentFilter INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER = 158 new IntentFilter(AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION); 159 160 private final Uri mUri; 161 @Nullable 162 private final BroadcastReceiver mRingerModeChangeReceiver; 163 164 private AbstractPreferenceController mPreferenceController; 165 private Preference mPreference; 166 167 /** Creates observer for given preference. */ SettingObserver(VibrationPreferenceConfig preferenceConfig)168 public SettingObserver(VibrationPreferenceConfig preferenceConfig) { 169 super(Looper.myLooper() != null ? new Handler(/* async= */ true) : null); 170 mUri = Settings.System.getUriFor(preferenceConfig.getSettingKey()); 171 172 if (preferenceConfig.isRestrictedByRingerModeSilent()) { 173 // If this preference is restricted by AudioManager.getRingerModeInternal() result 174 // for the device mode, then listen to changes in that value using the broadcast 175 // intent action INTERNAL_RINGER_MODE_CHANGED_ACTION. 176 mRingerModeChangeReceiver = new BroadcastReceiver() { 177 @Override 178 public void onReceive(Context context, Intent intent) { 179 final String action = intent.getAction(); 180 if (AudioManager.INTERNAL_RINGER_MODE_CHANGED_ACTION.equals(action)) { 181 notifyChange(); 182 } 183 } 184 }; 185 } else { 186 // No need to register a receiver if this preference is not affected by ringer mode. 187 mRingerModeChangeReceiver = null; 188 } 189 } 190 191 @Override onChange(boolean selfChange, Uri uri)192 public void onChange(boolean selfChange, Uri uri) { 193 if (mUri.equals(uri) || MAIN_SWITCH_SETTING_URI.equals(uri)) { 194 notifyChange(); 195 } 196 } 197 notifyChange()198 private void notifyChange() { 199 if (mPreferenceController != null && mPreference != null) { 200 mPreferenceController.updateState(mPreference); 201 } 202 } 203 204 /** 205 * Register this observer to given {@link Context}, to be called from lifecycle 206 * {@code onStart} method. 207 */ register(Context context)208 public void register(Context context) { 209 if (mRingerModeChangeReceiver != null) { 210 context.registerReceiver(mRingerModeChangeReceiver, 211 INTERNAL_RINGER_MODE_CHANGED_INTENT_FILTER); 212 } 213 context.getContentResolver().registerContentObserver( 214 mUri, /* notifyForDescendants= */ false, this); 215 context.getContentResolver().registerContentObserver( 216 MAIN_SWITCH_SETTING_URI, /* notifyForDescendants= */ false, this); 217 } 218 219 /** 220 * Unregister this observer from given {@link Context}, to be called from lifecycle 221 * {@code onStop} method. 222 */ unregister(Context context)223 public void unregister(Context context) { 224 if (mRingerModeChangeReceiver != null) { 225 context.unregisterReceiver(mRingerModeChangeReceiver); 226 } 227 context.getContentResolver().unregisterContentObserver(this); 228 } 229 230 /** 231 * Binds this observer to given controller and preference, once it has been displayed to the 232 * user. 233 */ onDisplayPreference(AbstractPreferenceController controller, Preference preference)234 public void onDisplayPreference(AbstractPreferenceController controller, 235 Preference preference) { 236 mPreferenceController = controller; 237 mPreference = preference; 238 } 239 } 240 } 241