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