Merge "tracinglib: fix build warnings" into main
diff --git a/msdllib/src/com/google/android/msdl/data/model/HapticComposition.kt b/msdllib/src/com/google/android/msdl/data/model/HapticComposition.kt
index c0d3731..30f8e13 100644
--- a/msdllib/src/com/google/android/msdl/data/model/HapticComposition.kt
+++ b/msdllib/src/com/google/android/msdl/data/model/HapticComposition.kt
@@ -16,18 +16,27 @@
 
 package com.google.android.msdl.data.model
 
-/** A haptic composition as a list of [HapticCompositionPrimitive] */
-data class HapticComposition(val primitives: List<HapticCompositionPrimitive>? = null)
+import android.os.VibrationEffect
+
+/**
+ * A haptic composition as a list of [HapticCompositionPrimitive] and a [android.os.VibrationEffect]
+ * to use as a fallback.
+ */
+data class HapticComposition(
+    val primitives: List<HapticCompositionPrimitive>,
+    val fallbackEffect: VibrationEffect,
+)
 
 /**
  * An abstraction of a haptic primitive in a composition that includes:
  *
  * @param[primitiveId] The id of the primitive.
  * @param[scale] The scale of the primitive.
- * @param[delay] The delay of the primitive relative to the end of a previous primitive.
+ * @param[delayMillis] The delay of the primitive relative to the end of a previous primitive. Given
+ *   in milliseconds.
  */
 data class HapticCompositionPrimitive(
     val primitiveId: Int,
     var scale: Float = 1f,
-    var delay: Int = 0,
+    var delayMillis: Int = 0,
 )
diff --git a/msdllib/src/com/google/android/msdl/data/model/HapticToken.kt b/msdllib/src/com/google/android/msdl/data/model/HapticToken.kt
index cb6a895..98be02d 100644
--- a/msdllib/src/com/google/android/msdl/data/model/HapticToken.kt
+++ b/msdllib/src/com/google/android/msdl/data/model/HapticToken.kt
@@ -24,6 +24,7 @@
     POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
     POSITIVE_CONFIRMATION_LOW_EMPHASIS,
     NEUTRAL_CONFIRMATION_HIGH_EMPHASIS,
+    NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS,
     LONG_PRESS,
     SWIPE_THRESHOLD_INDICATOR,
     TAP_HIGH_EMPHASIS,
diff --git a/msdllib/src/com/google/android/msdl/data/model/MSDLToken.kt b/msdllib/src/com/google/android/msdl/data/model/MSDLToken.kt
index 5b982ea..99e6d00 100644
--- a/msdllib/src/com/google/android/msdl/data/model/MSDLToken.kt
+++ b/msdllib/src/com/google/android/msdl/data/model/MSDLToken.kt
@@ -46,12 +46,24 @@
         SoundToken.START,
         FeedbackLevel.DEFAULT,
     ),
+    /* Inform the user that an ongoing activity has paused */
+    PAUSE(
+        HapticToken.NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS,
+        SoundToken.PAUSE,
+        FeedbackLevel.DEFAULT,
+    ),
     /* Inform the user that their previously started activity has stopped SUCCESSFULLY */
     STOP(
         HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
         SoundToken.STOP,
         FeedbackLevel.DEFAULT,
     ),
+    /* Inform the user that their previously started activity has cancelled SUCCESSFULLY */
+    CANCEL(
+        HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
+        SoundToken.CANCEL,
+        FeedbackLevel.DEFAULT,
+    ),
     /* Inform the user that the state of an interactive component has been switched to on SUCCESSFULLY */
     SWITCH_ON(
         HapticToken.POSITIVE_CONFIRMATION_MEDIUM_EMPHASIS,
@@ -100,16 +112,10 @@
         SoundToken.TAP_MEDIUM_EMPHASIS,
         FeedbackLevel.DEFAULT,
     ),
-    /* Played when a users drag gesture reaches the maximum value */
-    DRAG_THRESHOLD_INDICATOR_CEILING(
+    /* Played when a users drag gesture reaches the maximum or minimum value */
+    DRAG_THRESHOLD_INDICATOR_LIMIT(
         HapticToken.DRAG_THRESHOLD_INDICATOR,
-        SoundToken.DRAG_THRESHOLD_INDICATOR_CEILING,
-        FeedbackLevel.DEFAULT,
-    ),
-    /* Played when a users drag gesture reaches the minimum value */
-    DRAG_THRESHOLD_INDICATOR_FLOOR(
-        HapticToken.DRAG_THRESHOLD_INDICATOR,
-        SoundToken.DRAG_THRESHOLD_INDICATOR_FLOOR,
+        SoundToken.DRAG_THRESHOLD_INDICATOR_LIMIT,
         FeedbackLevel.DEFAULT,
     ),
     /* Inform the user that their drag gesture has resulted in an incremental value change.
diff --git a/msdllib/src/com/google/android/msdl/data/model/SoundToken.kt b/msdllib/src/com/google/android/msdl/data/model/SoundToken.kt
index d38fce6..bc1ebf0 100644
--- a/msdllib/src/com/google/android/msdl/data/model/SoundToken.kt
+++ b/msdllib/src/com/google/android/msdl/data/model/SoundToken.kt
@@ -22,7 +22,9 @@
     FAILURE,
     SUCCESS,
     START,
+    PAUSE,
     STOP,
+    CANCEL,
     SWITCH_ON,
     SWITCH_OFF,
     UNLOCK,
@@ -31,8 +33,7 @@
     SWIPE_THRESHOLD_INDICATOR,
     TAP_HIGH_EMPHASIS,
     TAP_MEDIUM_EMPHASIS,
-    DRAG_THRESHOLD_INDICATOR_CEILING,
-    DRAG_THRESHOLD_INDICATOR_FLOOR,
+    DRAG_THRESHOLD_INDICATOR_LIMIT,
     DRAG_INDICATOR,
     TAP_LOW_EMPHASIS,
     KEYPRESS_STANDARD,
diff --git a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
index ce10a98..e409e02 100644
--- a/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
+++ b/msdllib/src/com/google/android/msdl/data/repository/MSDLRepositoryImpl.kt
@@ -33,29 +33,69 @@
     override fun getHapticData(hapticToken: HapticToken): MSDLHapticData? = HAPTIC_DATA[hapticToken]
 
     companion object {
+        // Timings and amplitudes that recreate a composition of three SPIN primitives as a waveform
+        private val SPIN_TIMINGS = longArrayOf(20, 20, 3, 43, 20, 20, 3)
+        private val SPIN_AMPLITUDES = intArrayOf(40, 80, 40, 0, 40, 80, 40)
+        private const val SPIN_DELAY = 56L
+        private const val SPIN_BREAK = 10
+        private val SPIN_WAVEFORM_TIMINGS =
+            SPIN_TIMINGS + SPIN_DELAY + SPIN_TIMINGS + SPIN_DELAY + SPIN_TIMINGS
+        private val SPIN_WAVEFORM_AMPLITUDES =
+            SPIN_AMPLITUDES + SPIN_BREAK + SPIN_AMPLITUDES + SPIN_BREAK + SPIN_AMPLITUDES
+
         private val HAPTIC_DATA: Map<HapticToken, MSDLHapticData> =
             mapOf(
                 HapticToken.NEGATIVE_CONFIRMATION_HIGH_EMPHASIS to
-                    MSDLHapticData { HapticComposition(null) },
+                    MSDLHapticData {
+                        HapticComposition(
+                            listOf(
+                                HapticCompositionPrimitive(
+                                    VibrationEffect.Composition.PRIMITIVE_SPIN,
+                                    scale = 1f,
+                                    delayMillis = 0,
+                                ),
+                                HapticCompositionPrimitive(
+                                    VibrationEffect.Composition.PRIMITIVE_SPIN,
+                                    scale = 1f,
+                                    delayMillis = SPIN_DELAY.toInt(),
+                                ),
+                                HapticCompositionPrimitive(
+                                    VibrationEffect.Composition.PRIMITIVE_SPIN,
+                                    scale = 1f,
+                                    delayMillis = SPIN_DELAY.toInt(),
+                                )
+                            ),
+                            VibrationEffect.createWaveform(
+                                SPIN_WAVEFORM_TIMINGS,
+                                SPIN_WAVEFORM_AMPLITUDES,
+                                -1,
+                            )
+                        )
+                    },
                 HapticToken.NEGATIVE_CONFIRMATION_MEDIUM_EMPHASIS to
                     MSDLHapticData {
                         HapticComposition(
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    114
+                                    scale = 1f,
+                                    delayMillis = 114
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    114
+                                    scale = 1f,
+                                    delayMillis = 114
                                 )
+                            ),
+                            VibrationEffect.createWaveform(
+                                longArrayOf(10, 10, 10, 114, 10, 10, 10, 114, 10, 10, 10),
+                                intArrayOf(10, 255, 20, 0, 10, 255, 20, 0, 10, 255, 20),
+                                -1
                             )
                         )
                     },
@@ -65,14 +105,19 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    114
+                                    scale = 1f,
+                                    delayMillis = 114
                                 )
+                            ),
+                            VibrationEffect.createWaveform(
+                                longArrayOf(10, 10, 10, 114, 10, 10, 10),
+                                intArrayOf(10, 255, 20, 0, 10, 255, 20),
+                                -1
                             )
                         )
                     },
@@ -82,14 +127,19 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    52
+                                    scale = 1f,
+                                    delayMillis = 52
                                 )
+                            ),
+                            VibrationEffect.createWaveform(
+                                longArrayOf(10, 10, 10, 52, 10, 10, 10),
+                                intArrayOf(10, 255, 20, 0, 10, 255, 20),
+                                -1
                             )
                         )
                     },
@@ -99,14 +149,19 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 ),
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    52
+                                    scale = 1f,
+                                    delayMillis = 52
                                 )
+                            ),
+                            VibrationEffect.createWaveform(
+                                longArrayOf(5, 52, 10, 10, 10),
+                                intArrayOf(100, 0, 10, 255, 20),
+                                -1
                             )
                         )
                     },
@@ -116,22 +171,41 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_THUD,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 )
+                            ),
+                            VibrationEffect.createWaveform(
+                                longArrayOf(50, 100, 100, 50),
+                                intArrayOf(5, 50, 20, 10),
+                                -1
                             )
                         )
                     },
+                HapticToken.NEUTRAL_CONFIRMATION_MEDIUM_EMPHASIS to
+                    MSDLHapticData {
+                        HapticComposition(
+                            listOf(
+                                HapticCompositionPrimitive(
+                                    VibrationEffect.Composition.PRIMITIVE_CLICK,
+                                    scale = 1f,
+                                    delayMillis = 0,
+                                )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
+                        )
+                    },
                 HapticToken.LONG_PRESS to
                     MSDLHapticData {
                         HapticComposition(
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.SWIPE_THRESHOLD_INDICATOR to
@@ -140,10 +214,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.7f,
-                                    0
+                                    scale = 0.7f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.TAP_HIGH_EMPHASIS to
@@ -152,10 +227,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.7f,
-                                    0
+                                    scale = 0.7f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.TAP_MEDIUM_EMPHASIS to
@@ -164,10 +240,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.5f,
-                                    0
-                                )
-                            )
+                                    scale = 0.5f,
+                                    delayMillis = 0
+                                ),
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.DRAG_THRESHOLD_INDICATOR to
@@ -176,10 +253,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
-                                    1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
                         )
                     },
                 HapticToken.DRAG_INDICATOR to
@@ -188,10 +266,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
-                                    0.5f,
-                                    0
+                                    scale = 0.5f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
                         )
                     },
                 HapticToken.TAP_LOW_EMPHASIS to
@@ -200,10 +279,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.3f,
-                                    0
+                                    scale = 0.3f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.KEYPRESS_STANDARD to
@@ -212,10 +292,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_TICK,
-                                    0.7f,
-                                    0
-                                )
-                            )
+                                    scale = 0.7f,
+                                    delayMillis = 0
+                                ),
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK)
                         )
                     },
                 HapticToken.KEYPRESS_SPACEBAR to
@@ -224,10 +305,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.7f,
-                                    0
+                                    scale = 0.7f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.KEYPRESS_RETURN to
@@ -236,10 +318,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.7f,
-                                    0
+                                    scale = 0.7f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     },
                 HapticToken.KEYPRESS_DELETE to
@@ -248,10 +331,11 @@
                             listOf(
                                 HapticCompositionPrimitive(
                                     VibrationEffect.Composition.PRIMITIVE_CLICK,
-                                    0.1f,
-                                    0
+                                    scale = 1f,
+                                    delayMillis = 0
                                 )
-                            )
+                            ),
+                            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)
                         )
                     }
             )
diff --git a/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt b/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
index 567302e..a0aef76 100644
--- a/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
+++ b/msdllib/src/com/google/android/msdl/domain/MSDLPlayer.kt
@@ -19,8 +19,10 @@
 import android.content.Context
 import android.os.VibratorManager
 import com.google.android.msdl.data.model.FeedbackLevel
+import com.google.android.msdl.data.model.HapticComposition
 import com.google.android.msdl.data.model.MSDLToken
 import com.google.android.msdl.data.repository.MSDLRepositoryImpl
+import com.google.android.msdl.domain.MSDLPlayerImpl.Companion.REQUIRED_PRIMITIVES
 import java.util.concurrent.Executor
 import java.util.concurrent.Executors
 
@@ -66,11 +68,34 @@
             // Gather vibration dependencies
             val vibratorManager =
                 context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
+            val vibrator = vibratorManager.defaultVibrator
 
             // Create repository
             val repository = MSDLRepositoryImpl()
 
-            return MSDLPlayerImpl(repository, vibratorManager.defaultVibrator, executor)
+            // Determine the support for haptic primitives to know if fallbacks will be used
+            val supportedPrimitives =
+                REQUIRED_PRIMITIVES.associateWith { vibrator.arePrimitivesSupported(it).first() }
+            val useHapticFallbackForToken =
+                MSDLToken.entries.associateWith { token ->
+                    // For each token, determine if the haptic data from the repository should use
+                    // the fallback effect
+                    val hapticComposition =
+                        repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
+                    hapticComposition?.shouldPlayFallback(supportedPrimitives)
+                }
+
+            return MSDLPlayerImpl(repository, vibrator, executor, useHapticFallbackForToken)
         }
     }
 }
+
+fun HapticComposition.shouldPlayFallback(supportedPrimitives: Map<Int, Boolean>): Boolean {
+    primitives.forEach { primitive ->
+        val isSupported = supportedPrimitives[primitive.primitiveId]
+        if (isSupported == null || isSupported == false) {
+            return true
+        }
+    }
+    return false
+}
diff --git a/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt b/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
index 0f4ebd7..9e78cea 100644
--- a/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
+++ b/msdllib/src/com/google/android/msdl/domain/MSDLPlayerImpl.kt
@@ -22,9 +22,7 @@
 import com.google.android.msdl.data.model.FeedbackLevel
 import com.google.android.msdl.data.model.HapticComposition
 import com.google.android.msdl.data.model.MSDLToken
-import com.google.android.msdl.data.repository.MSDLHapticData
 import com.google.android.msdl.data.repository.MSDLRepository
-import com.google.android.msdl.data.repository.MSDLSoundData
 import java.util.concurrent.Executor
 
 /**
@@ -35,11 +33,14 @@
  * @param[repository] Repository to retrieve audio and haptic data.
  * @param[executor] An [Executor] used to schedule haptic playback.
  * @param[vibrator] Instance of the default [Vibrator] on the device.
+ * @param[useHapticFallbackForToken] A map that determines if the haptic fallback effect should be
+ *   used for a given token.
  */
 class MSDLPlayerImpl(
     private val repository: MSDLRepository,
     private val vibrator: Vibrator,
     private val executor: Executor,
+    private val useHapticFallbackForToken: Map<MSDLToken, Boolean?>,
 ) : MSDLPlayer {
 
     // TODO(b/355230334): This should be retrieved from the system Settings
@@ -50,19 +51,18 @@
         // level of the token
         if (getSystemFeedbackLevel() < token.minimumFeedbackLevel) return
 
+        // Play the data for the token with the given properties
+        playData(token, properties)
+    }
+
+    private fun playData(
+        token: MSDLToken,
+        properties: InteractionProperties?,
+    ) {
         // Gather the data from the repositories
         val hapticData = repository.getHapticData(token.hapticToken)
         val soundData = repository.getAudioData(token.soundToken)
 
-        // Play the data for the token with the given properties
-        playData(hapticData, soundData, properties)
-    }
-
-    private fun playData(
-        hapticData: MSDLHapticData?,
-        soundData: MSDLSoundData?,
-        properties: InteractionProperties?,
-    ) {
         // Nothing to play
         if (hapticData == null && soundData == null) return
 
@@ -71,9 +71,8 @@
             // 1. Create the effect
             val composition: HapticComposition? = hapticData?.get() as? HapticComposition
             val effect =
-                if (properties == null) {
-                    // Compose as-is
-                    composition?.composeIntoVibrationEffect()
+                if (useHapticFallbackForToken[token] == true) {
+                    composition?.fallbackEffect
                 } else {
                     when (properties) {
                         is InteractionProperties.DynamicVibrationScale -> {
@@ -81,7 +80,7 @@
                                 scaleOverride = properties.scale
                             )
                         }
-                        else -> null
+                        else -> composition?.composeIntoVibrationEffect() // compose as-is
                     }
                 }
 
@@ -98,20 +97,28 @@
             // TODO(b/345248875): Play audio and haptics
         }
     }
+
+    companion object {
+        val REQUIRED_PRIMITIVES =
+            listOf(
+                VibrationEffect.Composition.PRIMITIVE_SPIN,
+                VibrationEffect.Composition.PRIMITIVE_THUD,
+                VibrationEffect.Composition.PRIMITIVE_TICK,
+                VibrationEffect.Composition.PRIMITIVE_CLICK,
+            )
+    }
 }
 
 fun HapticComposition.composeIntoVibrationEffect(
     scaleOverride: Float? = null,
     delayOverride: Int? = null,
 ): VibrationEffect? {
-    if (primitives == null) return null
-
     val effectComposition = VibrationEffect.startComposition()
     primitives.forEach { primitive ->
         effectComposition.addPrimitive(
             primitive.primitiveId,
             scaleOverride ?: primitive.scale,
-            delayOverride ?: primitive.delay,
+            delayOverride ?: primitive.delayMillis,
         )
     }
     return effectComposition.compose()
diff --git a/msdllib/tests/src/com/google/android/msdl/domain/FakeVibrator.kt b/msdllib/tests/src/com/google/android/msdl/domain/FakeVibrator.kt
index 69dfdc3..e15a6ee 100644
--- a/msdllib/tests/src/com/google/android/msdl/domain/FakeVibrator.kt
+++ b/msdllib/tests/src/com/google/android/msdl/domain/FakeVibrator.kt
@@ -26,9 +26,24 @@
     var latestVibration: VibrationEffect? = null
         private set
 
+    val supportedPrimitives =
+        mutableMapOf(
+            VibrationEffect.Composition.PRIMITIVE_CLICK to true,
+            VibrationEffect.Composition.PRIMITIVE_SPIN to true,
+            VibrationEffect.Composition.PRIMITIVE_THUD to true,
+            VibrationEffect.Composition.PRIMITIVE_TICK to true,
+            VibrationEffect.Composition.PRIMITIVE_LOW_TICK to true,
+            VibrationEffect.Composition.PRIMITIVE_QUICK_FALL to true,
+            VibrationEffect.Composition.PRIMITIVE_QUICK_RISE to true,
+            VibrationEffect.Composition.PRIMITIVE_SLOW_RISE to true,
+        )
+
     var latestAttributes: VibrationAttributes? = null
         private set
 
+    fun setSupportForAllPrimitives(supported: Boolean) =
+        supportedPrimitives.replaceAll { _, _ -> supported }
+
     override fun cancel() {}
 
     override fun cancel(usageFilter: Int) {}
@@ -37,6 +52,9 @@
 
     override fun hasVibrator(): Boolean = this.hasVibrator
 
+    override fun arePrimitivesSupported(vararg primitiveIds: Int): BooleanArray =
+        primitiveIds.map { id -> supportedPrimitives[id] ?: false }.toBooleanArray()
+
     override fun vibrate(
         uid: Int,
         opPkg: String,
diff --git a/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt b/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
index 4dbe9ee..1b10a90 100644
--- a/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
+++ b/msdllib/tests/src/com/google/android/msdl/domain/MSDLPlayerImplTest.kt
@@ -37,12 +37,15 @@
     private val repository = MSDLRepositoryImpl()
     private val vibrator = FakeVibrator()
     private val executor = Executor { it.run() }
+    private val useHapticFallbackForToken = MSDLToken.entries.associateWith { false }.toMutableMap()
 
-    private val msdlPlayer = MSDLPlayerImpl(repository, vibrator, executor)
+    private var msdlPlayer =
+        MSDLPlayerImpl(repository, vibrator, executor, useHapticFallbackForToken)
 
     @Before
     fun setup() {
         MSDLPlayer.SYSTEM_FEEDBACK_LEVEL = FeedbackLevel.EXPRESSIVE
+        vibrator.setSupportForAllPrimitives(true)
     }
 
     @Test
@@ -88,6 +91,24 @@
     }
 
     @Test
+    fun playHapticComposition_withoutSupportedPrimitives_playsFallbackEffects() {
+        // GIVEN that no primitives are supported
+        useHapticFallbackForToken.replaceAll { _, _ -> true }
+
+        // GIVEN the fallback effect of a composition
+        val composition = repository.getHapticData(token.hapticToken)?.get() as? HapticComposition
+        val effect = composition?.fallbackEffect
+
+        // WHEN the composition is played for a token without interaction properties
+        msdlPlayer.playToken(token)
+
+        // THEN the vibration delivers the same fallback effect with USAGE_TOUCH vibration
+        val touchAttributes =
+            VibrationAttributes.Builder().setUsage(VibrationAttributes.USAGE_TOUCH).build()
+        assertVibrationEffectDelivered(effect, touchAttributes)
+    }
+
+    @Test
     fun playHapticComposition_withDynamicVibrationScaleProperties_playsExpectedVibrationEffect() {
         // GIVEN  DynamicVibrationScaleProperties and a vibration effect built with this scale
         val scaleOverride = 0.4f