diff options
11 files changed, 564 insertions, 400 deletions
diff --git a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java index b5a7fcb72982..e650c52b68b4 100644 --- a/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java +++ b/services/core/java/com/android/server/vibrator/ExternalVibrationSession.java @@ -56,24 +56,26 @@ final class ExternalVibrationSession extends Vibration } @Override + public long getCreateUptimeMillis() { + return stats.getCreateUptimeMillis(); + } + + @Override public CallerInfo getCallerInfo() { return callerInfo; } @Override - public VibrationSession.DebugInfo getDebugInfo() { - return new Vibration.DebugInfoImpl(getStatus(), stats, /* playedEffect= */ null, - /* originalEffect= */ null, mScale.scaleLevel, mScale.adaptiveHapticsScale, - callerInfo); + public IBinder getCallerToken() { + return mExternalVibration.getToken(); } @Override - public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { - return new VibrationStats.StatsInfo( - mExternalVibration.getUid(), - FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, - mExternalVibration.getVibrationAttributes().getUsage(), getStatus(), stats, - completionUptimeMillis); + public VibrationSession.DebugInfo getDebugInfo() { + return new Vibration.DebugInfoImpl(getStatus(), callerInfo, + FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__EXTERNAL, stats, + /* playedEffect= */ null, /* originalEffect= */ null, mScale.scaleLevel, + mScale.adaptiveHapticsScale); } @Override @@ -86,6 +88,12 @@ final class ExternalVibrationSession extends Vibration } @Override + public boolean wasEndRequested() { + // End request is immediate, so just check if vibration has already ended. + return hasEnded(); + } + + @Override public boolean linkToDeath(Runnable callback) { synchronized (mLock) { mBinderDeathCallback = callback; @@ -104,10 +112,12 @@ final class ExternalVibrationSession extends Vibration @Override public void binderDied() { + Runnable callback; synchronized (mLock) { - if (mBinderDeathCallback != null) { - mBinderDeathCallback.run(); - } + callback = mBinderDeathCallback; + } + if (callback != null) { + callback.run(); } } @@ -131,6 +141,16 @@ final class ExternalVibrationSession extends Vibration end(new EndInfo(status, endedBy)); } + @Override + public void notifyVibratorCallback(int vibratorId, long vibrationId) { + // ignored, external control does not expect callbacks from the vibrator + } + + @Override + public void notifySyncedVibratorsCallback(long vibrationId) { + // ignored, external control does not expect callbacks from the vibrator manager + } + boolean isHoldingSameVibration(ExternalVibration vib) { return mExternalVibration.equals(vib); } diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index ce9c47ba6ba4..fbcc856d0974 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -19,15 +19,16 @@ package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.CombinedVibration; -import android.os.IBinder; import android.os.VibrationAttributes; import android.os.VibrationEffect; +import android.os.vibrator.PrebakedSegment; +import android.os.vibrator.VibrationEffectSegment; import android.util.SparseArray; -import com.android.internal.util.FrameworkStatsLog; - +import java.util.List; import java.util.Objects; import java.util.concurrent.CountDownLatch; +import java.util.function.IntFunction; /** * Represents a vibration defined by a {@link CombinedVibration} that will be performed by @@ -36,7 +37,6 @@ import java.util.concurrent.CountDownLatch; final class HalVibration extends Vibration { public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>(); - public final IBinder callerToken; /** A {@link CountDownLatch} to enable waiting for completion. */ private final CountDownLatch mCompletionLatch = new CountDownLatch(1); @@ -56,10 +56,9 @@ final class HalVibration extends Vibration { private int mScaleLevel; private float mAdaptiveScale; - HalVibration(@NonNull IBinder callerToken, @NonNull CombinedVibration effect, - @NonNull VibrationSession.CallerInfo callerInfo) { + HalVibration(@NonNull VibrationSession.CallerInfo callerInfo, + @NonNull CombinedVibration effect) { super(callerInfo); - this.callerToken = callerToken; mOriginalEffect = effect; mEffectToPlay = effect; mScaleLevel = VibrationScaler.SCALE_NONE; @@ -87,11 +86,11 @@ final class HalVibration extends Vibration { } /** - * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported, - * which might be necessary for replacement in realtime. + * Add a fallback {@link VibrationEffect} to be played for each predefined effect id, which + * might be necessary for replacement in realtime. */ - public void addFallback(int effectId, VibrationEffect effect) { - mFallbacks.put(effectId, effect); + public void fillFallbacks(IntFunction<VibrationEffect> fallbackProvider) { + fillFallbacksForEffect(mEffectToPlay, fallbackProvider); } /** @@ -131,11 +130,6 @@ final class HalVibration extends Vibration { // No need to update fallback effects, they are already configured per device. } - @Override - public boolean isRepeating() { - return mOriginalEffect.getDuration() == Long.MAX_VALUE; - } - /** Return the effect that should be played by this vibration. */ public CombinedVibration getEffectToPlay() { return mEffectToPlay; @@ -146,20 +140,9 @@ final class HalVibration extends Vibration { // Clear the original effect if it's the same as the effect that was played, for simplicity CombinedVibration originalEffect = Objects.equals(mOriginalEffect, mEffectToPlay) ? null : mOriginalEffect; - return new Vibration.DebugInfoImpl(getStatus(), stats, mEffectToPlay, originalEffect, - mScaleLevel, mAdaptiveScale, callerInfo); - } - - @Override - public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) { - int vibrationType = mEffectToPlay.hasVendorEffects() - ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR - : isRepeating() - ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED - : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; - return new VibrationStats.StatsInfo( - callerInfo.uid, vibrationType, callerInfo.attrs.getUsage(), getStatus(), - stats, completionUptimeMillis); + return new Vibration.DebugInfoImpl(getStatus(), callerInfo, + VibrationStats.StatsInfo.findVibrationType(mEffectToPlay), stats, mEffectToPlay, + originalEffect, mScaleLevel, mAdaptiveScale); } /** @@ -174,6 +157,42 @@ final class HalVibration extends Vibration { return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet( VibrationAttributes.FLAG_PIPELINED_EFFECT) && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) - && !isRepeating(); + && (mOriginalEffect.getDuration() != Long.MAX_VALUE); + } + + private void fillFallbacksForEffect(CombinedVibration effect, + IntFunction<VibrationEffect> fallbackProvider) { + if (effect instanceof CombinedVibration.Mono) { + fillFallbacksForEffect(((CombinedVibration.Mono) effect).getEffect(), fallbackProvider); + } else if (effect instanceof CombinedVibration.Stereo) { + SparseArray<VibrationEffect> effects = + ((CombinedVibration.Stereo) effect).getEffects(); + for (int i = 0; i < effects.size(); i++) { + fillFallbacksForEffect(effects.valueAt(i), fallbackProvider); + } + } else if (effect instanceof CombinedVibration.Sequential) { + List<CombinedVibration> effects = + ((CombinedVibration.Sequential) effect).getEffects(); + for (int i = 0; i < effects.size(); i++) { + fillFallbacksForEffect(effects.get(i), fallbackProvider); + } + } + } + + private void fillFallbacksForEffect(VibrationEffect effect, + IntFunction<VibrationEffect> fallbackProvider) { + if (!(effect instanceof VibrationEffect.Composed composed)) { + return; + } + int segmentCount = composed.getSegments().size(); + for (int i = 0; i < segmentCount; i++) { + VibrationEffectSegment segment = composed.getSegments().get(i); + if ((segment instanceof PrebakedSegment prebaked) && prebaked.shouldFallback()) { + VibrationEffect fallback = fallbackProvider.apply(prebaked.getEffectId()); + if (fallback != null) { + mFallbacks.put(prebaked.getEffectId(), fallback); + } + } + } } } diff --git a/services/core/java/com/android/server/vibrator/SingleVibrationSession.java b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java new file mode 100644 index 000000000000..f80407d03e5c --- /dev/null +++ b/services/core/java/com/android/server/vibrator/SingleVibrationSession.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.vibrator; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.CombinedVibration; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import java.util.NoSuchElementException; + +/** + * A vibration session holding a single {@link CombinedVibration} request, performed by a + * {@link VibrationStepConductor}. + */ +final class SingleVibrationSession implements VibrationSession, IBinder.DeathRecipient { + private static final String TAG = "SingleVibrationSession"; + + private final Object mLock = new Object(); + private final IBinder mCallerToken; + private final HalVibration mVibration; + + @GuardedBy("mLock") + private VibrationStepConductor mConductor; + + @GuardedBy("mLock") + @Nullable + private Runnable mBinderDeathCallback; + + SingleVibrationSession(@NonNull IBinder callerToken, @NonNull CallerInfo callerInfo, + @NonNull CombinedVibration vibration) { + mCallerToken = callerToken; + mVibration = new HalVibration(callerInfo, vibration); + } + + public void setVibrationConductor(@Nullable VibrationStepConductor conductor) { + synchronized (mLock) { + mConductor = conductor; + } + } + + public HalVibration getVibration() { + return mVibration; + } + + @Override + public long getCreateUptimeMillis() { + return mVibration.stats.getCreateUptimeMillis(); + } + + @Override + public boolean isRepeating() { + return mVibration.getEffectToPlay().getDuration() == Long.MAX_VALUE; + } + + @Override + public CallerInfo getCallerInfo() { + return mVibration.callerInfo; + } + + @Override + public IBinder getCallerToken() { + return mCallerToken; + } + + @Override + public DebugInfo getDebugInfo() { + return mVibration.getDebugInfo(); + } + + @Override + public boolean wasEndRequested() { + if (mVibration.hasEnded()) { + return true; + } + synchronized (mLock) { + return mConductor != null && mConductor.wasNotifiedToCancel(); + } + } + + @Override + public void binderDied() { + Slog.d(TAG, "Binder died, cancelling vibration..."); + requestEnd(Status.CANCELLED_BINDER_DIED, /* endedBy= */ null, /* immediate= */ false); + Runnable callback; + synchronized (mLock) { + callback = mBinderDeathCallback; + } + if (callback != null) { + callback.run(); + } + } + + @Override + public boolean linkToDeath(@Nullable Runnable callback) { + synchronized (mLock) { + mBinderDeathCallback = callback; + } + try { + mCallerToken.linkToDeath(this, 0); + } catch (RemoteException e) { + Slog.e(TAG, "Error linking vibration to token death", e); + return false; + } + return true; + } + + @Override + public void unlinkToDeath() { + try { + mCallerToken.unlinkToDeath(this, 0); + } catch (NoSuchElementException e) { + Slog.wtf(TAG, "Failed to unlink vibration to token death", e); + } + synchronized (mLock) { + mBinderDeathCallback = null; + } + } + + @Override + public void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, + boolean immediate) { + synchronized (mLock) { + if (mConductor != null) { + mConductor.notifyCancelled(new Vibration.EndInfo(status, endedBy), immediate); + } else { + mVibration.end(new Vibration.EndInfo(status, endedBy)); + } + } + } + + @Override + public void notifyVibratorCallback(int vibratorId, long vibrationId) { + if (vibrationId != mVibration.id) { + return; + } + synchronized (mLock) { + if (mConductor != null) { + mConductor.notifyVibratorComplete(vibratorId); + } + } + } + + @Override + public void notifySyncedVibratorsCallback(long vibrationId) { + if (vibrationId != mVibration.id) { + return; + } + synchronized (mLock) { + if (mConductor != null) { + mConductor.notifySyncedVibrationComplete(); + } + } + } +} diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java index 21fd4ce0acd0..bb2a17c698ee 100644 --- a/services/core/java/com/android/server/vibrator/Vibration.java +++ b/services/core/java/com/android/server/vibrator/Vibration.java @@ -88,15 +88,9 @@ abstract class Vibration { stats.reportEnded(endInfo.endedBy); } - /** Return true if vibration is a repeating vibration. */ - abstract boolean isRepeating(); - /** Return {@link VibrationSession.DebugInfo} with read-only debug data about this vibration. */ abstract VibrationSession.DebugInfo getDebugInfo(); - /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */ - abstract VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis); - /** Immutable info passed as a signal to end a vibration. */ static final class EndInfo { /** The vibration status to be set when it ends with this info. */ @@ -146,35 +140,41 @@ abstract class Vibration { * potentially expensive or resource-linked objects, such as {@link IBinder}. */ static final class DebugInfoImpl implements VibrationSession.DebugInfo { - final VibrationSession.Status mStatus; - final long mCreateTime; - final VibrationSession.CallerInfo mCallerInfo; + private final VibrationSession.Status mStatus; + private final VibrationStats.StatsInfo mStatsInfo; + private final VibrationSession.CallerInfo mCallerInfo; @Nullable - final CombinedVibration mPlayedEffect; - - private final long mStartTime; - private final long mEndTime; - private final long mDurationMs; + private final CombinedVibration mPlayedEffect; @Nullable private final CombinedVibration mOriginalEffect; private final int mScaleLevel; private final float mAdaptiveScale; - DebugInfoImpl(VibrationSession.Status status, VibrationStats stats, - @Nullable CombinedVibration playedEffect, - @Nullable CombinedVibration originalEffect, int scaleLevel, - float adaptiveScale, @NonNull VibrationSession.CallerInfo callerInfo) { + private final long mCreateUptime; + private final long mCreateTime; + private final long mStartTime; + private final long mEndTime; + private final long mDurationMs; + + DebugInfoImpl(VibrationSession.Status status, + @NonNull VibrationSession.CallerInfo callerInfo, int vibrationType, + VibrationStats stats, @Nullable CombinedVibration playedEffect, + @Nullable CombinedVibration originalEffect, int scaleLevel, float adaptiveScale) { Objects.requireNonNull(callerInfo); - mCreateTime = stats.getCreateTimeDebug(); - mStartTime = stats.getStartTimeDebug(); - mEndTime = stats.getEndTimeDebug(); - mDurationMs = stats.getDurationDebug(); + mCallerInfo = callerInfo; + mStatsInfo = stats.toStatsInfo(callerInfo.uid, vibrationType, + callerInfo.attrs.getUsage(), status); mPlayedEffect = playedEffect; mOriginalEffect = originalEffect; mScaleLevel = scaleLevel; mAdaptiveScale = adaptiveScale; - mCallerInfo = callerInfo; mStatus = status; + + mCreateUptime = stats.getCreateUptimeMillis(); + mCreateTime = stats.getCreateTimeDebug(); + mStartTime = stats.getStartTimeDebug(); + mEndTime = stats.getEndTimeDebug(); + mDurationMs = stats.getDurationDebug(); } @Override @@ -184,7 +184,7 @@ abstract class Vibration { @Override public long getCreateUptimeMillis() { - return mCreateTime; + return mCreateUptime; } @Override @@ -216,6 +216,7 @@ abstract class Vibration { @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale); + statsLogger.writeVibrationReportedAsync(mStatsInfo); } @Override diff --git a/services/core/java/com/android/server/vibrator/VibrationSession.java b/services/core/java/com/android/server/vibrator/VibrationSession.java index 70477a26b759..4de8f78f7836 100644 --- a/services/core/java/com/android/server/vibrator/VibrationSession.java +++ b/services/core/java/com/android/server/vibrator/VibrationSession.java @@ -39,9 +39,18 @@ import java.util.Objects; */ interface VibrationSession { + /** Returns the session creation time from {@link android.os.SystemClock#uptimeMillis()}. */ + long getCreateUptimeMillis(); + + /** Return true if vibration session plays a repeating vibration. */ + boolean isRepeating(); + /** Returns data about the client app that triggered this vibration session. */ CallerInfo getCallerInfo(); + /** Returns the binder token from the client app attached to this vibration session. */ + IBinder getCallerToken(); + /** Returns debug data for logging and metric reports. */ DebugInfo getDebugInfo(); @@ -58,6 +67,19 @@ interface VibrationSession { /** Removes link to the app process death. */ void unlinkToDeath(); + /** Returns true if this session was requested to end by {@link #requestEnd}. */ + boolean wasEndRequested(); + + /** + * Request the end of this session, which might be acted upon asynchronously. + * + * <p>This is the same as {@link #requestEnd(Status, CallerInfo, boolean)}, with no + * {@link CallerInfo} and with {@code immediate} flag set to false. + */ + default void requestEnd(@NonNull Status status) { + requestEnd(status, /* endedBy= */ null, /* immediate= */ false); + } + /** * Notify the session end was requested, which might be acted upon asynchronously. * @@ -71,6 +93,25 @@ interface VibrationSession { void requestEnd(@NonNull Status status, @Nullable CallerInfo endedBy, boolean immediate); /** + * Notify a vibrator has completed the last command during the playback of given vibration. + * + * <p>This will be called by the vibrator hardware callback indicating the last vibrate call is + * complete (e.g. on(), perform(), compose()). This does not mean the vibration is complete, + * since its playback might have one or more interactions with the vibrator hardware. + */ + void notifyVibratorCallback(int vibratorId, long vibrationId); + + /** + * Notify all synced vibrators have completed the last synchronized command during the playback + * of given vibration. + * + * <p>This will be called by the vibrator manager hardware callback indicating the last + * synchronized vibrate call is complete. This does not mean the vibration is complete, since + * its playback might have one or more interactions with the vibrator hardware. + */ + void notifySyncedVibratorsCallback(long vibrationId); + + /** * Session status with reference to values from vibratormanagerservice.proto for logging. */ enum Status { diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java index fc0c6e7bf05e..637a5a180063 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStats.java +++ b/services/core/java/com/android/server/vibrator/VibrationStats.java @@ -18,6 +18,7 @@ package com.android.server.vibrator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.os.CombinedVibration; import android.os.SystemClock; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.PrimitiveSegment; @@ -37,11 +38,11 @@ final class VibrationStats { // vibrate request. // - Start: time a vibration started to play, which is closer to the time that the // VibrationEffect started playing the very first segment. - // - End: time a vibration ended, even if it never started to play. This can be as soon as the - // vibrator HAL reports it has finished the last command, or before it has even started - // when the vibration is ignored or cancelled. - // Create and end times set by VibratorManagerService only, guarded by its lock. - // Start times set by VibrationThread only (single-threaded). + // - End: time a vibration ended with a status, even if it never started to play. This can be as + // soon as the vibrator HAL reports it has finished the last command, or before it has + // even started when the vibration is ignored or cancelled. + // Created and ended times set by VibratorManagerService only, guarded by its lock. + // Start time set by VibrationThread only (single-threaded). private long mCreateUptimeMillis; private long mStartUptimeMillis; private long mEndUptimeMillis; @@ -97,6 +98,10 @@ final class VibrationStats { mInterruptedUsage = -1; } + StatsInfo toStatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status) { + return new VibrationStats.StatsInfo(uid, vibrationType, usage, status, this); + } + long getCreateUptimeMillis() { return mCreateUptimeMillis; } @@ -300,7 +305,7 @@ final class VibrationStats { * {@link com.android.internal.util.FrameworkStatsLog} as a * {@link com.android.internal.util.FrameworkStatsLog#VIBRATION_REPORTED}. */ - static final class StatsInfo { + public static final class StatsInfo { public final int uid; public final int vibrationType; public final int usage; @@ -331,7 +336,7 @@ final class VibrationStats { private boolean mIsWritten; StatsInfo(int uid, int vibrationType, int usage, VibrationSession.Status status, - VibrationStats stats, long completionUptimeMillis) { + VibrationStats stats) { this.uid = uid; this.vibrationType = vibrationType; this.usage = usage; @@ -342,6 +347,9 @@ final class VibrationStats { interruptedUsage = stats.mInterruptedUsage; repeatCount = stats.mRepeatCount; + // Consider this vibration is being completed now. + long completionUptimeMillis = SystemClock.uptimeMillis(); + // This duration goes from the time this object was created until the time it was // completed. We can use latencies to detect the times between first and last // interaction with vibrator. @@ -419,5 +427,25 @@ final class VibrationStats { } return res; } + + /** + * Returns the vibration type value from {@code ReportedVibration} that best represents this + * {@link CombinedVibration}. + * + * <p>This does not include external vibrations, as those are not represented by a single + * vibration effect. + */ + public static int findVibrationType(@Nullable CombinedVibration effect) { + if (effect == null) { + return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; + } + if (effect.hasVendorEffects()) { + return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__VENDOR; + } + if (effect.getDuration() == Long.MAX_VALUE) { + return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED; + } + return FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE; + } } } diff --git a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java index 1d52e3c87d17..4bb0c16d9655 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStepConductor.java +++ b/services/core/java/com/android/server/vibrator/VibrationStepConductor.java @@ -20,8 +20,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Build; import android.os.CombinedVibration; -import android.os.IBinder; -import android.os.RemoteException; import android.os.VibrationEffect; import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; @@ -39,7 +37,6 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.NoSuchElementException; import java.util.PriorityQueue; import java.util.Queue; import java.util.concurrent.CancellationException; @@ -55,7 +52,7 @@ import java.util.concurrent.TimeoutException; * VibrationThread. The only thread-safe methods for calling from other threads are the "notify" * methods (which should never be used from the VibrationThread thread). */ -final class VibrationStepConductor implements IBinder.DeathRecipient { +final class VibrationStepConductor { private static final boolean DEBUG = VibrationThread.DEBUG; private static final String TAG = VibrationThread.TAG; @@ -346,42 +343,6 @@ final class VibrationStepConductor implements IBinder.DeathRecipient { } /** - * Binder death notification. VibrationThread registers this when it's running a conductor. - * Note that cancellation could theoretically happen immediately, before the conductor has - * started, but in this case it will be processed in the first signals loop. - */ - @Override - public void binderDied() { - if (DEBUG) { - Slog.d(TAG, "Binder died, cancelling vibration..."); - } - notifyCancelled(new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), - /* immediate= */ false); - } - - /** - * Returns true if successfully linked this conductor to the death of the binder that requested - * the vibration. - */ - public boolean linkToDeath() { - try { - mVibration.callerToken.linkToDeath(this, 0); - } catch (RemoteException e) { - Slog.e(TAG, "Error linking vibration to token death", e); - return false; - } - return true; - } - - public void unlinkToDeath() { - try { - mVibration.callerToken.unlinkToDeath(this, 0); - } catch (NoSuchElementException e) { - Slog.wtf(TAG, "Failed to unlink vibration to token death", e); - } - } - - /** * Notify the execution that cancellation is requested. This will be acted upon * asynchronously in the VibrationThread. * diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 07473d10b217..9b7bdece69f9 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -51,7 +51,6 @@ import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; -import android.os.SystemClock; import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; @@ -159,9 +158,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") private final SparseArray<AlwaysOnVibration> mAlwaysOnEffects = new SparseArray<>(); @GuardedBy("mLock") - private VibrationStepConductor mCurrentVibration; + private SingleVibrationSession mCurrentVibration; @GuardedBy("mLock") - private VibrationStepConductor mNextVibration; + private SingleVibrationSession mNextVibration; @GuardedBy("mLock") private ExternalVibrationSession mCurrentExternalVibration; @GuardedBy("mLock") @@ -188,24 +187,20 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // When the system is entering a non-interactive state, we want to cancel // vibrations in case a misbehaving app has abandoned them. if (shouldCancelOnScreenOffLocked(mNextVibration)) { - clearNextVibrationLocked(new Vibration.EndInfo( - Status.CANCELLED_BY_SCREEN_OFF)); + clearNextVibrationLocked(Status.CANCELLED_BY_SCREEN_OFF); } if (shouldCancelOnScreenOffLocked(mCurrentVibration)) { - mCurrentVibration.notifyCancelled(new Vibration.EndInfo( - Status.CANCELLED_BY_SCREEN_OFF), /* immediate= */ false); + mCurrentVibration.requestEnd(Status.CANCELLED_BY_SCREEN_OFF); } } } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers() && intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) { synchronized (mLock) { if (shouldCancelOnFgUserRequest(mNextVibration)) { - clearNextVibrationLocked(new Vibration.EndInfo( - Status.CANCELLED_BY_FOREGROUND_USER)); + clearNextVibrationLocked(Status.CANCELLED_BY_FOREGROUND_USER); } if (shouldCancelOnFgUserRequest(mCurrentVibration)) { - mCurrentVibration.notifyCancelled(new Vibration.EndInfo( - Status.CANCELLED_BY_FOREGROUND_USER), /* immediate= */ false); + mCurrentVibration.requestEnd(Status.CANCELLED_BY_FOREGROUND_USER); } } } @@ -222,12 +217,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } synchronized (mLock) { if (shouldCancelAppOpModeChangedLocked(mNextVibration)) { - clearNextVibrationLocked(new Vibration.EndInfo( - Status.CANCELLED_BY_APP_OPS)); + clearNextVibrationLocked(Status.CANCELLED_BY_APP_OPS); } if (shouldCancelAppOpModeChangedLocked(mCurrentVibration)) { - mCurrentVibration.notifyCancelled(new Vibration.EndInfo( - Status.CANCELLED_BY_APP_OPS), /* immediate= */ false); + mCurrentVibration.requestEnd(Status.CANCELLED_BY_APP_OPS); } } } @@ -602,8 +595,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return null; } // Create Vibration.Stats as close to the received request as possible, for tracking. - HalVibration vib = new HalVibration(token, effect, callerInfo); - fillVibrationFallbacks(vib, effect); + SingleVibrationSession session = new SingleVibrationSession(token, callerInfo, effect); + HalVibration vib = session.getVibration(); + vib.fillFallbacks(mVibrationSettings::getFallbackEffect); if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) { // Force update of user settings before checking if this vibration effect should @@ -617,21 +611,26 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } // Check if user settings or DnD is set to ignore this vibration. - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo); + Status ignoreStatus = shouldIgnoreVibrationLocked(callerInfo); + CallerInfo ignoredBy = null; // Check if ongoing vibration is more important than this vibration. - if (vibrationEndInfo == null) { - vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(vib); + if (ignoreStatus == null) { + Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(session); + if (vibrationEndInfo != null) { + ignoreStatus = vibrationEndInfo.status; + ignoredBy = vibrationEndInfo.endedBy; + } } // If not ignored so far then try to start this vibration. - if (vibrationEndInfo == null) { + if (ignoreStatus == null) { final long ident = Binder.clearCallingIdentity(); try { if (mCurrentExternalVibration != null) { vib.stats.reportInterruptedAnotherVibration( mCurrentExternalVibration.getCallerInfo()); - endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, vib.callerInfo, + endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo, /* continueExternalControl= */ false); } else if (mCurrentVibration != null) { if (mCurrentVibration.getVibration().canPipelineWith(vib)) { @@ -645,21 +644,19 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } else { vib.stats.reportInterruptedAnotherVibration( mCurrentVibration.getVibration().callerInfo); - mCurrentVibration.notifyCancelled( - new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED, - vib.callerInfo), + mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, callerInfo, /* immediate= */ false); } } - vibrationEndInfo = startVibrationLocked(vib); + ignoreStatus = startVibrationLocked(session); } finally { Binder.restoreCallingIdentity(ident); } } // Ignored or failed to start the vibration, end it and report metrics right away. - if (vibrationEndInfo != null) { - endVibrationLocked(vib, vibrationEndInfo); + if (ignoreStatus != null) { + endVibrationLocked(session, ignoreStatus, ignoredBy); } return vib; } @@ -677,27 +674,18 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "Canceling vibration"); } - Vibration.EndInfo cancelledByUserInfo = - new Vibration.EndInfo(Status.CANCELLED_BY_USER); final long ident = Binder.clearCallingIdentity(); try { - if (mNextVibration != null - && shouldCancelVibration(mNextVibration.getVibration(), - usageFilter, token)) { - clearNextVibrationLocked(cancelledByUserInfo); + if (shouldCancelVibration(mNextVibration, usageFilter, token)) { + clearNextVibrationLocked(Status.CANCELLED_BY_USER); } - if (mCurrentVibration != null - && shouldCancelVibration(mCurrentVibration.getVibration(), - usageFilter, token)) { - mCurrentVibration.notifyCancelled( - cancelledByUserInfo, /* immediate= */false); + if (shouldCancelVibration(mCurrentVibration, usageFilter, token)) { + mCurrentVibration.requestEnd(Status.CANCELLED_BY_USER); } - if (mCurrentExternalVibration != null - && shouldCancelVibration( - mCurrentExternalVibration.getCallerInfo().attrs, - usageFilter)) { - endExternalVibrateLocked(cancelledByUserInfo.status, - cancelledByUserInfo.endedBy, /* continueExternalControl= */ false); + // TODO(b/370948466): investigate why token is not checked here and fix it. + if (shouldCancelVibration(mCurrentExternalVibration, usageFilter, null)) { + endExternalVibrateLocked(Status.CANCELLED_BY_USER, + /* endedBy= */ null, /* continueExternalControl= */ false); } } finally { Binder.restoreCallingIdentity(ident); @@ -842,18 +830,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { return; } - HalVibration vib = mCurrentVibration.getVibration(); - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo); - - if (inputDevicesChanged || (vibrationEndInfo != null)) { + Status ignoreStatus = shouldIgnoreVibrationLocked(mCurrentVibration.getCallerInfo()); + if (inputDevicesChanged || (ignoreStatus != null)) { if (DEBUG) { Slog.d(TAG, "Canceling vibration because settings changed: " - + (inputDevicesChanged ? "input devices changed" - : vibrationEndInfo.status)); + + (inputDevicesChanged ? "input devices changed" : ignoreStatus)); } - mCurrentVibration.notifyCancelled( - new Vibration.EndInfo(Status.CANCELLED_BY_SETTINGS_UPDATE), - /* immediate= */ false); + mCurrentVibration.requestEnd(Status.CANCELLED_BY_SETTINGS_UPDATE); } } } @@ -873,8 +856,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (vibrator == null) { continue; } - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked(vib.callerInfo); - if (vibrationEndInfo == null) { + Status ignoreStatus = shouldIgnoreVibrationLocked(vib.callerInfo); + if (ignoreStatus == null) { effect = mVibrationScaler.scale(effect, vib.callerInfo.attrs.getUsage()); } else { // Vibration should not run, use null effect to remove registered effect. @@ -886,25 +869,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable - private Vibration.EndInfo startVibrationLocked(HalVibration vib) { + private Status startVibrationLocked(SingleVibrationSession session) { Trace.traceBegin(TRACE_TAG_VIBRATOR, "startVibrationLocked"); try { if (mInputDeviceDelegate.isAvailable()) { - return startVibrationOnInputDevicesLocked(vib); + return startVibrationOnInputDevicesLocked(session.getVibration()); } - - VibrationStepConductor conductor = createVibrationStepConductor(vib); - if (mCurrentVibration == null) { - return startVibrationOnThreadLocked(conductor); + return startVibrationOnThreadLocked(session); } // If there's already a vibration queued (waiting for the previous one to finish // cancelling), end it cleanly and replace it with the new one. // Note that we don't consider pipelining here, because new pipelined ones should // replace pending non-executing pipelined ones anyway. - clearNextVibrationLocked( - new Vibration.EndInfo(Status.IGNORED_SUPERSEDED, vib.callerInfo)); - mNextVibration = conductor; + clearNextVibrationLocked(Status.IGNORED_SUPERSEDED, session.getCallerInfo()); + mNextVibration = session; return null; } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); @@ -913,50 +892,45 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @GuardedBy("mLock") @Nullable - private Vibration.EndInfo startVibrationOnThreadLocked(VibrationStepConductor conductor) { - HalVibration vib = conductor.getVibration(); - int mode = startAppOpModeLocked(vib.callerInfo); + private Status startVibrationOnThreadLocked(SingleVibrationSession session) { + VibrationStepConductor conductor = createVibrationStepConductor(session.getVibration()); + session.setVibrationConductor(conductor); + int mode = startAppOpModeLocked(session.getCallerInfo()); switch (mode) { case AppOpsManager.MODE_ALLOWED: Trace.asyncTraceBegin(TRACE_TAG_VIBRATOR, "vibration", 0); // Make sure mCurrentVibration is set while triggering the VibrationThread. - mCurrentVibration = conductor; - if (!mCurrentVibration.linkToDeath()) { + mCurrentVibration = session; + if (!mCurrentVibration.linkToDeath(null)) { // Shouldn't happen. The method call already logs a wtf. mCurrentVibration = null; // Aborted. - return new Vibration.EndInfo(Status.IGNORED_ERROR_TOKEN); + return Status.IGNORED_ERROR_TOKEN; } - if (!mVibrationThread.runVibrationOnVibrationThread(mCurrentVibration)) { + if (!mVibrationThread.runVibrationOnVibrationThread(conductor)) { // Shouldn't happen. The method call already logs a wtf. + mCurrentVibration.setVibrationConductor(null); mCurrentVibration = null; // Aborted. - return new Vibration.EndInfo(Status.IGNORED_ERROR_SCHEDULING); + return Status.IGNORED_ERROR_SCHEDULING; } return null; case AppOpsManager.MODE_ERRORED: Slog.w(TAG, "Start AppOpsManager operation errored for uid " - + vib.callerInfo.uid); - return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS); + + session.getCallerInfo().uid); + return Status.IGNORED_ERROR_APP_OPS; default: - return new Vibration.EndInfo(Status.IGNORED_APP_OPS); + return Status.IGNORED_APP_OPS; } } @GuardedBy("mLock") - private void endVibrationLocked(Vibration vib, Status status) { - endVibrationLocked(vib, new Vibration.EndInfo(status)); - } - - @GuardedBy("mLock") - private void endVibrationLocked(Vibration vib, Vibration.EndInfo vibrationEndInfo) { - vib.end(vibrationEndInfo); - reportEndedVibrationLocked(vib); + private void endVibrationLocked(VibrationSession session, Status status) { + endVibrationLocked(session, status, /* endedBy= */ null); } @GuardedBy("mLock") - private void reportEndedVibrationLocked(Vibration vib) { - logAndRecordVibration(vib.getDebugInfo()); - mFrameworkStatsLogger.writeVibrationReportedAsync( - vib.getStatsInfo(/* completionUptimeMillis= */ SystemClock.uptimeMillis())); + private void endVibrationLocked(VibrationSession session, Status status, CallerInfo endedBy) { + session.requestEnd(status, endedBy, /* immediate= */ false); + logAndRecordVibration(session.getDebugInfo()); } private VibrationStepConductor createVibrationStepConductor(HalVibration vib) { @@ -975,12 +949,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mFrameworkStatsLogger, requestVibrationParamsFuture, mVibrationThreadCallbacks); } - private Vibration.EndInfo startVibrationOnInputDevicesLocked(HalVibration vib) { + private Status startVibrationOnInputDevicesLocked(HalVibration vib) { // Scale resolves the default amplitudes from the effect before scaling them. vib.scaleEffects(mVibrationScaler); mInputDeviceDelegate.vibrateIfAvailable(vib.callerInfo, vib.getEffectToPlay()); - - return new Vibration.EndInfo(Status.FORWARDED_TO_INPUT_DEVICES); + return Status.FORWARDED_TO_INPUT_DEVICES; } private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg, @@ -994,9 +967,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect, CallerInfo callerInfo, Status status) { logAndRecordVibration( - new Vibration.DebugInfoImpl(status, new VibrationStats(), + new Vibration.DebugInfoImpl(status, callerInfo, + VibrationStats.StatsInfo.findVibrationType(effect), new VibrationStats(), effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE, - VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo)); + VibrationScaler.ADAPTIVE_SCALE_NONE)); } private void logAndRecordVibration(DebugInfo info) { @@ -1050,39 +1024,25 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } - @GuardedBy("mLock") - private void reportFinishedVibrationLocked() { - Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); - mCurrentVibration.unlinkToDeath(); - HalVibration vib = mCurrentVibration.getVibration(); - if (DEBUG) { - Slog.d(TAG, "Reporting vibration " + vib.id + " finished with " + vib.getStatus()); - } - finishAppOpModeLocked(vib.callerInfo); - reportEndedVibrationLocked(vib); - } - private void onSyncedVibrationComplete(long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null - && mCurrentVibration.getVibration().id == vibrationId) { + if (mCurrentVibration != null) { if (DEBUG) { Slog.d(TAG, "Synced vibration " + vibrationId + " complete, notifying thread"); } - mCurrentVibration.notifySyncedVibrationComplete(); + mCurrentVibration.notifySyncedVibratorsCallback(vibrationId); } } } private void onVibrationComplete(int vibratorId, long vibrationId) { synchronized (mLock) { - if (mCurrentVibration != null - && mCurrentVibration.getVibration().id == vibrationId) { + if (mCurrentVibration != null) { if (DEBUG) { Slog.d(TAG, "Vibration " + vibrationId + " on vibrator " + vibratorId + " complete, notifying thread"); } - mCurrentVibration.notifyVibratorComplete(vibratorId); + mCurrentVibration.notifyVibratorCallback(vibratorId, vibrationId); } } } @@ -1094,14 +1054,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") @Nullable - private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(Vibration vib) { + private Vibration.EndInfo shouldIgnoreVibrationForOngoingLocked(VibrationSession session) { if (mCurrentExternalVibration != null) { - return shouldIgnoreVibrationForOngoing(vib, mCurrentExternalVibration); + return shouldIgnoreVibrationForOngoing(session, mCurrentExternalVibration); } if (mNextVibration != null) { - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(vib, - mNextVibration.getVibration()); + Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationForOngoing(session, + mNextVibration); if (vibrationEndInfo != null) { // Next vibration has higher importance than the new one, so the new vibration // should be ignored. @@ -1110,14 +1070,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } if (mCurrentVibration != null) { - HalVibration currentVibration = mCurrentVibration.getVibration(); - if (currentVibration.hasEnded() || mCurrentVibration.wasNotifiedToCancel()) { - // Current vibration has ended or is cancelling, should not block incoming - // vibrations. + if (mCurrentVibration.wasEndRequested()) { + // Current session has ended or is cancelling, should not block incoming vibrations. return null; } - return shouldIgnoreVibrationForOngoing(vib, currentVibration); + return shouldIgnoreVibrationForOngoing(session, mCurrentVibration); } return null; @@ -1125,32 +1083,33 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** * Checks if the ongoing vibration has higher importance than the new one. If they have similar - * importance, then {@link Vibration#isRepeating()} is used as a tiebreaker. + * importance, then {@link VibrationSession#isRepeating()} is used as a tiebreaker. * * @return a Vibration.EndInfo if the vibration should be ignored, null otherwise. */ @Nullable private static Vibration.EndInfo shouldIgnoreVibrationForOngoing( - @NonNull Vibration newVibration, @NonNull Vibration ongoingVibration) { + @NonNull VibrationSession newSession, @NonNull VibrationSession ongoingSession) { - int newVibrationImportance = getVibrationImportance(newVibration); - int ongoingVibrationImportance = getVibrationImportance(ongoingVibration); + int newSessionImportance = getVibrationImportance(newSession); + int ongoingSessionImportance = getVibrationImportance(ongoingSession); - if (newVibrationImportance > ongoingVibrationImportance) { + if (newSessionImportance > ongoingSessionImportance) { // New vibration has higher importance and should not be ignored. return null; } - if (ongoingVibrationImportance > newVibrationImportance) { + if (ongoingSessionImportance > newSessionImportance) { // Existing vibration has higher importance and should not be cancelled. return new Vibration.EndInfo(Status.IGNORED_FOR_HIGHER_IMPORTANCE, - ongoingVibration.callerInfo); + ongoingSession.getCallerInfo()); } // Same importance, use repeating as a tiebreaker. - if (ongoingVibration.isRepeating() && !newVibration.isRepeating()) { + if (ongoingSession.isRepeating() && !newSession.isRepeating()) { // Ongoing vibration is repeating and new one is not, give priority to ongoing - return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, ongoingVibration.callerInfo); + return new Vibration.EndInfo(Status.IGNORED_FOR_ONGOING, + ongoingSession.getCallerInfo()); } // New vibration is repeating or this is a complete tie between them, // give priority to new vibration. @@ -1164,10 +1123,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { * @return a numeric representation for the vibration importance, larger values represent a * higher importance */ - private static int getVibrationImportance(Vibration vibration) { - int usage = vibration.callerInfo.attrs.getUsage(); + private static int getVibrationImportance(VibrationSession session) { + int usage = session.getCallerInfo().attrs.getUsage(); if (usage == VibrationAttributes.USAGE_UNKNOWN) { - if (vibration.isRepeating()) { + if (session.isRepeating()) { usage = VibrationAttributes.USAGE_RINGTONE; } else { usage = VibrationAttributes.USAGE_TOUCH; @@ -1201,10 +1160,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { */ @GuardedBy("mLock") @Nullable - private Vibration.EndInfo shouldIgnoreVibrationLocked(CallerInfo callerInfo) { + private Status shouldIgnoreVibrationLocked(CallerInfo callerInfo) { Status statusFromSettings = mVibrationSettings.shouldIgnoreVibration(callerInfo); if (statusFromSettings != null) { - return new Vibration.EndInfo(statusFromSettings); + return statusFromSettings; } int mode = checkAppOpModeLocked(callerInfo); @@ -1212,9 +1171,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (mode == AppOpsManager.MODE_ERRORED) { // We might be getting calls from within system_server, so we don't actually // want to throw a SecurityException here. - return new Vibration.EndInfo(Status.IGNORED_ERROR_APP_OPS); + return Status.IGNORED_ERROR_APP_OPS; } else { - return new Vibration.EndInfo(Status.IGNORED_APP_OPS); + return Status.IGNORED_APP_OPS; } } @@ -1239,32 +1198,29 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { /** * Return true if the vibration has the same token and usage belongs to given usage class. * - * @param vib The ongoing or pending vibration to be cancelled. + * @param session The ongoing or pending vibration session to be cancelled. * @param usageFilter The vibration usages to be cancelled, any bitwise combination of * VibrationAttributes.USAGE_* values. - * @param token The binder token to identify the vibration origin. Only vibrations + * @param tokenFilter The binder token to identify the vibration origin. Only vibrations * started with the same token can be cancelled with it. */ - private boolean shouldCancelVibration(HalVibration vib, int usageFilter, IBinder token) { - return (vib.callerToken == token) && shouldCancelVibration(vib.callerInfo.attrs, - usageFilter); - } - - /** - * Return true if the external vibration usage belongs to given usage class. - * - * @param attrs The attributes of an ongoing or pending vibration to be cancelled. - * @param usageFilter The vibration usages to be cancelled, any bitwise combination of - * VibrationAttributes.USAGE_* values. - */ - private boolean shouldCancelVibration(VibrationAttributes attrs, int usageFilter) { - if (attrs.getUsage() == VibrationAttributes.USAGE_UNKNOWN) { + private boolean shouldCancelVibration(@Nullable VibrationSession session, int usageFilter, + @Nullable IBinder tokenFilter) { + if (session == null) { + return false; + } + if ((tokenFilter != null) && (tokenFilter != session.getCallerToken())) { + // Vibration from a different app, this should not cancel it. + return false; + } + int usage = session.getCallerInfo().attrs.getUsage(); + if (usage == VibrationAttributes.USAGE_UNKNOWN) { // Special case, usage UNKNOWN would match all filters. Instead it should only match if // it's cancelling that usage specifically, or if cancelling all usages. return usageFilter == VibrationAttributes.USAGE_UNKNOWN || usageFilter == VibrationAttributes.USAGE_FILTER_MATCH_ALL; } - return (usageFilter & attrs.getUsage()) == attrs.getUsage(); + return (usageFilter & usage) == usage; } /** @@ -1340,45 +1296,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } /** - * Sets fallback effects to all prebaked ones in given combination of effects, based on {@link - * VibrationSettings#getFallbackEffect}. - */ - private void fillVibrationFallbacks(HalVibration vib, CombinedVibration effect) { - if (effect instanceof CombinedVibration.Mono) { - fillVibrationFallbacks(vib, ((CombinedVibration.Mono) effect).getEffect()); - } else if (effect instanceof CombinedVibration.Stereo) { - SparseArray<VibrationEffect> effects = - ((CombinedVibration.Stereo) effect).getEffects(); - for (int i = 0; i < effects.size(); i++) { - fillVibrationFallbacks(vib, effects.valueAt(i)); - } - } else if (effect instanceof CombinedVibration.Sequential) { - List<CombinedVibration> effects = - ((CombinedVibration.Sequential) effect).getEffects(); - for (int i = 0; i < effects.size(); i++) { - fillVibrationFallbacks(vib, effects.get(i)); - } - } - } - - private void fillVibrationFallbacks(HalVibration vib, VibrationEffect effect) { - if (!(effect instanceof VibrationEffect.Composed composed)) { - return; - } - int segmentCount = composed.getSegments().size(); - for (int i = 0; i < segmentCount; i++) { - VibrationEffectSegment segment = composed.getSegments().get(i); - if (segment instanceof PrebakedSegment prebaked) { - VibrationEffect fallback = mVibrationSettings.getFallbackEffect( - prebaked.getEffectId()); - if (prebaked.shouldFallback() && fallback != null) { - vib.addFallback(prebaked.getEffectId(), fallback); - } - } - } - } - - /** * Return new {@link VibrationAttributes} that only applies flags that this user has permissions * to use. */ @@ -1475,30 +1392,28 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } @GuardedBy("mLock") - private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationStepConductor conductor) { - if (conductor == null) { + private boolean shouldCancelOnScreenOffLocked(@Nullable VibrationSession session) { + if (session == null) { return false; } - HalVibration vib = conductor.getVibration(); - return mVibrationSettings.shouldCancelVibrationOnScreenOff(vib.callerInfo, - vib.stats.getCreateUptimeMillis()); + return mVibrationSettings.shouldCancelVibrationOnScreenOff(session.getCallerInfo(), + session.getCreateUptimeMillis()); } @GuardedBy("mLock") - private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationStepConductor conductor) { - if (conductor == null) { + private boolean shouldCancelAppOpModeChangedLocked(@Nullable VibrationSession session) { + if (session == null) { return false; } - return checkAppOpModeLocked(conductor.getVibration().callerInfo) - != AppOpsManager.MODE_ALLOWED; + return checkAppOpModeLocked(session.getCallerInfo()) != AppOpsManager.MODE_ALLOWED; } @GuardedBy("mLock") - private boolean shouldCancelOnFgUserRequest(@Nullable VibrationStepConductor conductor) { - if (conductor == null) { + private boolean shouldCancelOnFgUserRequest(@Nullable VibrationSession session) { + if (session == null) { return false; } - return conductor.getVibration().callerInfo.attrs.getUsageClass() == USAGE_CLASS_ALARM; + return session.getCallerInfo().attrs.getUsageClass() == USAGE_CLASS_ALARM; } @GuardedBy("mLock") @@ -1660,17 +1575,21 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } if (mCurrentVibration != null) { // This is when we consider the current vibration complete, report metrics. - reportFinishedVibrationLocked(); + if (DEBUG) { + Slog.d(TAG, "Reporting vibration " + vibrationId + " finished."); + } + mCurrentVibration.unlinkToDeath(); + finishAppOpModeLocked(mCurrentVibration.getCallerInfo()); + logAndRecordVibration(mCurrentVibration.getDebugInfo()); + Trace.asyncTraceEnd(Trace.TRACE_TAG_VIBRATOR, "vibration", 0); mCurrentVibration = null; } if (mNextVibration != null) { - VibrationStepConductor nextConductor = mNextVibration; + SingleVibrationSession nextVibration = mNextVibration; mNextVibration = null; - Vibration.EndInfo vibrationEndInfo = startVibrationOnThreadLocked( - nextConductor); - if (vibrationEndInfo != null) { - // Failed to start the vibration, end it and report metrics right away. - endVibrationLocked(nextConductor.getVibration(), vibrationEndInfo); + Status startErrorStatus = startVibrationOnThreadLocked(nextVibration); + if (startErrorStatus != null) { + endVibrationLocked(nextVibration, startErrorStatus); } } } @@ -1892,17 +1811,22 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mInfo.dump(proto, fieldId); } } + /** Clears mNextVibration if set, ending it cleanly */ + @GuardedBy("mLock") + private void clearNextVibrationLocked(Status status) { + clearNextVibrationLocked(status, /* endedBy= */ null); + } /** Clears mNextVibration if set, ending it cleanly */ @GuardedBy("mLock") - private void clearNextVibrationLocked(Vibration.EndInfo vibrationEndInfo) { + private void clearNextVibrationLocked(Status status, CallerInfo endedBy) { if (mNextVibration != null) { if (DEBUG) { - Slog.d(TAG, "Dropping pending vibration " + mNextVibration.getVibration().id - + " with end info: " + vibrationEndInfo); + Slog.d(TAG, "Dropping pending vibration from " + mNextVibration.getCallerInfo() + + " with status: " + status); } // Clearing next vibration before playing it, end it and report metrics right away. - endVibrationLocked(mNextVibration.getVibration(), vibrationEndInfo); + endVibrationLocked(mNextVibration, status, endedBy); mNextVibration = null; } } @@ -1927,7 +1851,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { setExternalControl(false, mCurrentExternalVibration.stats); } // The external control was turned off, end it and report metrics right away. - reportEndedVibrationLocked(mCurrentExternalVibration); + logAndRecordVibration(mCurrentExternalVibration.getDebugInfo()); mCurrentExternalVibration = null; } @@ -1987,16 +1911,16 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { try { // Create Vibration.Stats as close to the received request as possible, for // tracking. - ExternalVibrationSession externalVibration = new ExternalVibrationSession(vib); + ExternalVibrationSession session = new ExternalVibrationSession(vib); // Mute the request until we run all the checks and accept the vibration. - externalVibration.muteScale(); + session.muteScale(); boolean alreadyUnderExternalControl = false; boolean waitForCompletion = false; synchronized (mLock) { if (!hasExternalControlCapability()) { - endVibrationLocked(externalVibration, Status.IGNORED_UNSUPPORTED); - return externalVibration.getScale(); + endVibrationLocked(session, Status.IGNORED_UNSUPPORTED); + return session.getScale(); } if (ActivityManager.checkComponentPermission( @@ -2006,29 +1930,30 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { Slog.w(TAG, "pkg=" + vib.getPackage() + ", uid=" + vib.getUid() + " tried to play externally controlled vibration" + " without VIBRATE permission, ignoring."); - endVibrationLocked(externalVibration, Status.IGNORED_MISSING_PERMISSION); - return externalVibration.getScale(); + endVibrationLocked(session, Status.IGNORED_MISSING_PERMISSION); + return session.getScale(); } - Vibration.EndInfo vibrationEndInfo = shouldIgnoreVibrationLocked( - externalVibration.callerInfo); + Status ignoreStatus = shouldIgnoreVibrationLocked(session.callerInfo); + if (ignoreStatus != null) { + endVibrationLocked(session, ignoreStatus); + return session.getScale(); + } - if (vibrationEndInfo == null - && mCurrentExternalVibration != null + if (mCurrentExternalVibration != null && mCurrentExternalVibration.isHoldingSameVibration(vib)) { // We are already playing this external vibration, so we can return the same // scale calculated in the previous call to this method. return mCurrentExternalVibration.getScale(); } - if (vibrationEndInfo == null) { - // Check if ongoing vibration is more important than this vibration. - vibrationEndInfo = shouldIgnoreVibrationForOngoingLocked(externalVibration); - } - + // Check if ongoing vibration is more important than this vibration. + Vibration.EndInfo vibrationEndInfo = + shouldIgnoreVibrationForOngoingLocked(session); if (vibrationEndInfo != null) { - endVibrationLocked(externalVibration, vibrationEndInfo); - return externalVibration.getScale(); + endVibrationLocked(session, vibrationEndInfo.status, + vibrationEndInfo.endedBy); + return session.getScale(); } if (mCurrentExternalVibration == null) { @@ -2036,15 +1961,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // vibration that may be playing and ready the vibrator for external // control. if (mCurrentVibration != null) { - externalVibration.stats.reportInterruptedAnotherVibration( + session.stats.reportInterruptedAnotherVibration( mCurrentVibration.getVibration().callerInfo); - clearNextVibrationLocked( - new Vibration.EndInfo(Status.IGNORED_FOR_EXTERNAL, - externalVibration.callerInfo)); - mCurrentVibration.notifyCancelled( - new Vibration.EndInfo(Status.CANCELLED_SUPERSEDED, - externalVibration.callerInfo), - /* immediate= */ true); + clearNextVibrationLocked(Status.IGNORED_FOR_EXTERNAL, + session.callerInfo); + mCurrentVibration.requestEnd(Status.CANCELLED_SUPERSEDED, + session.callerInfo, /* immediate= */ true); waitForCompletion = true; } } else { @@ -2060,10 +1982,10 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { // as we would need to mute the old one still if it came from a different // controller. alreadyUnderExternalControl = true; - externalVibration.stats.reportInterruptedAnotherVibration( + session.stats.reportInterruptedAnotherVibration( mCurrentExternalVibration.getCallerInfo()); endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, - externalVibration.callerInfo, /* continueExternalControl= */ true); + session.callerInfo, /* continueExternalControl= */ true); } } // Wait for lock and interact with HAL to set external control outside main lock. @@ -2071,8 +1993,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (!mVibrationThread.waitForThreadIdle(VIBRATION_CANCEL_WAIT_MILLIS)) { Slog.e(TAG, "Timed out waiting for vibration to cancel"); synchronized (mLock) { - endVibrationLocked(externalVibration, Status.IGNORED_ERROR_CANCELLING); - return externalVibration.getScale(); + endVibrationLocked(session, Status.IGNORED_ERROR_CANCELLING); + return session.getScale(); } } } @@ -2080,7 +2002,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { if (DEBUG) { Slog.d(TAG, "Vibrator going under external control."); } - setExternalControl(true, externalVibration.stats); + setExternalControl(true, session.stats); } synchronized (mLock) { if (DEBUG) { @@ -2095,14 +2017,14 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrationSettings.update(); } - mCurrentExternalVibration = externalVibration; - externalVibration.linkToDeath(this::onExternalVibrationBinderDied); - externalVibration.scale(mVibrationScaler, attrs.getUsage()); + mCurrentExternalVibration = session; + session.linkToDeath(this::onExternalVibrationBinderDied); + session.scale(mVibrationScaler, attrs.getUsage()); // Vibrator will start receiving data from external channels after this point. // Report current time as the vibration start time, for debugging. - externalVibration.stats.reportStarted(); - return externalVibration.getScale(); + session.stats.reportStarted(); + return session.getScale(); } } finally { Trace.traceEnd(TRACE_TAG_VIBRATOR); diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java index e83a4b22d9bf..7536f5f54ba2 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java @@ -47,7 +47,6 @@ import android.hardware.vibrator.IVibrator; import android.hardware.vibrator.IVibratorManager; import android.os.CombinedVibration; import android.os.Handler; -import android.os.IBinder; import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; @@ -119,7 +118,6 @@ public class VibrationThreadTest { @Mock private PackageManagerInternal mPackageManagerInternalMock; @Mock private VibrationThread.VibratorManagerHooks mManagerHooks; @Mock private VibratorController.OnVibrationCompleteListener mControllerCallbacks; - @Mock private IBinder mVibrationToken; @Mock private VibrationConfig mVibrationConfigMock; @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock; @@ -668,7 +666,7 @@ public class VibrationThreadTest { VibrationEffect fallback = VibrationEffect.createOneShot(10, 100); HalVibration vibration = createVibration(CombinedVibration.createParallel( VibrationEffect.get(VibrationEffect.EFFECT_CLICK))); - vibration.addFallback(VibrationEffect.EFFECT_CLICK, fallback); + vibration.fillFallbacks(unused -> fallback); startThreadAndDispatcher(vibration); waitForCompletion(); @@ -848,7 +846,7 @@ public class VibrationThreadTest { .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f) .compose(); HalVibration vibration = createVibration(CombinedVibration.createParallel(effect)); - vibration.addFallback(VibrationEffect.EFFECT_TICK, fallback); + vibration.fillFallbacks(unused -> fallback); startThreadAndDispatcher(vibration); waitForCompletion(); @@ -954,7 +952,8 @@ public class VibrationThreadTest { assertTrue(mThread.isRunningVibrationId(vibration.id)); assertTrue(mControllers.get(VIBRATOR_ID).isVibrating()); - mVibrationConductor.binderDied(); + mVibrationConductor.notifyCancelled( + new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false); waitForCompletion(); assertFalse(mControllers.get(VIBRATOR_ID).isVibrating()); @@ -1575,7 +1574,8 @@ public class VibrationThreadTest { TEST_TIMEOUT_MILLIS)); assertTrue(mThread.isRunningVibrationId(vibration.id)); - mVibrationConductor.binderDied(); + mVibrationConductor.notifyCancelled( + new Vibration.EndInfo(Status.CANCELLED_BINDER_DIED), /* immediate= */ false); waitForCompletion(); verifyCallbacksTriggered(vibration, Status.CANCELLED_BINDER_DIED); @@ -1865,9 +1865,9 @@ public class VibrationThreadTest { VibrationAttributes attrs = new VibrationAttributes.Builder() .setUsage(usage) .build(); - HalVibration vib = new HalVibration(mVibrationToken, - CombinedVibration.createParallel(effect), - new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason")); + HalVibration vib = new HalVibration( + new CallerInfo(attrs, UID, DEVICE_ID, PACKAGE_NAME, "reason"), + CombinedVibration.createParallel(effect)); return startThreadAndDispatcher(vib, requestVibrationParamsFuture); } @@ -1903,8 +1903,8 @@ public class VibrationThreadTest { } private HalVibration createVibration(CombinedVibration effect) { - return new HalVibration(mVibrationToken, effect, - new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason")); + return new HalVibration(new CallerInfo(ATTRS, UID, DEVICE_ID, PACKAGE_NAME, "reason"), + effect); } private SparseArray<VibratorController> createVibratorControllers() { diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java index cd057b619000..1d1b4e271e19 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorFrameworkStatsLoggerTest.java @@ -115,6 +115,6 @@ public class VibratorFrameworkStatsLoggerTest { } private static VibrationStats.StatsInfo newEmptyStatsInfo() { - return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats(), 0L); + return new VibrationStats.StatsInfo(0, 0, 0, Status.FINISHED, new VibrationStats()); } } diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index 538c3fc2ddae..b7821623855c 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -1791,28 +1791,6 @@ public class VibratorManagerServiceTest { } @Test - public void performHapticFeedback_usesServiceAsToken() throws Exception { - VibratorManagerService service = createSystemReadyService(); - - HalVibration vibration = - performHapticFeedbackAndWaitUntilFinished( - service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true); - - assertTrue(vibration.callerToken == service); - } - - @Test - public void performHapticFeedbackForInputDevice_usesServiceAsToken() throws Exception { - VibratorManagerService service = createSystemReadyService(); - - HalVibration vibration = performHapticFeedbackForInputDeviceAndWaitUntilFinished( - service, HapticFeedbackConstants.SCROLL_TICK, /* inputDeviceId= */ 0, - InputDevice.SOURCE_ROTARY_ENCODER, /* always= */ true); - - assertTrue(vibration.callerToken == service); - } - - @Test @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception { // Deny permission to vibrate with vendor effects @@ -2147,6 +2125,27 @@ public class VibratorManagerServiceTest { } @Test + public void cancelVibrate_externalVibration_cancelWithDifferentToken() { + mockVibrators(1); + mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); + createSystemReadyService(); + + IBinder vibrationBinderToken = mock(IBinder.class); + ExternalVibration externalVibration = new ExternalVibration(UID, PACKAGE_NAME, + AUDIO_ALARM_ATTRS, + mock(IExternalVibrationController.class), vibrationBinderToken); + ExternalVibrationScale scale = mExternalVibratorService.onExternalVibrationStart( + externalVibration); + + IBinder cancelBinderToken = mock(IBinder.class); + mService.cancelVibrate(VibrationAttributes.USAGE_FILTER_MATCH_ALL, cancelBinderToken); + + assertNotEquals(ExternalVibrationScale.ScaleLevel.SCALE_MUTE, scale.scaleLevel); + assertEquals(Arrays.asList(false, true, false), + mVibratorProviders.get(1).getExternalControlStates()); + } + + @Test public void onExternalVibration_ignoreVibrationFromVirtualDevices() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); |