1 /*
2  * Copyright 2023 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 androidx.core.view;
18 
19 import android.os.Build;
20 
21 import androidx.annotation.IntDef;
22 import androidx.annotation.RestrictTo;
23 import androidx.annotation.VisibleForTesting;
24 
25 import java.lang.annotation.Retention;
26 import java.lang.annotation.RetentionPolicy;
27 
28 /**
29  * Helper class for accessing values in {@link android.view.HapticFeedbackConstants}.
30  */
31 public final class HapticFeedbackConstantsCompat {
32 
33     /**
34      * No haptic feedback should be performed. Applications may use this value to indicate skipping
35      * a call to {@link android.view.View#performHapticFeedback} entirely, or else rely that it
36      * will immediately return {@code false}.
37      *
38      * <p>Compatibility:
39      * <ul>
40      *     <li>API &lt; 34: Same behavior, immediately returns false</li>
41      * </ul>
42      */
43     public static final int NO_HAPTICS = -1;
44 
45     /**
46      * The user has performed a long press on an object that is resulting in an action being
47      * performed.
48      */
49     public static final int LONG_PRESS = 0;
50 
51     /**
52      * The user has pressed on a virtual on-screen key.
53      */
54     public static final int VIRTUAL_KEY = 1;
55 
56     /**
57      * The user has pressed a soft keyboard key.
58      */
59     public static final int KEYBOARD_TAP = 3;
60 
61     /**
62      * The user has pressed either an hour or minute tick of a Clock.
63      *
64      * <p>Compatibility:
65      * <ul>
66      *     <li>API &lt; 21: No-op</li>
67      * </ul>
68      */
69     public static final int CLOCK_TICK = 4;
70 
71     /**
72      * The user has performed a context click on an object.
73      *
74      * <p>Compatibility:
75      * <ul>
76      *     <li>API &lt; 23: Same feedback as CLOCK_TICK</li>
77      *     <li>API &lt; 21: No-op</li>
78      * </ul>
79      */
80     public static final int CONTEXT_CLICK = 6;
81 
82     /**
83      * The user has pressed a virtual or software keyboard key.
84      *
85      * <p>Compatibility:
86      * <ul>
87      *     <li>API &lt; 27: Same feedback as KEYBOARD_TAP</li>
88      * </ul>
89      */
90     public static final int KEYBOARD_PRESS = KEYBOARD_TAP; // Platform constant is also the same.
91 
92     /**
93      * The user has released a virtual keyboard key.
94      *
95      * <p>Compatibility:
96      * <ul>
97      *     <li>API &lt; 27: No-op</li>
98      * </ul>
99      */
100     public static final int KEYBOARD_RELEASE = 7;
101 
102     /**
103      * The user has released a virtual key.
104      *
105      * <p>Compatibility:
106      * <ul>
107      *     <li>API &lt; 27: No-op</li>
108      * </ul>
109      */
110     public static final int VIRTUAL_KEY_RELEASE = 8;
111 
112     /**
113      * The user has performed a selection/insertion handle move on text field.
114      *
115      * <p>Compatibility:
116      * <ul>
117      *     <li>API &lt; 27: No-op</li>
118      * </ul>
119      */
120     public static final int TEXT_HANDLE_MOVE = 9;
121 
122     /**
123      * The user has started a gesture (e.g. on the soft keyboard).
124      *
125      * <p>Compatibility:
126      * <ul>
127      *     <li>API &lt; 30: Same feedback as VIRTUAL_KEY</li>
128      * </ul>
129      */
130     public static final int GESTURE_START = 12;
131 
132     /**
133      * The user has finished a gesture (e.g. on the soft keyboard).
134      *
135      * <p>Compatibility:
136      * <ul>
137      *     <li>API &lt; 30: Same feedback as CONTEXT_CLICK</li>
138      *     <li>API &lt; 21: No-op</li>
139      * </ul>
140      */
141     public static final int GESTURE_END = 13;
142 
143     /**
144      * A haptic effect to signal the confirmation or successful completion of a user interaction.
145      *
146      * <p>Compatibility:
147      * <ul>
148      *     <li>API &lt; 30: Same feedback as VIRTUAL_KEY</li>
149      * </ul>
150      */
151     public static final int CONFIRM = 16;
152 
153     /**
154      * A haptic effect to signal the rejection or failure of a user interaction.
155      *
156      * <p>Compatibility:
157      * <ul>
158      *     <li>API &lt; 30: Same feedback as LONG_PRESS</li>
159      * </ul>
160      */
161     public static final int REJECT = 17;
162 
163     /**
164      * The user has toggled a switch or button into the on position.
165      *
166      * <p>Compatibility:
167      * <ul>
168      *     <li>API &lt; 34: Same feedback as CONTEXT_CLICK</li>
169      *     <li>API &lt; 21: No-op</li>
170      * </ul>
171      */
172     public static final int TOGGLE_ON = 21;
173 
174     /**
175      * The user has toggled a switch or button into the off position.
176      *
177      * <p>Compatibility:
178      * <ul>
179      *     <li>API &lt; 34: Same feedback as CLOCK_TICK</li>
180      *     <li>API &lt; 21: No-op</li>
181      * </ul>
182      */
183     public static final int TOGGLE_OFF = 22;
184 
185     /**
186      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
187      * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
188      * moving back past the threshold. This constant indicates that the user's motion has just
189      * passed the threshold for the action to be activated on release.
190      *
191      * @see #GESTURE_THRESHOLD_DEACTIVATE
192      *
193      * <p>Compatibility:
194      * <ul>
195      *     <li>API &lt; 34: Same feedback as CONTEXT_CLICK</li>
196      *     <li>API &lt; 21: No-op</li>
197      * </ul>
198      */
199     public static final int GESTURE_THRESHOLD_ACTIVATE = 23;
200 
201     /**
202      * The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
203      * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
204      * moving back past the threshold. This constant indicates that the user's motion has just
205      * re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
206      * currently in a cancelled state.
207      *
208      * @see #GESTURE_THRESHOLD_ACTIVATE
209      *
210      * <p>Compatibility:
211      * <ul>
212      *     <li>API &lt; 34: Same feedback as CLOCK_TICK</li>
213      *     <li>API &lt; 21: No-op</li>
214      * </ul>
215      */
216     public static final int GESTURE_THRESHOLD_DEACTIVATE = 24;
217 
218     /**
219      * The user has started a drag-and-drop gesture. The drag target has just been "picked up".
220      *
221      * <p>Compatibility:
222      * <ul>
223      *     <li>API &lt; 34: Same feedback as LONG_PRESS</li>
224      * </ul>
225      */
226     public static final int DRAG_START = 25;
227 
228     /**
229      * The user is switching between a series of potential choices, for example items in a list
230      * or discrete points on a slider.
231      *
232      * <p>See also {@link #SEGMENT_FREQUENT_TICK} for cases where density of choices is high, and
233      * the haptics should be lighter or suppressed for a better user experience.
234      *
235      * <p>Compatibility:
236      * <ul>
237      *     <li>API &lt; 34: Same feedback as CONTEXT_CLICK</li>
238      *     <li>API &lt; 21: No-op</li>
239      * </ul>
240      */
241     public static final int SEGMENT_TICK = 26;
242 
243     /**
244      * The user is switching between a series of many potential choices, for example minutes on a
245      * clock face, or individual percentages. This constant is expected to be very soft, so as
246      * not to be uncomfortable when performed a lot in quick succession. If the device can’t make
247      * a suitably soft vibration, then it may not make any vibration.
248      *
249      * <p>Some specializations of this constant exist for specific actions, notably
250      * {@link #CLOCK_TICK} and {@link #TEXT_HANDLE_MOVE}.
251      *
252      * <p>See also {@link #SEGMENT_TICK}.
253      *
254      * <p>Compatibility:
255      * <ul>
256      *     <li>API &lt; 34: Same feedback as CLOCK_TICK</li>
257      *     <li>API &lt; 21: No-op</li>
258      * </ul>
259      */
260     public static final int SEGMENT_FREQUENT_TICK = 27;
261 
262     /** First constant value, excluding {@link #NO_HAPTICS} constant. */
263     @VisibleForTesting
264     static final int FIRST_CONSTANT_INT = LONG_PRESS;
265 
266     /** Last constant value used. */
267     @VisibleForTesting
268     static final int LAST_CONSTANT_INT = SEGMENT_FREQUENT_TICK;
269 
270     /**
271      * Flag for {@link ViewCompat#performHapticFeedback(android.view.View, int, int)}: Ignore the
272      * setting in the view for whether to perform haptic feedback, do it always.
273      */
274     public static final int FLAG_IGNORE_VIEW_SETTING = 0x0001;
275 
276     /** Haptic feedback types. */
277     @IntDef(value = {
278             NO_HAPTICS,
279             LONG_PRESS,
280             VIRTUAL_KEY,
281             KEYBOARD_TAP,
282             CLOCK_TICK,
283             CONTEXT_CLICK,
284             KEYBOARD_PRESS,
285             KEYBOARD_RELEASE,
286             VIRTUAL_KEY_RELEASE,
287             TEXT_HANDLE_MOVE,
288             GESTURE_START,
289             GESTURE_END,
290             CONFIRM,
291             REJECT,
292             TOGGLE_ON,
293             TOGGLE_OFF,
294             GESTURE_THRESHOLD_ACTIVATE,
295             GESTURE_THRESHOLD_DEACTIVATE,
296             DRAG_START,
297             SEGMENT_TICK,
298             SEGMENT_FREQUENT_TICK
299     })
300     @Retention(RetentionPolicy.SOURCE)
301     @RestrictTo(RestrictTo.Scope.LIBRARY)
302     public @interface HapticFeedbackType {
303     }
304 
305     /** Flags for performing haptic feedback. */
306     @IntDef(flag = true, value = {
307             FLAG_IGNORE_VIEW_SETTING
308     })
309     @Retention(RetentionPolicy.SOURCE)
310     @RestrictTo(RestrictTo.Scope.LIBRARY)
311     public @interface HapticFeedbackFlags {
312     }
313 
314     /**
315      * Returns a haptic feedback constant that is available for this platform build.
316      *
317      * @param feedbackConstant The feedback constant requested
318      * @return The same constant, if supported by this platform build, or a supported fallback.
319      */
320     @HapticFeedbackType
getFeedbackConstantOrFallback(@apticFeedbackType int feedbackConstant)321     static int getFeedbackConstantOrFallback(@HapticFeedbackType int feedbackConstant) {
322         if (feedbackConstant == NO_HAPTICS) {
323             // Skip fallback logic if constant is no-op.
324             return NO_HAPTICS;
325         }
326         if (Build.VERSION.SDK_INT < 34) {
327             switch (feedbackConstant) {
328                 case DRAG_START:
329                     feedbackConstant = LONG_PRESS;
330                     break;
331                 case TOGGLE_ON:
332                 case SEGMENT_TICK:
333                 case GESTURE_THRESHOLD_ACTIVATE:
334                     feedbackConstant = CONTEXT_CLICK;
335                     break;
336                 case TOGGLE_OFF:
337                 case SEGMENT_FREQUENT_TICK:
338                 case GESTURE_THRESHOLD_DEACTIVATE:
339                     feedbackConstant = CLOCK_TICK;
340                     break;
341             }
342         }
343         if (Build.VERSION.SDK_INT < 30) {
344             switch (feedbackConstant) {
345                 case REJECT:
346                     feedbackConstant = LONG_PRESS;
347                     break;
348                 case CONFIRM:
349                 case GESTURE_START:
350                     feedbackConstant = VIRTUAL_KEY;
351                     break;
352                 case GESTURE_END:
353                     feedbackConstant = CONTEXT_CLICK;
354                     break;
355             }
356         }
357         if (Build.VERSION.SDK_INT < 27) {
358             switch (feedbackConstant) {
359                 case KEYBOARD_RELEASE:
360                 case VIRTUAL_KEY_RELEASE:
361                 case TEXT_HANDLE_MOVE:
362                     feedbackConstant = NO_HAPTICS;
363                     break;
364             }
365         }
366         if (Build.VERSION.SDK_INT < 23) {
367             switch (feedbackConstant) {
368                 case CONTEXT_CLICK:
369                     feedbackConstant = CLOCK_TICK;
370                     break;
371             }
372         }
373         if (Build.VERSION.SDK_INT < 21) {
374             switch (feedbackConstant) {
375                 case CLOCK_TICK:
376                     feedbackConstant = NO_HAPTICS;
377                     break;
378             }
379         }
380         return feedbackConstant;
381     }
382 
HapticFeedbackConstantsCompat()383     private HapticFeedbackConstantsCompat() {}
384 }
385