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.device
18 
19 import androidx.core.haptics.signal.CompositionSignal
20 import androidx.core.haptics.signal.CompositionSignal.PrimitiveAtom
21 import java.util.Objects
22 
23 /**
24  * A [HapticCompositionProfile] describes the vibrator capabilities related to [CompositionSignal].
25  *
26  * Composition signals are defined by one or more haptic effect primitives that are custom tailored
27  * to the device hardware. This profile provides hardware capabilities related to these primitives,
28  * like support checks and estimated duration within a composition signal.
29  *
30  * @property supportedPrimitiveTypes A set of [Int] values defined by
31  *   [CompositionSignal.PrimitiveAtom.Type] representing composition primitive types supported.
32  */
33 public class HapticCompositionProfile
34 @JvmOverloads
35 constructor(
36 
37     /**
38      * Hint for the composition primitives that are supported by the vibrator hardware.
39      *
40      * The values must be one of [CompositionSignal.PrimitiveAtom.Type]. The actual support set also
41      * depends on the minimum SDK level required for each primitive.
42      */
43     supportedPrimitiveTypesHint: Set<Int> = emptySet(),
44 
45     /**
46      * Hint for the estimated duration of each type of composition primitive, if available.
47      *
48      * The keys must be one of [CompositionSignal.PrimitiveAtom.Type], and the values are durations
49      * in milliseconds. This can be null if the device does not report the estimated duration of
50      * primitives.
51      *
52      * The actual entries also depend on the minimum SDK level required for each primitive key.
53      */
54     primitiveDurationMillisMapHint: Map<Int, Long>? = null,
55 ) {
56     /**
57      * Whether the device reports the estimated duration for supported composition primitives.
58      *
59      * If primitive durations are reported then [getPrimitiveDurationMillis] can be used for
60      * supported primitive types to check individual [PrimitiveAtom] durations within a
61      * [CompositionSignal].
62      */
63     public val isPrimitiveDurationReported: Boolean
64         get() = _primitiveDurationMillisMap != null
65 
66     /**
67      * The composition primitive types that are supported by the vibrator hardware.
68      *
69      * Composition signals are only supported by the Android platform if the device hardware
70      * supports all primitive atom types used in the composition.
71      *
72      * The values will be one of [CompositionSignal.PrimitiveAtom.Type]. The set will be empty if
73      * the device reports no supported primitive, or if the required APIs are not available in this
74      * SDK level.
75      */
76     public val supportedPrimitiveTypes: Set<Int>
77 
78     private val _primitiveDurationMillisMap: Map<Int, Long>?
79 
80     init {
81         val availablePrimitives = PrimitiveAtom.getSdkAvailablePrimitiveTypes()
82         supportedPrimitiveTypes =
<lambda>null83             supportedPrimitiveTypesHint.filter { availablePrimitives.contains(it) }.toSet()
84 
85         _primitiveDurationMillisMap =
86             primitiveDurationMillisMapHint
<lambda>null87                 ?.filter { supportedPrimitiveTypes.contains(it.key) }
88                 ?.toMap()
89 
<lambda>null90         require(_primitiveDurationMillisMap?.let { supportedPrimitiveTypes == it.keys } ?: true) {
91             "Composition primitive durations should be reported for all supported primitives." +
92                 " Device supports primitive types $supportedPrimitiveTypes but got" +
93                 " estimated durations for ${_primitiveDurationMillisMap?.keys}."
94         }
95     }
96 
97     /**
98      * The estimated duration a [PrimitiveAtom] of give type will play in a [CompositionSignal].
99      *
100      * This will be zero if the primitive type is not supported or if the devices does not report
101      * primitive durations, which can be checked via [isPrimitiveDurationReported].
102      *
103      * @param primitiveType The type of primitive queried.
104      * @return The estimated duration the device will take to play a [PrimitiveAtom] of given type
105      *   in a [CompositionSignal], or null if the primitive type is not supported or the hardware
106      *   does not report primitive durations.
107      */
getPrimitiveDurationMillisnull108     public fun getPrimitiveDurationMillis(@PrimitiveAtom.Type primitiveType: Int): Long =
109         _primitiveDurationMillisMap?.get(primitiveType) ?: 0L
110 
111     override fun equals(other: Any?): Boolean {
112         if (this === other) return true
113         if (other !is HapticCompositionProfile) return false
114         if (supportedPrimitiveTypes != other.supportedPrimitiveTypes) return false
115         if (_primitiveDurationMillisMap != other._primitiveDurationMillisMap) return false
116         return true
117     }
118 
hashCodenull119     override fun hashCode(): Int {
120         return Objects.hash(
121             supportedPrimitiveTypes,
122             _primitiveDurationMillisMap,
123         )
124     }
125 
toStringnull126     override fun toString(): String {
127         return "HapticCompositionProfile(" +
128             ", isPrimitiveDurationReported=$isPrimitiveDurationReported" +
129             ", supportedPrimitiveTypes=" +
130             "${
131                 supportedPrimitiveTypes.joinToString(
132                     prefix = "[",
133                     postfix = "]",
134                     transform = { PrimitiveAtom.typeToString(it) },
135                 )
136             }" +
137             ", primitiveDurations=" +
138             "${
139                 _primitiveDurationMillisMap?.entries?.joinToString(
140                     prefix = "{",
141                     postfix = "}",
142                     transform = { "${PrimitiveAtom.typeToString(it.key)} to ${it.value}ms" },
143                 )
144             })"
145     }
146 }
147