• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.launcher3.util;
17 
18 import static android.os.VibrationEffect.Composition.PRIMITIVE_LOW_TICK;
19 import static android.os.VibrationEffect.createPredefined;
20 import static android.provider.Settings.System.HAPTIC_FEEDBACK_ENABLED;
21 
22 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
23 import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
24 
25 import android.annotation.SuppressLint;
26 import android.content.Context;
27 import android.media.AudioAttributes;
28 import android.net.Uri;
29 import android.os.VibrationEffect;
30 import android.os.Vibrator;
31 import android.provider.Settings;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.launcher3.dagger.ApplicationContext;
36 import com.android.launcher3.dagger.LauncherAppSingleton;
37 import com.android.launcher3.dagger.LauncherBaseAppComponent;
38 
39 import javax.inject.Inject;
40 
41 /**
42  * Wrapper around {@link Vibrator} to easily perform haptic feedback where necessary.
43  */
44 @LauncherAppSingleton
45 public class VibratorWrapper {
46 
47     public static final DaggerSingletonObject<VibratorWrapper> INSTANCE =
48             new DaggerSingletonObject<>(LauncherBaseAppComponent::getVibratorWrapper);
49 
50     public static final AudioAttributes VIBRATION_ATTRS = new AudioAttributes.Builder()
51             .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
52             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
53             .build();
54 
55     public static final VibrationEffect EFFECT_CLICK =
56             createPredefined(VibrationEffect.EFFECT_CLICK);
57     @VisibleForTesting
58     static final Uri HAPTIC_FEEDBACK_URI = Settings.System.getUriFor(HAPTIC_FEEDBACK_ENABLED);
59 
60     @VisibleForTesting static final float LOW_TICK_SCALE = 0.9f;
61 
62     /**
63      * Haptic when entering overview.
64      */
65     public static final VibrationEffect OVERVIEW_HAPTIC = EFFECT_CLICK;
66 
67     private final Vibrator mVibrator;
68     private final boolean mHasVibrator;
69 
70     @VisibleForTesting
71     final SettingsCache.OnChangeListener mHapticChangeListener =
72             isEnabled -> mIsHapticFeedbackEnabled = isEnabled;
73 
74     private boolean mIsHapticFeedbackEnabled;
75 
76     @Inject
VibratorWrapper(@pplicationContext Context context, SettingsCache settingsCache, DaggerSingletonTracker tracker)77     public VibratorWrapper(@ApplicationContext Context context, SettingsCache settingsCache,
78             DaggerSingletonTracker tracker) {
79 
80         mVibrator = context.getSystemService(Vibrator.class);
81         mHasVibrator = mVibrator.hasVibrator();
82         if (mHasVibrator) {
83             MAIN_EXECUTOR.execute(
84                     () -> settingsCache.register(HAPTIC_FEEDBACK_URI, mHapticChangeListener));
85             mIsHapticFeedbackEnabled = settingsCache.getValue(HAPTIC_FEEDBACK_URI, 0);
86             tracker.addCloseable(
87                     () -> settingsCache.unregister(HAPTIC_FEEDBACK_URI, mHapticChangeListener));
88         } else {
89             mIsHapticFeedbackEnabled = false;
90         }
91     }
92 
93     /**
94      * This should be used to cancel a haptic in case where the haptic shouldn't be vibrating. For
95      * example, when no animation is happening but a vibrator happens to be vibrating still.
96      */
cancelVibrate()97     public void cancelVibrate() {
98         UI_HELPER_EXECUTOR.execute(mVibrator::cancel);
99     }
100 
101     /** Vibrates with the given effect if haptic feedback is available and enabled. */
vibrate(VibrationEffect vibrationEffect)102     public void vibrate(VibrationEffect vibrationEffect) {
103         if (mHasVibrator && mIsHapticFeedbackEnabled) {
104             UI_HELPER_EXECUTOR.execute(() -> mVibrator.vibrate(vibrationEffect, VIBRATION_ATTRS));
105         }
106     }
107 
108     /**
109      * Vibrates with a single primitive, if supported, or use a fallback effect instead. This only
110      * vibrates if haptic feedback is available and enabled.
111      */
112     @SuppressLint("NewApi")
vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect)113     public void vibrate(int primitiveId, float primitiveScale, VibrationEffect fallbackEffect) {
114         if (mHasVibrator && mIsHapticFeedbackEnabled) {
115             UI_HELPER_EXECUTOR.execute(() -> {
116                 if (primitiveId >= 0 && mVibrator.areAllPrimitivesSupported(primitiveId)) {
117                     mVibrator.vibrate(VibrationEffect.startComposition()
118                             .addPrimitive(primitiveId, primitiveScale)
119                             .compose(), VIBRATION_ATTRS);
120                 } else {
121                     mVibrator.vibrate(fallbackEffect, VIBRATION_ATTRS);
122                 }
123             });
124         }
125     }
126 
127     /** Indicates that Taskbar has been invoked. */
vibrateForTaskbarUnstash()128     public void vibrateForTaskbarUnstash() {
129         if (mVibrator.areAllPrimitivesSupported(PRIMITIVE_LOW_TICK)) {
130             VibrationEffect primitiveLowTickEffect = VibrationEffect
131                     .startComposition()
132                     .addPrimitive(PRIMITIVE_LOW_TICK, LOW_TICK_SCALE)
133                     .compose();
134 
135             vibrate(primitiveLowTickEffect);
136         }
137     }
138 }
139