1 /* 2 * Copyright (C) 2018 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 package com.android.settings.accessibility; 17 18 import static android.os.Vibrator.VibrationIntensity; 19 20 import android.content.Context; 21 import android.database.ContentObserver; 22 import android.graphics.drawable.Drawable; 23 import android.media.AudioAttributes; 24 import android.net.Uri; 25 import android.os.Handler; 26 import android.os.VibrationEffect; 27 import android.os.Vibrator; 28 import android.provider.Settings; 29 import android.text.TextUtils; 30 import android.util.ArrayMap; 31 import android.util.Log; 32 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.settings.R; 36 import com.android.settings.widget.RadioButtonPickerFragment; 37 import com.android.settingslib.widget.CandidateInfo; 38 39 import java.util.ArrayList; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.Map; 43 44 /** 45 * Fragment for changing vibration settings. 46 */ 47 public abstract class VibrationPreferenceFragment extends RadioButtonPickerFragment { 48 private static final String TAG = "VibrationPreferenceFragment"; 49 50 @VisibleForTesting 51 final static String KEY_INTENSITY_OFF = "intensity_off"; 52 @VisibleForTesting 53 final static String KEY_INTENSITY_LOW = "intensity_low"; 54 @VisibleForTesting 55 final static String KEY_INTENSITY_MEDIUM = "intensity_medium"; 56 @VisibleForTesting 57 final static String KEY_INTENSITY_HIGH = "intensity_high"; 58 // KEY_INTENSITY_ON is only used when the device doesn't support multiple intensity levels. 59 @VisibleForTesting 60 final static String KEY_INTENSITY_ON = "intensity_on"; 61 62 private final Map<String, VibrationIntensityCandidateInfo> mCandidates; 63 private final SettingsObserver mSettingsObserver; 64 VibrationPreferenceFragment()65 public VibrationPreferenceFragment() { 66 mCandidates = new ArrayMap<>(); 67 mSettingsObserver = new SettingsObserver(); 68 } 69 70 @Override onAttach(Context context)71 public void onAttach(Context context) { 72 super.onAttach(context); 73 mSettingsObserver.register(); 74 if (mCandidates.isEmpty()) { 75 loadCandidates(context); 76 } 77 } 78 loadCandidates(Context context)79 private void loadCandidates(Context context) { 80 final boolean supportsMultipleIntensities = context.getResources().getBoolean( 81 R.bool.config_vibration_supports_multiple_intensities); 82 if (supportsMultipleIntensities) { 83 mCandidates.put(KEY_INTENSITY_OFF, 84 new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, 85 R.string.accessibility_vibration_intensity_off, 86 Vibrator.VIBRATION_INTENSITY_OFF)); 87 mCandidates.put(KEY_INTENSITY_LOW, 88 new VibrationIntensityCandidateInfo(KEY_INTENSITY_LOW, 89 R.string.accessibility_vibration_intensity_low, 90 Vibrator.VIBRATION_INTENSITY_LOW)); 91 mCandidates.put(KEY_INTENSITY_MEDIUM, 92 new VibrationIntensityCandidateInfo(KEY_INTENSITY_MEDIUM, 93 R.string.accessibility_vibration_intensity_medium, 94 Vibrator.VIBRATION_INTENSITY_MEDIUM)); 95 mCandidates.put(KEY_INTENSITY_HIGH, 96 new VibrationIntensityCandidateInfo(KEY_INTENSITY_HIGH, 97 R.string.accessibility_vibration_intensity_high, 98 Vibrator.VIBRATION_INTENSITY_HIGH)); 99 } else { 100 mCandidates.put(KEY_INTENSITY_OFF, 101 new VibrationIntensityCandidateInfo(KEY_INTENSITY_OFF, 102 R.string.switch_off_text, Vibrator.VIBRATION_INTENSITY_OFF)); 103 mCandidates.put(KEY_INTENSITY_ON, 104 new VibrationIntensityCandidateInfo(KEY_INTENSITY_ON, 105 R.string.switch_on_text, getDefaultVibrationIntensity())); 106 } 107 } 108 hasVibrationEnabledSetting()109 private boolean hasVibrationEnabledSetting() { 110 return !TextUtils.isEmpty(getVibrationEnabledSetting()); 111 } 112 updateSettings(VibrationIntensityCandidateInfo candidate)113 private void updateSettings(VibrationIntensityCandidateInfo candidate) { 114 boolean vibrationEnabled = candidate.getIntensity() != Vibrator.VIBRATION_INTENSITY_OFF; 115 if (hasVibrationEnabledSetting()) { 116 // Update vibration enabled setting 117 final String vibrationEnabledSetting = getVibrationEnabledSetting(); 118 final boolean wasEnabled = TextUtils.equals( 119 vibrationEnabledSetting, Settings.Global.APPLY_RAMPING_RINGER) 120 ? true 121 : (Settings.System.getInt( 122 getContext().getContentResolver(), vibrationEnabledSetting, 1) == 1); 123 if (vibrationEnabled != wasEnabled) { 124 if (vibrationEnabledSetting.equals(Settings.Global.APPLY_RAMPING_RINGER)) { 125 Settings.Global.putInt(getContext().getContentResolver(), 126 vibrationEnabledSetting, 0); 127 } else { 128 Settings.System.putInt(getContext().getContentResolver(), 129 vibrationEnabledSetting, vibrationEnabled ? 1 : 0); 130 } 131 132 int previousIntensity = Settings.System.getInt(getContext().getContentResolver(), 133 getVibrationIntensitySetting(), 0); 134 if (vibrationEnabled && previousIntensity == candidate.getIntensity()) { 135 // We can't play preview effect here for all cases because that causes a data 136 // race (VibratorService may access intensity settings before these settings 137 // are updated). But we can't just play it in intensity settings update 138 // observer, because the intensity settings are not changed if we turn the 139 // vibration off, then on. 140 // 141 // In this case we sould play the preview here. 142 // To be refactored in b/132952771 143 playVibrationPreview(); 144 } 145 } 146 } 147 // There are two conditions that need to change the intensity. 148 // First: Vibration is enabled and we are changing its strength. 149 // Second: There is no setting to enable this vibration, change the intensity directly. 150 if (vibrationEnabled || !hasVibrationEnabledSetting()) { 151 // Update vibration intensity setting 152 Settings.System.putInt(getContext().getContentResolver(), 153 getVibrationIntensitySetting(), candidate.getIntensity()); 154 } 155 } 156 157 @Override onDetach()158 public void onDetach() { 159 super.onDetach(); 160 mSettingsObserver.unregister(); 161 } 162 163 /** 164 * Get the setting string of the vibration intensity setting this preference is dealing with. 165 */ getVibrationIntensitySetting()166 protected abstract String getVibrationIntensitySetting(); 167 168 /** 169 * Get the setting string of the vibration enabledness setting this preference is dealing with. 170 */ getVibrationEnabledSetting()171 protected abstract String getVibrationEnabledSetting(); 172 173 /** 174 * Get the default intensity for the desired setting. 175 */ getDefaultVibrationIntensity()176 protected abstract int getDefaultVibrationIntensity(); 177 178 /** 179 * When a new vibration intensity is selected by the user. 180 */ onVibrationIntensitySelected(int intensity)181 protected void onVibrationIntensitySelected(int intensity) { } 182 183 /** 184 * Play a vibration effect with intensity just selected by user 185 */ playVibrationPreview()186 protected void playVibrationPreview() { 187 Vibrator vibrator = getContext().getSystemService(Vibrator.class); 188 VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK); 189 AudioAttributes.Builder builder = new AudioAttributes.Builder(); 190 builder.setUsage(getPreviewVibrationAudioAttributesUsage()); 191 vibrator.vibrate(effect, builder.build()); 192 } 193 194 /** 195 * Get the AudioAttributes usage for vibration preview. 196 */ getPreviewVibrationAudioAttributesUsage()197 protected int getPreviewVibrationAudioAttributesUsage() { 198 return AudioAttributes.USAGE_UNKNOWN; 199 } 200 201 @Override getCandidates()202 protected List<? extends CandidateInfo> getCandidates() { 203 List<VibrationIntensityCandidateInfo> candidates = new ArrayList<>(mCandidates.values()); 204 candidates.sort( 205 Comparator.comparing(VibrationIntensityCandidateInfo::getIntensity).reversed()); 206 return candidates; 207 } 208 209 @Override getDefaultKey()210 protected String getDefaultKey() { 211 int vibrationIntensity = Settings.System.getInt(getContext().getContentResolver(), 212 getVibrationIntensitySetting(), getDefaultVibrationIntensity()); 213 final String vibrationEnabledSetting = getVibrationEnabledSetting(); 214 final boolean vibrationEnabled = TextUtils.equals( 215 vibrationEnabledSetting, Settings.Global.APPLY_RAMPING_RINGER) 216 ? true 217 : (Settings.System.getInt( 218 getContext().getContentResolver(), vibrationEnabledSetting, 1) == 1); 219 if (!vibrationEnabled) { 220 vibrationIntensity = Vibrator.VIBRATION_INTENSITY_OFF; 221 } 222 for (VibrationIntensityCandidateInfo candidate : mCandidates.values()) { 223 final boolean matchesIntensity = candidate.getIntensity() == vibrationIntensity; 224 final boolean matchesOn = candidate.getKey().equals(KEY_INTENSITY_ON) 225 && vibrationIntensity != Vibrator.VIBRATION_INTENSITY_OFF; 226 if (matchesIntensity || matchesOn) { 227 return candidate.getKey(); 228 } 229 } 230 return null; 231 } 232 233 @Override setDefaultKey(String key)234 protected boolean setDefaultKey(String key) { 235 VibrationIntensityCandidateInfo candidate = mCandidates.get(key); 236 if (candidate == null) { 237 Log.e(TAG, "Tried to set unknown intensity (key=" + key + ")!"); 238 return false; 239 } 240 updateSettings(candidate); 241 onVibrationIntensitySelected(candidate.getIntensity()); 242 return true; 243 } 244 245 @VisibleForTesting 246 class VibrationIntensityCandidateInfo extends CandidateInfo { 247 private String mKey; 248 private int mLabelId; 249 @VibrationIntensity 250 private int mIntensity; 251 VibrationIntensityCandidateInfo(String key, int labelId, int intensity)252 public VibrationIntensityCandidateInfo(String key, int labelId, int intensity) { 253 super(true /* enabled */); 254 mKey = key; 255 mLabelId = labelId; 256 mIntensity = intensity; 257 } 258 259 @Override loadLabel()260 public CharSequence loadLabel() { 261 return getContext().getString(mLabelId); 262 } 263 264 @Override loadIcon()265 public Drawable loadIcon() { 266 return null; 267 } 268 269 @Override getKey()270 public String getKey() { 271 return mKey; 272 } 273 getIntensity()274 public int getIntensity() { 275 return mIntensity; 276 } 277 } 278 279 private class SettingsObserver extends ContentObserver { SettingsObserver()280 public SettingsObserver() { 281 super(new Handler()); 282 } 283 register()284 public void register() { 285 getContext().getContentResolver().registerContentObserver( 286 Settings.System.getUriFor(getVibrationIntensitySetting()), false, this); 287 } 288 unregister()289 public void unregister() { 290 getContext().getContentResolver().unregisterContentObserver(this); 291 } 292 293 @Override onChange(boolean selfChange, Uri uri)294 public void onChange(boolean selfChange, Uri uri) { 295 updateCandidates(); 296 playVibrationPreview(); 297 } 298 } 299 } 300