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
18 
19 import android.media.AudioAttributes
20 import android.os.Build
21 import android.os.VibrationAttributes
22 import androidx.annotation.IntDef
23 import androidx.annotation.RequiresApi
24 import androidx.annotation.RestrictTo
25 import androidx.core.haptics.impl.HapticAttributesConverter
26 import java.util.Objects
27 
28 /**
29  * Collection of attributes describing information about a haptic effect or signal.
30  *
31  * The attributes described here will be mapped to the corresponding value from either
32  * [android.os.VibrationAttributes] or [android.media.AudioAttributes], depending on the SDK level
33  * available.
34  *
35  * @sample androidx.core.haptics.samples.PlaySystemStandardClick
36  */
37 public class HapticAttributes
38 @JvmOverloads
39 constructor(
40 
41     /** The usage to apply the correct system policies and user settings to the vibration. */
42     @Usage public val usage: Int,
43 
44     /** The flags to control the vibration behavior. */
45     @Flag public val flags: Int = 0,
46 ) {
47 
48     /** Creates a [HapticAttributes] mapping fields from given [VibrationAttributes]. */
49     @RequiresApi(Build.VERSION_CODES.R)
50     public constructor(
51         attrs: VibrationAttributes
52     ) : this(
53         HapticAttributesConverter.usageFromVibrationAttributes(attrs),
54         HapticAttributesConverter.flagsFromVibrationAttributes(attrs),
55     )
56 
57     /** Creates a [HapticAttributes] mapping fields from given [AudioAttributes]. */
58     @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
59     public constructor(
60         attrs: AudioAttributes
61     ) : this(
62         HapticAttributesConverter.usageFromAudioAttributes(attrs),
63         flags = 0,
64     )
65 
66     /** Builder class for [HapticAttributes]. */
67     public class Builder
68     private constructor(
69         @Usage private var usage: Int,
70         @Flag private var flags: Int,
71     ) {
72 
73         /** Creates a builder for [HapticAttributes] with given usage. */
74         public constructor(@Usage usage: Int) : this(usage, flags = 0)
75 
76         /** Creates a builder for [HapticAttributes] copying all fields from given attributes. */
77         public constructor(attrs: HapticAttributes) : this(attrs.usage, attrs.flags)
78 
79         /** Creates a builder for [HapticAttributes] copying mapped fields from given attributes. */
80         @RequiresApi(Build.VERSION_CODES.R)
81         public constructor(
82             attrs: VibrationAttributes
83         ) : this(
84             HapticAttributesConverter.usageFromVibrationAttributes(attrs),
85             HapticAttributesConverter.flagsFromVibrationAttributes(attrs),
86         )
87 
88         /** Creates a builder for [HapticAttributes] copying mapped fields from given attributes. */
89         @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
90         public constructor(
91             attrs: AudioAttributes
92         ) : this(
93             HapticAttributesConverter.usageFromAudioAttributes(attrs),
94             flags = 0,
95         )
96 
97         /** Sets the usage that maps correct system policies and user settings to the vibration. */
<lambda>null98         public fun setUsage(@Usage usage: Int): Builder = apply { this.usage = usage }
99 
100         /** Sets the flags to control the vibration behavior. */
<lambda>null101         public fun setFlags(@Flag flags: Int): Builder = apply { this.flags = flags }
102 
103         /** Returns a built [HapticAttributes]. */
buildnull104         public fun build(): HapticAttributes = HapticAttributes(usage, flags)
105     }
106 
107     override fun equals(other: Any?): Boolean {
108         if (this === other) return true
109         if (other !is HapticAttributes) return false
110         if (usage != other.usage) return false
111         if (flags != other.flags) return false
112         return true
113     }
114 
hashCodenull115     override fun hashCode(): Int = Objects.hash(usage, flags)
116 
117     override fun toString(): String = "HapticAttributes(usage=$usage, flags=$flags)"
118 
119     internal fun toAttributes(): AttributesWrapper? = HapticAttributesConverter.toAttributes(this)
120 
121     public companion object {
122         // Values from VibrationAttributes.USAGE_* and VibrationAttributes.FLAG_*
123 
124         /** Usage value to use for accessibility vibrations, such as with a screen reader. */
125         public const val USAGE_ACCESSIBILITY: Int = 66
126 
127         /** Usage value to use for alarm vibrations. */
128         public const val USAGE_ALARM: Int = 1
129 
130         /**
131          * Usage value to use for vibrations which mean a request to enter/end a communication with
132          * the user, such as a voice prompt.
133          */
134         public const val USAGE_COMMUNICATION_REQUEST: Int = 65
135 
136         /**
137          * Usage value to use for vibrations which provide a feedback for hardware component
138          * interaction, such as a fingerprint sensor.
139          */
140         public const val USAGE_HARDWARE_FEEDBACK: Int = 50
141 
142         /**
143          * Usage value to use for media vibrations, such as music, movie, soundtrack, animations,
144          * games, or any interactive media that isn't for touch feedback specifically.
145          */
146         public const val USAGE_MEDIA: Int = 19
147 
148         /** Usage value to use for notification vibrations. */
149         public const val USAGE_NOTIFICATION: Int = 49
150 
151         /**
152          * Usage value to use for vibrations which emulate physical hardware reactions, such as edge
153          * squeeze.
154          *
155          * Note that normal screen-touch feedback "click" effects would typically be classed as
156          * [USAGE_TOUCH], and that on-screen "physical" animations like bouncing would be
157          * [USAGE_MEDIA].
158          */
159         public const val USAGE_PHYSICAL_EMULATION: Int = 34
160 
161         /** Usage value to use for ringtone vibrations. */
162         public const val USAGE_RINGTONE: Int = 33
163 
164         /**
165          * Usage value to use for touch vibrations.
166          *
167          * Most typical haptic feedback should be classed as touch feedback. Examples include
168          * vibrations for tap, long press, drag and scroll.
169          */
170         public const val USAGE_TOUCH: Int = 18
171 
172         /** Usage value to use when usage is unknown. */
173         public const val USAGE_UNKNOWN: Int = 0
174 
175         /**
176          * Flag requesting vibration effect to be played even under limited interruptions.
177          *
178          * Only privileged apps can ignore user settings that limit interruptions, and this flag
179          * will be ignored otherwise.
180          */
181         public const val FLAG_BYPASS_INTERRUPTION_POLICY: Int = 1
182     }
183 
184     /** Typedef for the usage attribute. */
185     @IntDef(
186         USAGE_ACCESSIBILITY,
187         USAGE_ALARM,
188         USAGE_COMMUNICATION_REQUEST,
189         USAGE_HARDWARE_FEEDBACK,
190         USAGE_MEDIA,
191         USAGE_NOTIFICATION,
192         USAGE_PHYSICAL_EMULATION,
193         USAGE_RINGTONE,
194         USAGE_TOUCH,
195         USAGE_UNKNOWN,
196     )
197     @RestrictTo(RestrictTo.Scope.LIBRARY)
198     @Retention(AnnotationRetention.SOURCE)
199     internal annotation class Usage
200 
201     /** Typedef for the flag attribute. */
202     @IntDef(
203         flag = true,
204         value =
205             [
206                 FLAG_BYPASS_INTERRUPTION_POLICY,
207             ],
208     )
209     @RestrictTo(RestrictTo.Scope.LIBRARY)
210     @Retention(AnnotationRetention.SOURCE)
211     internal annotation class Flag
212 }
213