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