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.List; 30 import java.util.concurrent.CountDownLatch; 31 import java.util.function.Function; 32 33 /** 34 * Represents a vibration defined by a {@link CombinedVibration} that will be performed by 35 * the IVibrator HAL. 36 */ 37 final class HalVibration extends Vibration { 38 39 public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); 40 41 /** The actual effect to be played. */ 42 @Nullable 43 private CombinedVibration mEffect; 44 45 /** 46 * The original effect that was requested. Typically these two things differ because the effect 47 * was scaled based on the users vibration intensity settings. 48 */ 49 @Nullable 50 private CombinedVibration mOriginalEffect; 51 52 /** Vibration status. */ 53 private Vibration.Status mStatus; 54 55 /** A {@link CountDownLatch} to enable waiting for completion. */ 56 private final CountDownLatch mCompletionLatch = new CountDownLatch(1); 57 HalVibration(@onNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo)58 HalVibration(@NonNull IBinder token, CombinedVibration effect, @NonNull CallerInfo callerInfo) { 59 super(token, callerInfo); 60 this.mEffect = effect; 61 mStatus = Vibration.Status.RUNNING; 62 } 63 64 /** 65 * Set the {@link Status} of this vibration and reports the current system time as this 66 * vibration end time, for debugging purposes. 67 * 68 * <p>This method will only accept given value if the current status is {@link 69 * Status#RUNNING}. 70 */ end(EndInfo info)71 public void end(EndInfo info) { 72 if (hasEnded()) { 73 // Vibration already ended, keep first ending status set and ignore this one. 74 return; 75 } 76 mStatus = info.status; 77 stats.reportEnded(info.endedBy); 78 mCompletionLatch.countDown(); 79 } 80 81 /** Waits indefinitely until another thread calls {@link #end} on this vibration. */ waitForEnd()82 public void waitForEnd() throws InterruptedException { 83 mCompletionLatch.await(); 84 } 85 86 /** 87 * Return the effect to be played when given prebaked effect id is not supported by the 88 * vibrator. 89 */ 90 @Nullable getFallback(int effectId)91 public VibrationEffect getFallback(int effectId) { 92 return mFallbacks.get(effectId); 93 } 94 95 /** 96 * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, 97 * which might be necessary for replacement in realtime. 98 */ addFallback(int effectId, VibrationEffect effect)99 public void addFallback(int effectId, VibrationEffect effect) { 100 mFallbacks.put(effectId, effect); 101 } 102 103 /** 104 * Applied update function to the current effect held by this vibration, and to each fallback 105 * effect added. 106 */ updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)107 public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) { 108 CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn); 109 if (!newEffect.equals(mEffect)) { 110 if (mOriginalEffect == null) { 111 mOriginalEffect = mEffect; 112 } 113 mEffect = newEffect; 114 } 115 for (int i = 0; i < mFallbacks.size(); i++) { 116 mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i))); 117 } 118 } 119 120 /** 121 * Creates a new {@link CombinedVibration} by applying the given transformation function 122 * to each {@link VibrationEffect}. 123 */ transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)124 private static CombinedVibration transformCombinedEffect( 125 CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) { 126 if (combinedEffect instanceof CombinedVibration.Mono) { 127 VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect(); 128 return CombinedVibration.createParallel(fn.apply(effect)); 129 } else if (combinedEffect instanceof CombinedVibration.Stereo) { 130 SparseArray<VibrationEffect> effects = 131 ((CombinedVibration.Stereo) combinedEffect).getEffects(); 132 CombinedVibration.ParallelCombination combination = 133 CombinedVibration.startParallel(); 134 for (int i = 0; i < effects.size(); i++) { 135 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i))); 136 } 137 return combination.combine(); 138 } else if (combinedEffect instanceof CombinedVibration.Sequential) { 139 List<CombinedVibration> effects = 140 ((CombinedVibration.Sequential) combinedEffect).getEffects(); 141 CombinedVibration.SequentialCombination combination = 142 CombinedVibration.startSequential(); 143 for (CombinedVibration effect : effects) { 144 combination.addNext(transformCombinedEffect(effect, fn)); 145 } 146 return combination.combine(); 147 } else { 148 // Unknown combination, return same effect. 149 return combinedEffect; 150 } 151 } 152 153 /** Return true is current status is different from {@link Status#RUNNING}. */ hasEnded()154 public boolean hasEnded() { 155 return mStatus != Status.RUNNING; 156 } 157 158 @Override isRepeating()159 public boolean isRepeating() { 160 return mEffect.getDuration() == Long.MAX_VALUE; 161 } 162 163 /** Return the effect that should be played by this vibration. */ 164 @Nullable getEffect()165 public CombinedVibration getEffect() { 166 return mEffect; 167 } 168 169 /** 170 * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. 171 */ getDebugInfo()172 public Vibration.DebugInfo getDebugInfo() { 173 return new Vibration.DebugInfo(mStatus, stats, mEffect, mOriginalEffect, /* scale= */ 0, 174 callerInfo); 175 } 176 177 /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ getStatsInfo(long completionUptimeMillis)178 public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { 179 int vibrationType = isRepeating() 180 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED 181 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; 182 return new VibrationStats.StatsInfo( 183 callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), mStatus, 184 stats, completionUptimeMillis); 185 } 186 187 /** 188 * Returns true if this vibration can pipeline with the specified one. 189 * 190 * <p>Note that currently, repeating vibrations can't pipeline with following vibrations, 191 * because the cancel() call to stop the repetition will cancel a pending vibration too. This 192 * can be changed if we have a use-case to reason around behavior for. It may also be nice to 193 * pipeline very short vibrations together, regardless of the flag. 194 */ canPipelineWith(HalVibration vib)195 public boolean canPipelineWith(HalVibration vib) { 196 return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet( 197 VibrationAttributes.FLAG_PIPELINED_EFFECT) 198 && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) 199 && !isRepeating(); 200 } 201 } 202