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.android.systemui.haptics.qs 18 19 import android.os.VibrationEffect 20 import android.util.Log 21 import kotlin.math.max 22 23 object LongPressHapticBuilder { 24 25 const val INVALID_DURATION = 0 /* in ms */ 26 27 private const val TAG = "LongPressHapticBuilder" 28 private const val SPIN_SCALE = 0.2f 29 private const val CLICK_SCALE = 0.5f 30 private const val LOW_TICK_SCALE = 0.08f 31 private const val WARMUP_TIME = 75 /* in ms */ 32 private const val DAMPING_TIME = 24 /* in ms */ 33 34 /** Create the signal that indicates that a long-press action is available. */ 35 fun createLongPressHint( 36 lowTickDuration: Int, 37 spinDuration: Int, 38 effectDuration: Int 39 ): VibrationEffect? { 40 if (lowTickDuration == 0 || spinDuration == 0) { 41 Log.d( 42 TAG, 43 "The LOW_TICK and/or SPIN primitives are not supported. No signal created.", 44 ) 45 return null 46 } 47 if (effectDuration < WARMUP_TIME + spinDuration + DAMPING_TIME) { 48 Log.d( 49 TAG, 50 "Cannot fit long-press hint signal in the effect duration. No signal created", 51 ) 52 return null 53 } 54 55 val nLowTicks = WARMUP_TIME / lowTickDuration 56 val rampDownLowTicks = DAMPING_TIME / lowTickDuration 57 val composition = VibrationEffect.startComposition() 58 59 // Warmup low ticks 60 repeat(nLowTicks) { 61 composition.addPrimitive( 62 VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 63 LOW_TICK_SCALE, 64 0, 65 ) 66 } 67 68 // Spin effect 69 composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_SPIN, SPIN_SCALE, 0) 70 71 // Damping low ticks 72 repeat(rampDownLowTicks) { i -> 73 composition.addPrimitive( 74 VibrationEffect.Composition.PRIMITIVE_LOW_TICK, 75 LOW_TICK_SCALE / (i + 1), 76 0, 77 ) 78 } 79 80 return composition.compose() 81 } 82 83 /** Create a "snapping" effect that triggers at the end of a long-press gesture */ 84 fun createSnapEffect(): VibrationEffect? = 85 VibrationEffect.startComposition() 86 .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, CLICK_SCALE, 0) 87 .compose() 88 89 /** Creates a signal that indicates the reversal of the long-press animation. */ 90 fun createReversedEffect( 91 pausedProgress: Float, 92 lowTickDuration: Int, 93 effectDuration: Int, 94 ): VibrationEffect? { 95 val duration = pausedProgress * effectDuration 96 if (duration == 0f) return null 97 98 if (lowTickDuration == 0) { 99 Log.d(TAG, "Cannot play reverse haptics because LOW_TICK is not supported") 100 return null 101 } 102 103 val nLowTicks = (duration / lowTickDuration).toInt() 104 if (nLowTicks == 0) return null 105 106 val composition = VibrationEffect.startComposition() 107 var scale: Float 108 val step = LOW_TICK_SCALE / nLowTicks 109 repeat(nLowTicks) { i -> 110 scale = max(LOW_TICK_SCALE - step * i, 0f) 111 composition.addPrimitive(VibrationEffect.Composition.PRIMITIVE_LOW_TICK, scale, 0) 112 } 113 return composition.compose() 114 } 115 } 116