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