• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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