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.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.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 com.android.internal.util.FrameworkStatsLog; 34 35 import java.text.SimpleDateFormat; 36 import java.util.Date; 37 import java.util.List; 38 import java.util.Objects; 39 import java.util.concurrent.CountDownLatch; 40 import java.util.function.Function; 41 42 /** Represents a vibration request to the vibrator service. */ 43 final class Vibration { 44 private static final SimpleDateFormat DEBUG_DATE_FORMAT = 45 new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 46 47 /** Vibration status with reference to values from vibratormanagerservice.proto for logging. */ 48 enum Status { 49 UNKNOWN(VibrationProto.UNKNOWN), 50 RUNNING(VibrationProto.RUNNING), 51 FINISHED(VibrationProto.FINISHED), 52 FINISHED_UNEXPECTED(VibrationProto.FINISHED_UNEXPECTED), 53 FORWARDED_TO_INPUT_DEVICES(VibrationProto.FORWARDED_TO_INPUT_DEVICES), 54 CANCELLED_BINDER_DIED(VibrationProto.CANCELLED_BINDER_DIED), 55 CANCELLED_BY_SCREEN_OFF(VibrationProto.CANCELLED_BY_SCREEN_OFF), 56 CANCELLED_BY_SETTINGS_UPDATE(VibrationProto.CANCELLED_BY_SETTINGS_UPDATE), 57 CANCELLED_BY_USER(VibrationProto.CANCELLED_BY_USER), 58 CANCELLED_BY_UNKNOWN_REASON(VibrationProto.CANCELLED_BY_UNKNOWN_REASON), 59 CANCELLED_SUPERSEDED(VibrationProto.CANCELLED_SUPERSEDED), 60 IGNORED_ERROR_APP_OPS(VibrationProto.IGNORED_ERROR_APP_OPS), 61 IGNORED_ERROR_CANCELLING(VibrationProto.IGNORED_ERROR_CANCELLING), 62 IGNORED_ERROR_SCHEDULING(VibrationProto.IGNORED_ERROR_SCHEDULING), 63 IGNORED_ERROR_TOKEN(VibrationProto.IGNORED_ERROR_TOKEN), 64 IGNORED_APP_OPS(VibrationProto.IGNORED_APP_OPS), 65 IGNORED_BACKGROUND(VibrationProto.IGNORED_BACKGROUND), 66 IGNORED_UNKNOWN_VIBRATION(VibrationProto.IGNORED_UNKNOWN_VIBRATION), 67 IGNORED_UNSUPPORTED(VibrationProto.IGNORED_UNSUPPORTED), 68 IGNORED_FOR_EXTERNAL(VibrationProto.IGNORED_FOR_EXTERNAL), 69 IGNORED_FOR_HIGHER_IMPORTANCE(VibrationProto.IGNORED_FOR_HIGHER_IMPORTANCE), 70 IGNORED_FOR_ONGOING(VibrationProto.IGNORED_FOR_ONGOING), 71 IGNORED_FOR_POWER(VibrationProto.IGNORED_FOR_POWER), 72 IGNORED_FOR_RINGER_MODE(VibrationProto.IGNORED_FOR_RINGER_MODE), 73 IGNORED_FOR_SETTINGS(VibrationProto.IGNORED_FOR_SETTINGS), 74 IGNORED_SUPERSEDED(VibrationProto.IGNORED_SUPERSEDED), 75 IGNORED_FROM_VIRTUAL_DEVICE(VibrationProto.IGNORED_FROM_VIRTUAL_DEVICE); 76 77 private final int mProtoEnumValue; 78 Status(int value)79 Status(int value) { 80 mProtoEnumValue = value; 81 } 82 getProtoEnumValue()83 public int getProtoEnumValue() { 84 return mProtoEnumValue; 85 } 86 } 87 88 public final VibrationAttributes attrs; 89 public final long id; 90 public final int uid; 91 public final int displayId; 92 public final String opPkg; 93 public final String reason; 94 public final IBinder token; 95 public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); 96 97 /** The actual effect to be played. */ 98 @Nullable 99 private CombinedVibration mEffect; 100 101 /** 102 * The original effect that was requested. Typically these two things differ because the effect 103 * was scaled based on the users vibration intensity settings. 104 */ 105 @Nullable 106 private CombinedVibration mOriginalEffect; 107 108 /** Vibration status. */ 109 private Vibration.Status mStatus; 110 111 /** Vibration runtime stats. */ 112 private final VibrationStats mStats = new VibrationStats(); 113 114 /** A {@link CountDownLatch} to enable waiting for completion. */ 115 private final CountDownLatch mCompletionLatch = new CountDownLatch(1); 116 Vibration(IBinder token, int id, CombinedVibration effect, VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason)117 Vibration(IBinder token, int id, CombinedVibration effect, 118 VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) { 119 this.token = token; 120 this.mEffect = effect; 121 this.id = id; 122 this.attrs = attrs; 123 this.uid = uid; 124 this.displayId = displayId; 125 this.opPkg = opPkg; 126 this.reason = reason; 127 mStatus = Vibration.Status.RUNNING; 128 } 129 stats()130 VibrationStats stats() { 131 return mStats; 132 } 133 134 /** 135 * Set the {@link Status} of this vibration and reports the current system time as this 136 * vibration end time, for debugging purposes. 137 * 138 * <p>This method will only accept given value if the current status is {@link 139 * Status#RUNNING}. 140 */ end(EndInfo info)141 public void end(EndInfo info) { 142 if (hasEnded()) { 143 // Vibration already ended, keep first ending status set and ignore this one. 144 return; 145 } 146 mStatus = info.status; 147 mStats.reportEnded(info.endedByUid, info.endedByUsage); 148 mCompletionLatch.countDown(); 149 } 150 151 /** Waits indefinitely until another thread calls {@link #end} on this vibration. */ waitForEnd()152 public void waitForEnd() throws InterruptedException { 153 mCompletionLatch.await(); 154 } 155 156 /** 157 * Return the effect to be played when given prebaked effect id is not supported by the 158 * vibrator. 159 */ 160 @Nullable getFallback(int effectId)161 public VibrationEffect getFallback(int effectId) { 162 return mFallbacks.get(effectId); 163 } 164 165 /** 166 * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, 167 * which might be necessary for replacement in realtime. 168 */ addFallback(int effectId, VibrationEffect effect)169 public void addFallback(int effectId, VibrationEffect effect) { 170 mFallbacks.put(effectId, effect); 171 } 172 173 /** 174 * Applied update function to the current effect held by this vibration, and to each fallback 175 * effect added. 176 */ updateEffects(Function<VibrationEffect, VibrationEffect> updateFn)177 public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) { 178 CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn); 179 if (!newEffect.equals(mEffect)) { 180 if (mOriginalEffect == null) { 181 mOriginalEffect = mEffect; 182 } 183 mEffect = newEffect; 184 } 185 for (int i = 0; i < mFallbacks.size(); i++) { 186 mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i))); 187 } 188 } 189 190 /** 191 * Creates a new {@link CombinedVibration} by applying the given transformation function 192 * to each {@link VibrationEffect}. 193 */ transformCombinedEffect( CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn)194 private static CombinedVibration transformCombinedEffect( 195 CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) { 196 if (combinedEffect instanceof CombinedVibration.Mono) { 197 VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect(); 198 return CombinedVibration.createParallel(fn.apply(effect)); 199 } else if (combinedEffect instanceof CombinedVibration.Stereo) { 200 SparseArray<VibrationEffect> effects = 201 ((CombinedVibration.Stereo) combinedEffect).getEffects(); 202 CombinedVibration.ParallelCombination combination = 203 CombinedVibration.startParallel(); 204 for (int i = 0; i < effects.size(); i++) { 205 combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i))); 206 } 207 return combination.combine(); 208 } else if (combinedEffect instanceof CombinedVibration.Sequential) { 209 List<CombinedVibration> effects = 210 ((CombinedVibration.Sequential) combinedEffect).getEffects(); 211 CombinedVibration.SequentialCombination combination = 212 CombinedVibration.startSequential(); 213 for (CombinedVibration effect : effects) { 214 combination.addNext(transformCombinedEffect(effect, fn)); 215 } 216 return combination.combine(); 217 } else { 218 // Unknown combination, return same effect. 219 return combinedEffect; 220 } 221 } 222 223 /** Return true is current status is different from {@link Status#RUNNING}. */ hasEnded()224 public boolean hasEnded() { 225 return mStatus != Status.RUNNING; 226 } 227 228 /** Return true is effect is a repeating vibration. */ isRepeating()229 public boolean isRepeating() { 230 return mEffect.getDuration() == Long.MAX_VALUE; 231 } 232 233 /** Return the effect that should be played by this vibration. */ 234 @Nullable getEffect()235 public CombinedVibration getEffect() { 236 return mEffect; 237 } 238 239 /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */ getDebugInfo()240 public Vibration.DebugInfo getDebugInfo() { 241 return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0, 242 attrs, uid, displayId, opPkg, reason); 243 } 244 245 /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ getStatsInfo(long completionUptimeMillis)246 public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { 247 int vibrationType = isRepeating() 248 ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED 249 : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; 250 return new VibrationStats.StatsInfo( 251 uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis); 252 } 253 254 /** Immutable info passed as a signal to end a vibration. */ 255 static final class EndInfo { 256 /** The {@link Status} to be set to the vibration when it ends with this info. */ 257 @NonNull 258 public final Status status; 259 /** The UID that triggered the vibration that ended this, or -1 if undefined. */ 260 public final int endedByUid; 261 /** The VibrationAttributes.USAGE_* of the vibration that ended this, or -1 if undefined. */ 262 public final int endedByUsage; 263 EndInfo(@onNull Vibration.Status status)264 EndInfo(@NonNull Vibration.Status status) { 265 this(status, /* endedByUid= */ -1, /* endedByUsage= */ -1); 266 } 267 EndInfo(@onNull Vibration.Status status, int endedByUid, int endedByUsage)268 EndInfo(@NonNull Vibration.Status status, int endedByUid, int endedByUsage) { 269 this.status = status; 270 this.endedByUid = endedByUid; 271 this.endedByUsage = endedByUsage; 272 } 273 274 @Override equals(Object o)275 public boolean equals(Object o) { 276 if (this == o) return true; 277 if (!(o instanceof EndInfo)) return false; 278 EndInfo that = (EndInfo) o; 279 return endedByUid == that.endedByUid 280 && endedByUsage == that.endedByUsage 281 && status == that.status; 282 } 283 284 @Override hashCode()285 public int hashCode() { 286 return Objects.hash(status, endedByUid, endedByUsage); 287 } 288 289 @Override toString()290 public String toString() { 291 return "EndInfo{" 292 + "status=" + status 293 + ", endedByUid=" + endedByUid 294 + ", endedByUsage=" + endedByUsage 295 + '}'; 296 } 297 } 298 299 /** Debug information about vibrations. */ 300 static final class DebugInfo { 301 private final long mCreateTime; 302 private final long mStartTime; 303 private final long mEndTime; 304 private final long mDurationMs; 305 private final CombinedVibration mEffect; 306 private final CombinedVibration mOriginalEffect; 307 private final float mScale; 308 private final VibrationAttributes mAttrs; 309 private final int mUid; 310 private final int mDisplayId; 311 private final String mOpPkg; 312 private final String mReason; 313 private final Status mStatus; 314 DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect, @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason)315 DebugInfo(Status status, VibrationStats stats, @Nullable CombinedVibration effect, 316 @Nullable CombinedVibration originalEffect, float scale, VibrationAttributes attrs, 317 int uid, int displayId, String opPkg, String reason) { 318 mCreateTime = stats.getCreateTimeDebug(); 319 mStartTime = stats.getStartTimeDebug(); 320 mEndTime = stats.getEndTimeDebug(); 321 mDurationMs = stats.getDurationDebug(); 322 mEffect = effect; 323 mOriginalEffect = originalEffect; 324 mScale = scale; 325 mAttrs = attrs; 326 mUid = uid; 327 mDisplayId = displayId; 328 mOpPkg = opPkg; 329 mReason = reason; 330 mStatus = status; 331 } 332 333 @Override toString()334 public String toString() { 335 return new StringBuilder() 336 .append("createTime: ") 337 .append(DEBUG_DATE_FORMAT.format(new Date(mCreateTime))) 338 .append(", startTime: ") 339 .append(DEBUG_DATE_FORMAT.format(new Date(mStartTime))) 340 .append(", endTime: ") 341 .append(mEndTime == 0 ? null 342 : DEBUG_DATE_FORMAT.format(new Date(mEndTime))) 343 .append(", durationMs: ") 344 .append(mDurationMs) 345 .append(", status: ") 346 .append(mStatus.name().toLowerCase()) 347 .append(", effect: ") 348 .append(mEffect) 349 .append(", originalEffect: ") 350 .append(mOriginalEffect) 351 .append(", scale: ") 352 .append(String.format("%.2f", mScale)) 353 .append(", attrs: ") 354 .append(mAttrs) 355 .append(", uid: ") 356 .append(mUid) 357 .append(", displayId: ") 358 .append(mDisplayId) 359 .append(", opPkg: ") 360 .append(mOpPkg) 361 .append(", reason: ") 362 .append(mReason) 363 .toString(); 364 } 365 366 /** Write this info into given {@code fieldId} on {@link ProtoOutputStream}. */ dumpProto(ProtoOutputStream proto, long fieldId)367 public void dumpProto(ProtoOutputStream proto, long fieldId) { 368 final long token = proto.start(fieldId); 369 proto.write(VibrationProto.START_TIME, mStartTime); 370 proto.write(VibrationProto.END_TIME, mEndTime); 371 proto.write(VibrationProto.DURATION_MS, mDurationMs); 372 proto.write(VibrationProto.STATUS, mStatus.ordinal()); 373 374 final long attrsToken = proto.start(VibrationProto.ATTRIBUTES); 375 proto.write(VibrationAttributesProto.USAGE, mAttrs.getUsage()); 376 proto.write(VibrationAttributesProto.AUDIO_USAGE, mAttrs.getAudioUsage()); 377 proto.write(VibrationAttributesProto.FLAGS, mAttrs.getFlags()); 378 proto.end(attrsToken); 379 380 if (mEffect != null) { 381 dumpEffect(proto, VibrationProto.EFFECT, mEffect); 382 } 383 if (mOriginalEffect != null) { 384 dumpEffect(proto, VibrationProto.ORIGINAL_EFFECT, mOriginalEffect); 385 } 386 387 proto.end(token); 388 } 389 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration effect)390 private void dumpEffect( 391 ProtoOutputStream proto, long fieldId, CombinedVibration effect) { 392 dumpEffect(proto, fieldId, 393 (CombinedVibration.Sequential) CombinedVibration.startSequential() 394 .addNext(effect) 395 .combine()); 396 } 397 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect)398 private void dumpEffect( 399 ProtoOutputStream proto, long fieldId, CombinedVibration.Sequential effect) { 400 final long token = proto.start(fieldId); 401 for (int i = 0; i < effect.getEffects().size(); i++) { 402 CombinedVibration nestedEffect = effect.getEffects().get(i); 403 if (nestedEffect instanceof CombinedVibration.Mono) { 404 dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, 405 (CombinedVibration.Mono) nestedEffect); 406 } else if (nestedEffect instanceof CombinedVibration.Stereo) { 407 dumpEffect(proto, CombinedVibrationEffectProto.EFFECTS, 408 (CombinedVibration.Stereo) nestedEffect); 409 } 410 proto.write(CombinedVibrationEffectProto.DELAYS, effect.getDelays().get(i)); 411 } 412 proto.end(token); 413 } 414 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect)415 private void dumpEffect( 416 ProtoOutputStream proto, long fieldId, CombinedVibration.Mono effect) { 417 final long token = proto.start(fieldId); 418 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffect()); 419 proto.end(token); 420 } 421 dumpEffect( ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect)422 private void dumpEffect( 423 ProtoOutputStream proto, long fieldId, CombinedVibration.Stereo effect) { 424 final long token = proto.start(fieldId); 425 for (int i = 0; i < effect.getEffects().size(); i++) { 426 proto.write(SyncVibrationEffectProto.VIBRATOR_IDS, effect.getEffects().keyAt(i)); 427 dumpEffect(proto, SyncVibrationEffectProto.EFFECTS, effect.getEffects().valueAt(i)); 428 } 429 proto.end(token); 430 } 431 dumpEffect( ProtoOutputStream proto, long fieldId, VibrationEffect effect)432 private void dumpEffect( 433 ProtoOutputStream proto, long fieldId, VibrationEffect effect) { 434 final long token = proto.start(fieldId); 435 VibrationEffect.Composed composed = (VibrationEffect.Composed) effect; 436 for (VibrationEffectSegment segment : composed.getSegments()) { 437 dumpEffect(proto, VibrationEffectProto.SEGMENTS, segment); 438 } 439 proto.write(VibrationEffectProto.REPEAT, composed.getRepeatIndex()); 440 proto.end(token); 441 } 442 dumpEffect(ProtoOutputStream proto, long fieldId, VibrationEffectSegment segment)443 private void dumpEffect(ProtoOutputStream proto, long fieldId, 444 VibrationEffectSegment segment) { 445 final long token = proto.start(fieldId); 446 if (segment instanceof StepSegment) { 447 dumpEffect(proto, SegmentProto.STEP, (StepSegment) segment); 448 } else if (segment instanceof RampSegment) { 449 dumpEffect(proto, SegmentProto.RAMP, (RampSegment) segment); 450 } else if (segment instanceof PrebakedSegment) { 451 dumpEffect(proto, SegmentProto.PREBAKED, (PrebakedSegment) segment); 452 } else if (segment instanceof PrimitiveSegment) { 453 dumpEffect(proto, SegmentProto.PRIMITIVE, (PrimitiveSegment) segment); 454 } 455 proto.end(token); 456 } 457 dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment)458 private void dumpEffect(ProtoOutputStream proto, long fieldId, StepSegment segment) { 459 final long token = proto.start(fieldId); 460 proto.write(StepSegmentProto.DURATION, segment.getDuration()); 461 proto.write(StepSegmentProto.AMPLITUDE, segment.getAmplitude()); 462 proto.write(StepSegmentProto.FREQUENCY, segment.getFrequencyHz()); 463 proto.end(token); 464 } 465 dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment)466 private void dumpEffect(ProtoOutputStream proto, long fieldId, RampSegment segment) { 467 final long token = proto.start(fieldId); 468 proto.write(RampSegmentProto.DURATION, segment.getDuration()); 469 proto.write(RampSegmentProto.START_AMPLITUDE, segment.getStartAmplitude()); 470 proto.write(RampSegmentProto.END_AMPLITUDE, segment.getEndAmplitude()); 471 proto.write(RampSegmentProto.START_FREQUENCY, segment.getStartFrequencyHz()); 472 proto.write(RampSegmentProto.END_FREQUENCY, segment.getEndFrequencyHz()); 473 proto.end(token); 474 } 475 dumpEffect(ProtoOutputStream proto, long fieldId, PrebakedSegment segment)476 private void dumpEffect(ProtoOutputStream proto, long fieldId, 477 PrebakedSegment segment) { 478 final long token = proto.start(fieldId); 479 proto.write(PrebakedSegmentProto.EFFECT_ID, segment.getEffectId()); 480 proto.write(PrebakedSegmentProto.EFFECT_STRENGTH, segment.getEffectStrength()); 481 proto.write(PrebakedSegmentProto.FALLBACK, segment.shouldFallback()); 482 proto.end(token); 483 } 484 dumpEffect(ProtoOutputStream proto, long fieldId, PrimitiveSegment segment)485 private void dumpEffect(ProtoOutputStream proto, long fieldId, 486 PrimitiveSegment segment) { 487 final long token = proto.start(fieldId); 488 proto.write(PrimitiveSegmentProto.PRIMITIVE_ID, segment.getPrimitiveId()); 489 proto.write(PrimitiveSegmentProto.SCALE, segment.getScale()); 490 proto.write(PrimitiveSegmentProto.DELAY, segment.getDelay()); 491 proto.end(token); 492 } 493 } 494 495 } 496