1 /*
<lambda>null2 * Copyright (C) 2024 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 com.google.android.msdl.domain
18
19 import android.os.Vibrator
20 import android.util.Log
21 import com.google.android.msdl.data.model.FeedbackLevel
22 import com.google.android.msdl.data.model.HapticComposition
23 import com.google.android.msdl.data.model.MSDLToken
24 import com.google.android.msdl.data.repository.MSDLRepository
25 import com.google.android.msdl.data.repository.MSDLRepositoryImpl
26 import com.google.android.msdl.domain.MSDLPlayerImpl.Companion.REQUIRED_PRIMITIVES
27 import com.google.android.msdl.logging.MSDLEvent
28 import java.util.concurrent.Executor
29 import java.util.concurrent.Executors
30
31 /**
32 * Player of MSDL feedback.
33 *
34 * This player is central API to deliver audio and haptic feedback bundled and referenced by
35 * instances of [MSDLToken].
36 */
37 interface MSDLPlayer {
38
39 // Current feedback level set in the system
40 fun getSystemFeedbackLevel(): FeedbackLevel
41
42 /**
43 * Play a [MSDLToken].
44 *
45 * @param[token] The [MSDLToken] to play. This will be used to fetch its corresponding haptic
46 * and sound data.
47 * @param[properties] [InteractionProperties] associated with the token requested to play. These
48 * properties can modify how a token plays (e.g.,
49 * [InteractionProperties.DynamicVibrationScale] for slider haptics in the
50 * [MSDLToken.DRAG_INDICATOR] token) and can be supplied if custom
51 * [android.os.VibrationAttributes] are required for haptic playback. If no properties are
52 * supplied, haptic feedback will play using USAGE_TOUCH [android.os.VibrationAttributes].
53 */
54 fun playToken(token: MSDLToken, properties: InteractionProperties? = null)
55
56 /**
57 * Get the history of recent [MSDLEvent]s. The list can be useful to include in loggers and
58 * system dumps for debugging purposes.
59 */
60 fun getHistory(): List<MSDLEvent>
61
62 companion object {
63
64 // TODO(b/355230334): remove once we have a system setting for the level
65 var SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.DEFAULT
66
67 /**
68 * Create a new [MSDLPlayer].
69 *
70 * @param[vibrator] The [Vibrator] this player will use for haptic playback.
71 * @param[executor] An [Executor] to schedule haptic playback.
72 * @param[useHapticFeedbackForToken] A map that determines if a haptic fallback effect
73 * should be used to play haptics for a given [MSDLToken]. If null, the map will be
74 * created using the support information from the given vibrator.
75 */
76 fun createPlayer(
77 vibrator: Vibrator?,
78 executor: Executor = Executors.newSingleThreadExecutor(),
79 useHapticFeedbackForToken: Map<MSDLToken, Boolean>? = null,
80 ): MSDLPlayer {
81 // Return an empty player if no vibrator is available
82 if (vibrator == null) {
83 Log.w(
84 "MSDLPlayer",
85 "A null vibrator was used to create a MSDLPlayer. An empty player was created",
86 )
87 return EmptyMSDLPlayer()
88 }
89
90 // Create repository
91 val repository = MSDLRepositoryImpl()
92
93 // Determine the support for haptic primitives to know if fallbacks will be used.
94 // This can be provided by the client. If omitted, it will be determined from the
95 // supported primitives of the given vibrator.
96 val shouldUseFallbackForToken =
97 useHapticFeedbackForToken ?: createHapticFallbackDecisionMap(vibrator, repository)
98
99 return MSDLPlayerImpl(repository, vibrator, executor, shouldUseFallbackForToken)
100 }
101
102 private fun createHapticFallbackDecisionMap(
103 vibrator: Vibrator,
104 repository: MSDLRepository,
105 ): Map<MSDLToken, Boolean> {
106 val supportedPrimitives =
107 REQUIRED_PRIMITIVES.associateWith { vibrator.arePrimitivesSupported(it).first() }
108 return MSDLToken.entries.associateWith { token ->
109 // For each token, determine if the haptic data from the repository
110 // should use the fallback effect.
111 val hapticComposition =
112 repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
113 hapticComposition?.shouldPlayFallback(supportedPrimitives) ?: false
114 }
115 }
116 }
117 }
118
HapticCompositionnull119 fun HapticComposition.shouldPlayFallback(supportedPrimitives: Map<Int, Boolean>): Boolean {
120 primitives.forEach { primitive ->
121 val isSupported = supportedPrimitives[primitive.primitiveId]
122 if (isSupported == null || isSupported == false) {
123 return true
124 }
125 }
126 return false
127 }
128