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.IBinder; 23 import android.os.VibrationAttributes; 24 import android.os.VibrationEffect; 25 import android.util.SparseArray; 26 27 import com.android.internal.util.FrameworkStatsLog; 28 29 import java.util.Objects; 30 import java.util.concurrent.CountDownLatch; 31 32 /** 33 * Represents a vibration defined by a {@link CombinedVibration} that will be performed by 34 * the IVibrator HAL. 35 */ 36 final class HalVibration extends Vibration { 37 38 public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); 39 40 /** A {@link CountDownLatch} to enable waiting for completion. */ 41 private final CountDownLatch mCompletionLatch = new CountDownLatch(1); 42 43 /** The original effect that was requested, for debugging purposes. */ 44 @NonNull 45 private final CombinedVibration mOriginalEffect; 46 47 /** 48 * The scaled and adapted effect to be played. This should only be updated from a single thread, 49 * but can be read from different ones for debugging purposes. 50 */ 51 @NonNull 52 private volatile CombinedVibration mEffectToPlay; 53 54 /** Vibration status. */ 55 private Vibration.Status mStatus; 56 57 /** Reported scale values applied to the vibration effects. */ 58 private int mScaleLevel; 59 private float mAdaptiveScale; 60 HalVibration(@onNull IBinder token, @NonNull CombinedVibration effect, @NonNull CallerInfo callerInfo)61 HalVibration(@NonNull IBinder token, @NonNull CombinedVibration effect, 62 @NonNull CallerInfo callerInfo) { 63 super(token, callerInfo); 64 mOriginalEffect = effect; 65 mEffectToPlay = effect; 66 mStatus = Vibration.Status.RUNNING; 67 mScaleLevel = VibrationScaler.SCALE_NONE; 68 mAdaptiveScale = VibrationScaler.ADAPTIVE_SCALE_NONE; 69 } 70 71 /** 72 * Set the {@link Status} of this vibration and reports the current system time as this 73 * vibration end time, for debugging purposes. 74 * 75 * <p>This method will only accept given value if the current status is {@link 76 * Status#RUNNING}. 77 */ end(EndInfo info)78 public void end(EndInfo info) { 79 if (hasEnded()) { 80 // Vibration already ended, keep first ending status set and ignore this one. 81 return; 82 } 83 mStatus = info.status; 84 stats.reportEnded(info.endedBy); 85 mCompletionLatch.countDown(); 86 } 87 88 /** Waits indefinitely until another thread calls {@link #end} on this vibration. */ waitForEnd()89 public void waitForEnd() throws InterruptedException { 90 mCompletionLatch.await(); 91 } 92 93 /** 94 * Return the effect to be played when given prebaked effect id is not supported by the 95 * vibrator. 96 */ 97 @Nullable getFallback(int effectId)98 public VibrationEffect getFallback(int effectId) { 99 return mFallbacks.get(effectId); 100 } 101 102 /** 103 * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, 104 * which might be necessary for replacement in realtime. 105 */ addFallback(int effectId, VibrationEffect effect)106 public void addFallback(int effectId, VibrationEffect effect) { 107 mFallbacks.put(effectId, effect); 108 } 109 110 /** 111 * Resolves the default vibration amplitude of {@link #getEffectToPlay()} and each fallback. 112 * 113 * @param defaultAmplitude An integer in [1,255] representing the device default amplitude to 114 * replace the {@link VibrationEffect#DEFAULT_AMPLITUDE}. 115 */ resolveEffects(int defaultAmplitude)116 public void resolveEffects(int defaultAmplitude) { 117 CombinedVibration newEffect = 118 mEffectToPlay.transform(VibrationEffect::resolve, defaultAmplitude); 119 if (!Objects.equals(mEffectToPlay, newEffect)) { 120 mEffectToPlay = newEffect; 121 } 122 for (int i = 0; i < mFallbacks.size(); i++) { 123 mFallbacks.setValueAt(i, mFallbacks.valueAt(i).resolve(defaultAmplitude)); 124 } 125 } 126 127 /** 128 * Scales the {@link #getEffectToPlay()} and each fallback effect based on the vibration usage. 129 */ scaleEffects(VibrationScaler scaler)130 public void scaleEffects(VibrationScaler scaler) { 131 int vibrationUsage = callerInfo.attrs.getUsage(); 132 133 // Save scale values for debugging purposes. 134 mScaleLevel = scaler.getScaleLevel(vibrationUsage); 135 mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage); 136 stats.reportAdaptiveScale(mAdaptiveScale); 137 138 // Scale all VibrationEffect instances in given CombinedVibration. 139 CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage); 140 if (!Objects.equals(mEffectToPlay, newEffect)) { 141 mEffectToPlay = newEffect; 142 } 143 144 // Scale all fallback VibrationEffect instances that can be used by VibrationThread. 145 for (int i = 0; i < mFallbacks.size(); i++) { 146 mFallbacks.setValueAt(i, scaler.scale(mFallbacks.valueAt(i), vibrationUsage)); 147 } 148 } 149 150 /** 151 * Adapts the {@link #getEffectToPlay()} to the device using given vibrator adapter. 152 * 153 * @param deviceAdapter A {@link CombinedVibration.VibratorAdapter} that transforms vibration 154 * effects to device vibrators based on its capabilities. 155 */ adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter)156 public void adaptToDevice(CombinedVibration.VibratorAdapter deviceAdapter) { 157 CombinedVibration newEffect = mEffectToPlay.adapt(deviceAdapter); 158 if (!Objects.equals(mEffectToPlay, newEffect)) { 159 mEffectToPlay = newEffect; 160 } 161 // No need to update fallback effects, they are already configured per device. 162 } 163 164 /** Return true is current status is different from {@link Status#RUNNING}. */ hasEnded()165 public boolean hasEnded() { 166 return mStatus != Status.RUNNING; 167 } 168 169 @Override isRepeating()170 public boolean isRepeating() { 171 return mOriginalEffect.getDuration() == Long.MAX_VALUE; 172 } 173 174 /** Return the effect that should be played by this vibration. */ getEffectToPlay()175 public CombinedVibration getEffectToPlay() { 176 return mEffectToPlay; 177 } 178 179 /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */ getDebugInfo()180 public Vibration.DebugInfo getDebugInfo() { 181 // Clear the original effect if it's the same as the effect that was played, for simplicity 182 CombinedVibration originalEffect = 183 Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect; 184 return new Vibration.DebugInfo(mStatus, stats, mEffectToPlay, originalEffect, 185 mScaleLevel, mAdaptiveScale, callerInfo); 186 } 187 188 /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ getStatsInfo(long completionUptimeMillis)189 public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { 190 int vibrationType = isRepeating() 191 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED 192 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; 193 return new VibrationStats.StatsInfo( 194 callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus, 195 stats, completionUptimeMillis); 196 } 197 198 /** 199 * Returns true if this vibration can pipeline with the specified one. 200 * 201 * <p>Note that currently, repeating vibrations can't pipeline with following vibrations, 202 * because the cancel() call to stop the repetition will cancel a pending vibration too. This 203 * can be changed if we have a use-case to reason around behavior for. It may also be nice to 204 * pipeline very short vibrations together, regardless of the flag. 205 */ canPipelineWith(HalVibration vib)206 public boolean canPipelineWith(HalVibration vib) { 207 return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet( 208 VibrationAttributes.FLAG_PIPELINED_EFFECT) 209 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) 210 && !isRepeating(); 211 } 212 } 213