Disable ring, notification and touch vibration in silent mode

Disable haptic settings for ring, notification and touch feedback when
the phone ringer mode is set to silent.

Preserve the setting values and display message explaining why those
settings are disabled.

Bug: 203188852
Test: *Vibration[Intensity|Toggle]PreferenceControllerTest
Change-Id: I6c24079ece9d637f2feb487f756937888a92caa7
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ec72321..70a47fe 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -5457,6 +5457,8 @@
     <string name="accessibility_vibration_settings_title">Vibration &amp; haptics</string>
     <!-- Summary for preference screen for configuring vibrations. [CHAR LIMIT=NONE] -->
     <string name="accessibility_vibration_settings_summary">Control the vibration strength for different usages</string>
+    <!-- Summary for vibration preference shown when it is disabled because the device is in silent mode. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_vibration_setting_disabled_for_silent_mode_summary">Setting disabled because device is set to silent</string>
     <!-- Title for the category of preferences to configure device vibrations related to calls. [CHAR LIMIT=NONE] -->
     <string name="accessibility_call_vibration_category_title">Calls</string>
     <!-- Title for the category of preferences to configure device vibrations related to notifications and alarms. [CHAR LIMIT=NONE] -->
diff --git a/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceController.java b/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceController.java
index 98fd5f2..05dc784 100644
--- a/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceController.java
+++ b/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceController.java
@@ -38,6 +38,12 @@
         }
 
         @Override
+        public boolean isRestrictedByRingerModeSilent() {
+            // Touch feedback is disabled when the phone is in silent mode.
+            return true;
+        }
+
+        @Override
         public int readIntensity() {
             final int hapticFeedbackEnabled = Settings.System.getInt(mContentResolver,
                     Settings.System.HAPTIC_FEEDBACK_ENABLED, ON);
diff --git a/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceController.java b/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceController.java
index cee45f6..951d322 100644
--- a/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceController.java
+++ b/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceController.java
@@ -32,6 +32,12 @@
             super(context, Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                     VibrationAttributes.USAGE_NOTIFICATION);
         }
+
+        @Override
+        public boolean isRestrictedByRingerModeSilent() {
+            // Notifications never vibrate when the phone is in silent mode.
+            return true;
+        }
     }
 
     public NotificationVibrationIntensityPreferenceController(Context context,
diff --git a/src/com/android/settings/accessibility/RingVibrationPreferenceConfig.java b/src/com/android/settings/accessibility/RingVibrationPreferenceConfig.java
index da446d7..f4ec747 100644
--- a/src/com/android/settings/accessibility/RingVibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/RingVibrationPreferenceConfig.java
@@ -36,6 +36,12 @@
     }
 
     @Override
+    public boolean isRestrictedByRingerModeSilent() {
+        // Incoming calls never vibrate when the phone is in silent mode.
+        return true;
+    }
+
+    @Override
     public int readIntensity() {
         final int vibrateWhenRinging = Settings.System.getInt(mContentResolver,
                 Settings.System.VIBRATE_WHEN_RINGING, ON);
diff --git a/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java b/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java
index b3e8168..6441eeb 100644
--- a/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java
+++ b/src/com/android/settings/accessibility/VibrationIntensityPreferenceController.java
@@ -58,12 +58,12 @@
 
     @Override
     public void onStart() {
-        mSettingsContentObserver.register(mContext.getContentResolver());
+        mSettingsContentObserver.register(mContext);
     }
 
     @Override
     public void onStop() {
-        mSettingsContentObserver.unregister(mContext.getContentResolver());
+        mSettingsContentObserver.unregister(mContext);
     }
 
     @Override
@@ -72,6 +72,7 @@
         final SeekBarPreference preference = screen.findPreference(getPreferenceKey());
         mSettingsContentObserver.onDisplayPreference(this, preference);
         preference.setEnabled(mPreferenceConfig.isPreferenceEnabled());
+        preference.setSummaryProvider(unused -> mPreferenceConfig.getSummary());
         // TODO: remove setContinuousUpdates and replace with a different way to play the haptic
         // preview without relying on the setting being propagated to the service.
         preference.setContinuousUpdates(true);
diff --git a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
index 1b0b163..9208b18 100644
--- a/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
+++ b/src/com/android/settings/accessibility/VibrationPreferenceConfig.java
@@ -18,9 +18,13 @@
 
 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;
@@ -28,8 +32,10 @@
 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;
 
 /**
@@ -45,8 +51,10 @@
     public static final String MAIN_SWITCH_SETTING_KEY = Settings.System.VIBRATE_ON;
 
     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 mVibrationAttributes;
 
@@ -58,6 +66,9 @@
     public VibrationPreferenceConfig(Context context, String settingKey, 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);
         mVibrationAttributes = new VibrationAttributes.Builder()
@@ -70,9 +81,24 @@
         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);
+        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. */
@@ -96,12 +122,23 @@
                 mVibrationAttributes);
     }
 
+    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;
+    }
+
     /** {@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;
 
@@ -109,35 +146,63 @@
         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 (mPreferenceController == null || mPreference == null) {
-                // onDisplayPreference not triggered yet, nothing to update.
-                return;
-            }
             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 ContentResolver}, to be called from lifecycle
+         * Register this observer to given {@link Context}, to be called from lifecycle
          * {@code onStart} method.
          */
-        public void register(ContentResolver contentResolver) {
-            contentResolver.registerContentObserver(mUri, /* notifyForDescendants= */ false, this);
-            contentResolver.registerContentObserver(MAIN_SWITCH_SETTING_URI,
-                    /* notifyForDescendants= */ false, this);
+        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 ContentResolver}, to be called from lifecycle
+         * Unregister this observer from given {@link Context}, to be called from lifecycle
          * {@code onStop} method.
          */
-        public void unregister(ContentResolver contentResolver) {
-            contentResolver.unregisterContentObserver(this);
+        public void unregister(Context context) {
+            if (mRingerModeChangeReceiver != null) {
+                context.unregisterReceiver(mRingerModeChangeReceiver);
+            }
+            context.getContentResolver().unregisterContentObserver(this);
         }
 
         /**
diff --git a/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java b/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
index 37a0257..8d1b43e 100644
--- a/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceController.java
@@ -93,7 +93,7 @@
 
     @Override
     public void onStart() {
-        mRingSettingObserver.register(mContext.getContentResolver());
+        mRingSettingObserver.register(mContext);
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.APPLY_RAMPING_RINGER),
                 /* notifyForDescendants= */ false,
@@ -102,7 +102,7 @@
 
     @Override
     public void onStop() {
-        mRingSettingObserver.unregister(mContext.getContentResolver());
+        mRingSettingObserver.unregister(mContext);
         mContext.getContentResolver().unregisterContentObserver(mSettingObserver);
     }
 
diff --git a/src/com/android/settings/accessibility/VibrationTogglePreferenceController.java b/src/com/android/settings/accessibility/VibrationTogglePreferenceController.java
index 8f158cc..c60158d 100644
--- a/src/com/android/settings/accessibility/VibrationTogglePreferenceController.java
+++ b/src/com/android/settings/accessibility/VibrationTogglePreferenceController.java
@@ -45,12 +45,12 @@
 
     @Override
     public void onStart() {
-        mSettingsContentObserver.register(mContext.getContentResolver());
+        mSettingsContentObserver.register(mContext);
     }
 
     @Override
     public void onStop() {
-        mSettingsContentObserver.unregister(mContext.getContentResolver());
+        mSettingsContentObserver.unregister(mContext);
     }
 
     @Override
@@ -66,6 +66,7 @@
         super.updateState(preference);
         if (preference != null) {
             preference.setEnabled(mPreferenceConfig.isPreferenceEnabled());
+            preference.setSummary(mPreferenceConfig.getSummary());
         }
     }
 
diff --git a/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceControllerTest.java
index 2273a3e..9d87c93 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackIntensityPreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -48,6 +50,7 @@
     private static final int ON = 1;
 
     @Mock private PreferenceScreen mScreen;
+    @Mock private AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -59,7 +62,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new HapticFeedbackIntensityPreferenceController(mContext, PREFERENCE_KEY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -89,6 +94,54 @@
     }
 
     @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+        // TODO(b/136805769): summary is broken in SeekBarPreference, enable this once fixed
+//        assertThat(mPreference.getSummary()).isNotNull();
+//        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+//                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_hapticFeedbackDisabled_shouldDisplayAlwaysOff() {
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, OFF);
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+    }
+
+    @Test
     public void updateState_shouldDisplayIntensityInSliderPosition() {
         updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
         mController.updateState(mPreference);
diff --git a/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackTogglePreferenceControllerTest.java
index f992b0e..3e8aeac 100644
--- a/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/HapticFeedbackTogglePreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -29,6 +31,7 @@
 import androidx.preference.SwitchPreference;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
@@ -48,6 +51,7 @@
     private static final int ON = 1;
 
     @Mock private PreferenceScreen mScreen;
+    @Mock AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -59,7 +63,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new HapticFeedbackTogglePreferenceController(mContext, PREFERENCE_KEY);
         mLifecycle.addObserver(mController);
@@ -84,6 +90,54 @@
         assertThat(mPreference.isChecked()).isTrue();
     }
 
+
+    @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_hapticFeedbackDisabled_shouldDisplayAlwaysOff() {
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_ENABLED, OFF);
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
     @Test
     public void updateState_shouldDisplayOnOffState() {
         updateSetting(Settings.System.HAPTIC_FEEDBACK_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
diff --git a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java
index d0be8b5..9533c53 100644
--- a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationIntensityPreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -46,6 +48,7 @@
     private static final String PREFERENCE_KEY = "preference_key";
 
     @Mock private PreferenceScreen mScreen;
+    @Mock private AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -57,7 +60,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new NotificationVibrationIntensityPreferenceController(mContext,
                 PREFERENCE_KEY, Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -86,6 +91,34 @@
                 mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION));
     }
 
+
+    @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+        // TODO(b/136805769): summary is broken in SeekBarPreference, enable this once fixed
+//        assertThat(mPreference.getSummary()).isNotNull();
+//        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+//                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
     @Test
     public void updateState_shouldDisplayIntensityInSliderPosition() {
         updateSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
diff --git a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationTogglePreferenceControllerTest.java
index adf9436..4b6f686 100644
--- a/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/NotificationVibrationTogglePreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -29,6 +31,7 @@
 import androidx.preference.SwitchPreference;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
@@ -46,6 +49,7 @@
     private static final String PREFERENCE_KEY = "preference_key";
 
     @Mock private PreferenceScreen mScreen;
+    @Mock private AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -57,7 +61,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new NotificationVibrationTogglePreferenceController(mContext, PREFERENCE_KEY);
         mLifecycle.addObserver(mController);
@@ -83,6 +89,32 @@
     }
 
     @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
     public void updateState_shouldDisplayOnOffState() {
         updateSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
diff --git a/tests/robotests/src/com/android/settings/accessibility/RingVibrationIntensityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/RingVibrationIntensityPreferenceControllerTest.java
index d891926..2bffaf4 100644
--- a/tests/robotests/src/com/android/settings/accessibility/RingVibrationIntensityPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/RingVibrationIntensityPreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -49,6 +51,7 @@
     private static final int ON = 1;
 
     @Mock private PreferenceScreen mScreen;
+    @Mock private AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -60,7 +63,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new RingVibrationIntensityPreferenceController(mContext, PREFERENCE_KEY,
                 Vibrator.VIBRATION_INTENSITY_HIGH);
@@ -90,6 +95,55 @@
     }
 
     @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+        // TODO(b/136805769): summary is broken in SeekBarPreference, enable this once fixed
+//        assertThat(mPreference.getSummary()).isNotNull();
+//        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+//                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_LOW);
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_vibrateWhenRingingAndRampingRingerOff_shouldDisplayAlwaysOff() {
+        when(mAudioManager.isRampingRingerEnabled()).thenReturn(false);
+        updateSetting(Settings.System.VIBRATE_WHEN_RINGING, OFF);
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        mController.updateState(mPreference);
+        assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
+    }
+
+    @Test
     public void updateState_shouldDisplayIntensityInSliderPosition() {
         updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
         mController.updateState(mPreference);
@@ -109,7 +163,6 @@
         assertThat(mPreference.getProgress()).isEqualTo(Vibrator.VIBRATION_INTENSITY_OFF);
     }
 
-
     @Test
     @Ignore
     public void setProgress_updatesIntensityAndDependentSettings() throws Exception {
diff --git a/tests/robotests/src/com/android/settings/accessibility/RingVibrationTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/RingVibrationTogglePreferenceControllerTest.java
index 8e85c86..08ad1cd 100644
--- a/tests/robotests/src/com/android/settings/accessibility/RingVibrationTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/RingVibrationTogglePreferenceControllerTest.java
@@ -18,9 +18,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.media.AudioManager;
 import android.os.VibrationAttributes;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -29,6 +31,7 @@
 import androidx.preference.SwitchPreference;
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 
@@ -48,6 +51,7 @@
     private static final int ON = 1;
 
     @Mock private PreferenceScreen mScreen;
+    @Mock private AudioManager mAudioManager;
 
     private Lifecycle mLifecycle;
     private Context mContext;
@@ -59,7 +63,9 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mLifecycle = new Lifecycle(() -> mLifecycle);
-        mContext = ApplicationProvider.getApplicationContext();
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mVibrator = mContext.getSystemService(Vibrator.class);
         mController = new RingVibrationTogglePreferenceController(mContext, PREFERENCE_KEY);
         mLifecycle.addObserver(mController);
@@ -85,6 +91,54 @@
     }
 
     @Test
+    public void updateState_ringerModeUpdates_shouldPreserveSettingAndDisplaySummary() {
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_SILENT);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+        assertThat(mPreference.getSummary()).isNotNull();
+        assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
+                R.string.accessibility_vibration_setting_disabled_for_silent_mode_summary));
+        assertThat(mPreference.isEnabled()).isFalse();
+
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isTrue();
+        assertThat(mPreference.getSummary()).isNull();
+        assertThat(mPreference.isEnabled()).isTrue();
+    }
+
+    @Test
+    public void updateState_vibrateWhenRingingAndRampingRingerOff_shouldDisplayAlwaysOff() {
+        when(mAudioManager.isRampingRingerEnabled()).thenReturn(false);
+        updateSetting(Settings.System.VIBRATE_WHEN_RINGING, OFF);
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_LOW);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+
+        updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_OFF);
+        mController.updateState(mPreference);
+        assertThat(mPreference.isChecked()).isFalse();
+    }
+
+    @Test
     public void updateState_shouldDisplayOnOffState() {
         updateSetting(Settings.System.RING_VIBRATION_INTENSITY, Vibrator.VIBRATION_INTENSITY_HIGH);
         mController.updateState(mPreference);
diff --git a/tests/robotests/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceControllerTest.java
index 1f2c0d9..9e31130 100644
--- a/tests/robotests/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/accessibility/VibrationRampingRingerTogglePreferenceControllerTest.java
@@ -70,6 +70,7 @@
         mContext = spy(ApplicationProvider.getApplicationContext());
         when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
         when(mContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+        when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         mController = new VibrationRampingRingerTogglePreferenceController(mContext,
                 PREFERENCE_KEY, mDeviceConfigProvider);
         mLifecycle.addObserver(mController);