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