1 /* 2 * Copyright (C) 2020 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.Nullable; 20 import android.os.CombinedVibration; 21 import android.os.IBinder; 22 import android.os.SystemClock; 23 import android.os.VibrationAttributes; 24 import android.os.VibrationEffect; 25 import android.os.vibrator.PrebakedSegment; 26 import android.os.vibrator.PrimitiveSegment; 27 import android.os.vibrator.RampSegment; 28 import android.os.vibrator.StepSegment; 29 import android.os.vibrator.VibrationEffectSegment; 30 import android.util.SparseArray; 31 import android.util.proto.ProtoOutputStream; 32 33 import java.text.SimpleDateFormat; 34 import java.util.Date; 35 import java.util.List; 36 import java.util.function.Function; 37 38 /** Represents a vibration request to the vibrator service. */ 39 final class Vibration { 40 private static final String TAG = "Vibration"; 41 private static final SimpleDateFormat DEBUG_DATE_FORMAT = 42 new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 43 44 enum Status { 45 RUNNING, 46 FINISHED, 47 FORWARDED_TO_INPUT_DEVICES, 48 CANCELLED, 49 IGNORED_ERROR_APP_OPS, 50 IGNORED, 51 IGNORED_APP_OPS, 52 IGNORED_BACKGROUND, 53 IGNORED_RINGTONE, 54 IGNORED_UNKNOWN_VIBRATION, 55 IGNORED_UNSUPPORTED, 56 IGNORED_FOR_ALARM, 57 IGNORED_FOR_EXTERNAL, 58 IGNORED_FOR_ONGOING, 59 IGNORED_FOR_POWER, 60 IGNORED_FOR_SETTINGS, 61 } 62 63 /** Start time in CLOCK_BOOTTIME base. */ 64 public final long startTime; 65 public final VibrationAttributes attrs; 66 public final long id; 67 public final int uid; 68 public final String opPkg; 69 public final String reason; 70 public final IBinder token; 71 public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); 72 73 /** The actual effect to be played. */ 74 @Nullable 75 private CombinedVibration mEffect; 76 77 /** 78 * The original effect that was requested. Typically these two things differ because the effect 79 * was scaled based on the users vibration intensity settings. 80 */ 81 @Nullable 82 private CombinedVibration mOriginalEffect; 83 84 /** 85 * Start/end times in unix epoch time. Only to be used for debugging purposes and to correlate 86 * with other system events, any duration calculations should be done use {@link #startTime} so 87 * as not to be affected by discontinuities created by RTC adjustments. 88 */ 89 private final long mStartTimeDebug; 90 private long mEndTimeDebug; 91 private Status mStatus; 92 Vibration(IBinder token, int id, CombinedVibration effect, VibrationAttributes attrs, int uid, String opPkg, String reason)93 Vibration(IBinder token, int id, CombinedVibration effect, 94 VibrationAttributes attrs, int uid, String opPkg, String reason) { 95 this.token = token; 96 this.mEffect = effect; 97 this.id = id; 98 this.startTime = SystemClock.elapsedRealtime(); 99 this.attrs = attrs; 100 this.uid = uid; 101 this.opPkg = opPkg; 102 this.reason = reason; 103 mStartTimeDebug = System.currentTimeMillis(); 104 mStatus = Status.RUNNING; 105 } 106 107 /** 108 * Set the {@link Status} of this vibration and the current system time as this 109 * vibration end time, for debugging purposes. 110 * 111 * <p>This method will only accept given value if the current status is {@link 112 * Status#RUNNING}. 113 */ end(Status status)114 public void end(Status status) { 115 if (hasEnded()) { 116 // Vibration already ended, keep first ending status set and ignore this one. 117 return; 118 } 119 mStatus = status; 120 mEndTimeDebug = System.currentTimeMillis(); 121 } 122 123 /** 124 * Return the effect to be played when given prebaked effect id is not supported by the 125 * vibrator. 126 */ 127 @Nullable getFallback(int effectId)128 public VibrationEffect getFallback(int effectId) { 129 return mFallbacks.get(effectId); 130 } 131 132 /** 133 * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, 134 * which might be necessary for replacement in realtime. 135 */ addFallback(int effectId, VibrationEffect effect)136 public void addFallback(int effectId, VibrationEffect effect) { 137 mFallbacks.put(effectId, effect); 138 } 139 140 /** 141 * Applied update function to the current effect held by this vibration, and to each fallback 142 * effect added. 143 */ updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)144 public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) { 145 CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn); 146 if (!newEffect.equals(mEffect)) { 147 if (mOriginalEffect == null) { 148 mOriginalEffect = mEffect; 149 } 150 mEffect = newEffect; 151 } 152 for (int i = 0; i < mFallbacks.size(); i++) { 153 mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i))); 154 } 155 } 156 157 /** 158 * Creates a new {@link CombinedVibration} by applying the given transformation function 159 * to each {@link VibrationEffect}. 160 */ transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)161 private static CombinedVibration transformCombinedEffect( 162 CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) { 163 if (combinedEffect instanceof CombinedVibration.Mono) { 164 VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect(); 165 return CombinedVibration.createParallel(fn.apply(effect)); 166 } else if (combinedEffect instanceof CombinedVibration.Stereo) { 167 SparseArray<VibrationEffect> effects = 168 ((CombinedVibration.Stereo) combinedEffect).getEffects(); 169 CombinedVibration.ParallelCombination combination = 170 CombinedVibration.startParallel(); 171 for (int i = 0; i < effects.size(); i++) { 172 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i))); 173 } 174 return combination.combine(); 175 } else if (combinedEffect instanceof CombinedVibration.Sequential) { 176 List<CombinedVibration> effects = 177 ((CombinedVibration.Sequential) combinedEffect).getEffects(); 178 CombinedVibration.SequentialCombination combination = 179 CombinedVibration.startSequential(); 180 for (CombinedVibration effect : effects) { 181 combination.addNext(transformCombinedEffect(effect, fn)); 182 } 183 return combination.combine(); 184 } else { 185 // Unknown combination, return same effect. 186 return combinedEffect; 187 } 188 } 189 190 /** Return true is current status is different from {@link Status#RUNNING}. */ hasEnded()191 public boolean hasEnded() { 192 return mStatus != Status.RUNNING; 193 } 194 195 /** Return true is effect is a repeating vibration. */ isRepeating()196 public boolean isRepeating() { 197 return mEffect.getDuration() == Long.MAX_VALUE; 198 } 199 200 /** Return the effect that should be played by this vibration. */ 201 @Nullable getEffect()202 public CombinedVibration getEffect() { 203 return mEffect; 204 } 205 206 /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */ getDebugInfo()207 public Vibration.DebugInfo getDebugInfo() { 208 return new Vibration.DebugInfo( 209 mStartTimeDebug, mEndTimeDebug, mEffect, mOriginalEffect, /* scale= */ 0, attrs, 210 uid, opPkg, reason, mStatus); 211 } 212 213 /** Debug information about vibrations. */ 214 static final class DebugInfo { 215 private final long mStartTimeDebug; 216 private final long mEndTimeDebug; 217 private final CombinedVibration mEffect; 218 private final CombinedVibration mOriginalEffect; 219 private final float mScale; 220 private final VibrationAttributes mAttrs; 221 private final int mUid; 222 private final String mOpPkg; 223 private final String mReason; 224 private final Status mStatus; 225 DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibration effect, CombinedVibration originalEffect, float scale, VibrationAttributes attrs, int uid, String opPkg, String reason, Status status)226 DebugInfo(long startTimeDebug, long endTimeDebug, CombinedVibration effect, 227 CombinedVibration originalEffect, float scale, VibrationAttributes attrs, 228 int uid, String opPkg, String reason, Status status) { 229 mStartTimeDebug = startTimeDebug; 230 mEndTimeDebug = endTimeDebug; 231 mEffect = effect; 232 mOriginalEffect = originalEffect; 233 mScale = scale; 234 mAttrs = attrs; 235 mUid = uid; 236 mOpPkg = opPkg; 237 mReason = reason; 238 mStatus = status; 239 } 240 241 @Override toString()242 public String toString() { 243 return new StringBuilder() 244 .append("startTime: ") 245 .append(DEBUG_DATE_FORMAT.format(new Date(mStartTimeDebug))) 246 .append(", endTime: ") 247 .append(mEndTimeDebug == 0 ? null 248 : DEBUG_DATE_FORMAT.format(new Date(mEndTimeDebug))) 249 .append(", status: ") 250 .append(mStatus.name().toLowerCase()) 251 .append(", effect: ") 252 .append(mEffect) 253 .append(", originalEffect: ") 254 .append(mOriginalEffect) 255 .append(", scale: ") 256 .append(String.format("%.2f", mScale)) 257 .append(", attrs: ") 258 .append(mAttrs) 259 .append(", uid: ") 260 .append(mUid) 261 .append(", opPkg: ") 262 .append(mOpPkg) 263 .append(", reason: ") 264 .append(mReason) 265 .toString(); 266 } 267 268 /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */ dumpProto(ProtoOutputStream proto, long fieldId)269 public void dumpProto(ProtoOutputStream proto, long fieldId) { 270 final long token = proto.start(fieldId); 271 proto.write(VibrationProto.START_TIME, mStartTimeDebug); 272 proto.write(VibrationProto.END_TIME, mEndTimeDebug); 273 proto.write(VibrationProto.STATUS, mStatus.ordinal()); 274 275 final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); 276 proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage()); 277 proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage()); 278 proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags()); 279 proto.end(attrsToken); 280 281 if (mEffect != null) { 282 dumpEffect(proto, VibrationProto.EFFECT, mEffect); 283 } 284 if (mOriginalEffect != null) { 285 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect); 286 } 287 288 proto.end(token); 289 } 290 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration effect)291 private void dumpEffect( 292 ProtoOutputStream proto, long fieldId, CombinedVibration effect) { 293 dumpEffect(proto, fieldId, 294 (CombinedVibration.Sequential) CombinedVibration.startSequential() 295 .addNext(effect) 296 .combine()); 297 } 298 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect)299 private void dumpEffect( 300 ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect) { 301 final long token = proto.start(fieldId); 302 for (int i = 0; i < effect.getEffects().size(); i++) { 303 CombinedVibration nestedEffect = effect.getEffects().get(i); 304 if (nestedEffect instanceof CombinedVibration.Mono) { 305 dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, 306 (CombinedVibration.Mono) nestedEffect); 307 } else if (nestedEffect instanceof CombinedVibration.Stereo) { 308 dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, 309 (CombinedVibration.Stereo) nestedEffect); 310 } 311 proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i)); 312 } 313 proto.end(token); 314 } 315 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect)316 private void dumpEffect( 317 ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect) { 318 final long token = proto.start(fieldId); 319 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect()); 320 proto.end(token); 321 } 322 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect)323 private void dumpEffect( 324 ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect) { 325 final long token = proto.start(fieldId); 326 for (int i = 0; i < effect.getEffects().size(); i++) { 327 proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i)); 328 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i)); 329 } 330 proto.end(token); 331 } 332 dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect)333 private void dumpEffect( 334 ProtoOutputStream proto, long fieldId, VibrationEffect effect) { 335 final long token = proto.start(fieldId); 336 VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; 337 for (VibrationEffectSegment segment : composed.getSegments()) { 338 dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment); 339 } 340 proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex()); 341 proto.end(token); 342 } 343 dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffectSegment segment)344 private void dumpEffect(ProtoOutputStream proto, long fieldId, 345 VibrationEffectSegment segment) { 346 final long token = proto.start(fieldId); 347 if (segment instanceof StepSegment) { 348 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment); 349 } else if (segment instanceof RampSegment) { 350 dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment); 351 } else if (segment instanceof PrebakedSegment) { 352 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment); 353 } else if (segment instanceof PrimitiveSegment) { 354 dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment); 355 } 356 proto.end(token); 357 } 358 dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment)359 private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) { 360 final long token = proto.start(fieldId); 361 proto.write(StepSegmentProto.DURATION, segment.getDuration()); 362 proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude()); 363 proto.write(StepSegmentProto.FREQUENCY, segment.getFrequency()); 364 proto.end(token); 365 } 366 dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment)367 private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) { 368 final long token = proto.start(fieldId); 369 proto.write(RampSegmentProto.DURATION, segment.getDuration()); 370 proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude()); 371 proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude()); 372 proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequency()); 373 proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequency()); 374 proto.end(token); 375 } 376 dumpEffect(ProtoOutputStream proto, long fieldId, PrebakedSegment segment)377 private void dumpEffect(ProtoOutputStream proto, long fieldId, 378 PrebakedSegment segment) { 379 final long token = proto.start(fieldId); 380 proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId()); 381 proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength()); 382 proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback()); 383 proto.end(token); 384 } 385 dumpEffect(ProtoOutputStream proto, long fieldId, PrimitiveSegment segment)386 private void dumpEffect(ProtoOutputStream proto, long fieldId, 387 PrimitiveSegment segment) { 388 final long token = proto.start(fieldId); 389 proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId()); 390 proto.write(PrimitiveSegmentProto.SCALE, segment.getScale()); 391 proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay()); 392 proto.end(token); 393 } 394 } 395 } 396