1 /* 2 * Copyright (C) 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 com.android.server.vibrator; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.os.CombinedVibration; 22 import android.os.VibrationAttributes; 23 import android.os.VibrationEffect; 24 import android.os.VibratorInfo; 25 import android.os.vibrator.Flags; 26 import android.os.vibrator.PrebakedSegment; 27 import android.os.vibrator.VibrationEffectSegment; 28 import android.util.SparseArray; 29 30 import java.util.List; 31 import java.util.Objects; 32 import java.util.concurrent.CountDownLatch; 33 import java.util.function.IntFunction; 34 35 /** 36 * Represents a vibration defined by a {@link CombinedVibration} that will be performed by 37 * the IVibrator HAL. 38 */ 39 final class HalVibration extends Vibration { 40 41 public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); 42 43 /** A {@link CountDownLatch} to enable waiting for completion. */ 44 private final CountDownLatch mCompletionLatch = new CountDownLatch(1); 45 46 /** The original effect that was requested, for debugging purposes. */ 47 @NonNull 48 private final CombinedVibration mOriginalEffect; 49 50 /** 51 * The scaled and adapted effect to be played. This should only be updated from a single thread, 52 * but can be read from different ones for debugging purposes. 53 */ 54 @NonNull 55 private volatile CombinedVibration mEffectToPlay; 56 57 /** Reported scale values applied to the vibration effects. */ 58 private int mScaleLevel; 59 private float mAdaptiveScale; 60 HalVibration(@onNull VibrationSession.CallerInfo callerInfo, @NonNull CombinedVibration effect)61 HalVibration(@NonNull VibrationSession.CallerInfo callerInfo, 62 @NonNull CombinedVibration effect) { 63 super(callerInfo); 64 mOriginalEffect = effect; 65 mEffectToPlay = effect; 66 mScaleLevel = VibrationScaler.SCALE_NONE; 67 mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE; 68 } 69 70 @Override end(EndInfo endInfo)71 public void end(EndInfo endInfo) { 72 super.end(endInfo); 73 mCompletionLatch.countDown(); 74 } 75 76 /** Waits indefinitely until another thread calls {@link #end} on this vibration. */ waitForEnd()77 public void waitForEnd() throws InterruptedException { 78 mCompletionLatch.await(); 79 } 80 81 /** 82 * Return the effect to be played when given prebaked effect id is not supported by the 83 * vibrator. 84 */ 85 @Nullable getFallback(int effectId)86 public VibrationEffect getFallback(int effectId) { 87 return mFallbacks.get(effectId); 88 } 89 90 /** 91 * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which 92 * might be necessary for replacement in realtime. 93 */ fillFallbacks(IntFunction<VibrationEffect> fallbackProvider)94 public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) { 95 fillFallbacksForEffect(mEffectToPlay, fallbackProvider); 96 } 97 98 /** 99 * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage. 100 */ scaleEffects(VibrationScaler scaler)101 public void scaleEffects(VibrationScaler scaler) { 102 int vibrationUsage = callerInfo.attrs.getUsage(); 103 104 // Save scale values for debugging purposes. 105 mScaleLevel = scaler.getScaleLevel(vibrationUsage); 106 mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage); 107 stats.reportAdaptiveScale(mAdaptiveScale); 108 109 // Scale all VibrationEffect instances in given CombinedVibration. 110 CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage); 111 if (!Objects.equals(mEffectToPlay, newEffect)) { 112 mEffectToPlay = newEffect; 113 } 114 115 // Scale all fallback VibrationEffect instances that can be used by VibrationThread. 116 for (int i = 0; i < mFallbacks.size(); i++) { 117 mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage)); 118 } 119 } 120 121 /** 122 * Adapts the {@link #getEffectToPlay()} to the device using given vibrator adapter. 123 * 124 * @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration 125 * effects to device vibrators based on its capabilities. 126 */ adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter)127 public boolean adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) { 128 CombinedVibration adaptedEffect = mEffectToPlay.adapt(deviceAdapter); 129 if (adaptedEffect == null) { 130 return false; 131 } 132 133 if (!mEffectToPlay.equals(adaptedEffect)) { 134 mEffectToPlay = adaptedEffect; 135 } 136 // No need to update fallback effects, they are already configured per device. 137 138 return true; 139 } 140 141 /** Return the effect that should be played by this vibration. */ getEffectToPlay()142 public CombinedVibration getEffectToPlay() { 143 return mEffectToPlay; 144 } 145 146 @Override getDebugInfo()147 public VibrationSession.DebugInfo getDebugInfo() { 148 // Clear the original effect if it's the same as the effect that was played, for simplicity 149 CombinedVibration originalEffect = 150 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect; 151 return new Vibration.DebugInfoImpl(getStatus(), callerInfo, 152 VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay, 153 originalEffect, mScaleLevel, mAdaptiveScale); 154 } 155 156 /** Returns true if this vibration can pipeline with the specified one. */ canPipelineWith(HalVibration vib, @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs)157 public boolean canPipelineWith(HalVibration vib, 158 @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs) { 159 long effectDuration = Flags.vibrationPipelineEnabled() && (vibratorInfos != null) 160 ? mEffectToPlay.getDuration(vibratorInfos) 161 : mEffectToPlay.getDuration(); 162 if (effectDuration == Long.MAX_VALUE) { 163 // Repeating vibrations can't pipeline with following vibrations, because the cancel() 164 // call to stop the repetition will cancel a pending vibration too. This can be changed 165 // if we have a use-case, requiring changes to how pipelined vibrations are cancelled. 166 return false; 167 } 168 if (Flags.vibrationPipelineEnabled() 169 && (effectDuration > 0) && (effectDuration < durationThresholdMs)) { 170 // Duration is known and it's less than the pipeline threshold, so allow it. 171 // No need to check UID, as we want to avoid cancelling any short effect and let the 172 // vibrator hardware gracefully finish the vibration. 173 return true; 174 } 175 // Check the same app is requesting multiple vibrations with the pipeline flag, 176 // independently of the effect durations. 177 return callerInfo.uid == vib.callerInfo.uid 178 && callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) 179 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT); 180 } 181 fillFallbacksForEffect(CombinedVibration effect, IntFunction<VibrationEffect> fallbackProvider)182 private void fillFallbacksForEffect(CombinedVibration effect, 183 IntFunction<VibrationEffect> fallbackProvider) { 184 if (effect instanceof CombinedVibration.Mono) { 185 fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider); 186 } else if (effect instanceof CombinedVibration.Stereo) { 187 SparseArray<VibrationEffect> effects = 188 ((CombinedVibration.Stereo) effect).getEffects(); 189 for (int i = 0; i < effects.size(); i++) { 190 fillFallbacksForEffect(effects.valueAt(i), fallbackProvider); 191 } 192 } else if (effect instanceof CombinedVibration.Sequential) { 193 List<CombinedVibration> effects = 194 ((CombinedVibration.Sequential) effect).getEffects(); 195 for (int i = 0; i < effects.size(); i++) { 196 fillFallbacksForEffect(effects.get(i), fallbackProvider); 197 } 198 } 199 } 200 fillFallbacksForEffect(VibrationEffect effect, IntFunction<VibrationEffect> fallbackProvider)201 private void fillFallbacksForEffect(VibrationEffect effect, 202 IntFunction<VibrationEffect> fallbackProvider) { 203 if (!(effect instanceof VibrationEffect.Composed composed)) { 204 return; 205 } 206 int segmentCount = composed.getSegments().size(); 207 for (int i = 0; i < segmentCount; i++) { 208 VibrationEffectSegment segment = composed.getSegments().get(i); 209 if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) { 210 VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId()); 211 if (fallback != null) { 212 mFallbacks.put(prebaked.getEffectId(), fallback); 213 } 214 } 215 } 216 } 217 } 218