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.haptics.signal
18 
19 import android.os.Build
20 import androidx.annotation.IntDef
21 import androidx.annotation.RestrictTo
22 import androidx.core.haptics.VibrationWrapper
23 import androidx.core.haptics.device.HapticDeviceProfile
24 import androidx.core.haptics.impl.HapticSignalConverter
25 
26 /**
27  * A predefined haptic effect that represents common vibration effects, like clicks and ticks.
28  *
29  * Predefined haptic effects should be identical, regardless of the app they come from, in order to
30  * provide a cohesive experience for users across the entire device. The predefined effects are
31  * based on the [android.os.VibrationEffect.createPredefined] platform API.
32  *
33  * @sample androidx.core.haptics.samples.PlaySystemStandardClick
34  */
35 public class PredefinedEffectSignal
36 private constructor(
37 
38     /** The type of haptic effect to be played. */
39     @Type internal val type: Int,
40 
41     /** The minimum SDK level where this effect type is available in the platform. */
42     private val minSdk: Int,
43 ) : FiniteSignal() {
44 
45     /** Typedef for the [type] attribute. */
46     @RestrictTo(RestrictTo.Scope.LIBRARY)
47     @Retention(AnnotationRetention.SOURCE)
48     @IntDef(
49         TICK,
50         CLICK,
51         HEAVY_CLICK,
52         DOUBLE_CLICK,
53     )
54     public annotation class Type
55 
56     public companion object {
57         internal const val TICK = 2 // VibrationEffect.EFFECT_TICK
58         internal const val CLICK = 0 // VibrationEffect.EFFECT_CLICK
59         internal const val HEAVY_CLICK = 5 // VibrationEffect.EFFECT_HEAVY_CLICK
60         internal const val DOUBLE_CLICK = 1 // VibrationEffect.EFFECT_DOUBLE_CLICK
61 
62         private val Tick = PredefinedEffectSignal(TICK, Build.VERSION_CODES.Q)
63         private val Click = PredefinedEffectSignal(CLICK, Build.VERSION_CODES.Q)
64         private val HeavyClick = PredefinedEffectSignal(HEAVY_CLICK, Build.VERSION_CODES.Q)
65         private val DoubleClick = PredefinedEffectSignal(DOUBLE_CLICK, Build.VERSION_CODES.Q)
66 
67         internal val ALL_EFFECTS = listOf(Tick, Click, HeavyClick, DoubleClick)
68 
69         /** Returns all [PredefinedEffectSignal] types available at the current SDK level. */
70         @JvmStatic
getSdkAvailableEffectsnull71         internal fun getSdkAvailableEffects(): List<PredefinedEffectSignal> =
72             ALL_EFFECTS.filter { it.minSdk <= Build.VERSION.SDK_INT }.toList()
73 
74         @JvmStatic
typeToStringnull75         internal fun typeToString(@Type type: Int): String {
76             return when (type) {
77                 TICK -> "Tick"
78                 CLICK -> "Click"
79                 HEAVY_CLICK -> "HeavyClick"
80                 DOUBLE_CLICK -> "DoubleClick"
81                 else -> type.toString()
82             }
83         }
84 
85         /**
86          * A standard tick effect.
87          *
88          * This effect is less strong than the [predefinedClick].
89          */
predefinedTicknull90         @JvmStatic public fun predefinedTick(): PredefinedEffectSignal = Tick
91 
92         /**
93          * A standard click effect.
94          *
95          * Use this effect as a baseline, as it's the most common type of click effect.
96          */
97         @JvmStatic public fun predefinedClick(): PredefinedEffectSignal = Click
98 
99         /**
100          * A heavy click effect.
101          *
102          * This effect is stronger than the [predefinedClick].
103          */
104         @JvmStatic public fun predefinedHeavyClick(): PredefinedEffectSignal = HeavyClick
105 
106         /** A double-click effect. */
107         @JvmStatic public fun predefinedDoubleClick(): PredefinedEffectSignal = DoubleClick
108     }
109 
110     override fun equals(other: Any?): Boolean {
111         if (this === other) return true
112         if (other !is PredefinedEffectSignal) return false
113         if (type != other.type) return false
114         return true
115     }
116 
hashCodenull117     override fun hashCode(): Int {
118         return type.hashCode()
119     }
120 
toStringnull121     override fun toString(): String {
122         return "PredefinedEffectSignal(type=${typeToString(type)})"
123     }
124 
125     /** Returns the minimum SDK level required by the effect type. */
minSdknull126     internal fun minSdk(): Int = minSdk
127 
128     override fun toVibration(): VibrationWrapper? = HapticSignalConverter.toVibration(this)
129 
130     override fun isSupportedBy(deviceProfile: HapticDeviceProfile): Boolean = true
131 }
132