Loading services/core/java/com/android/server/vibrator/ExternalVibrationSession.java +33 −13 Original line number Diff line number Diff line Loading @@ -55,25 +55,27 @@ final class ExternalVibrationSession extends Vibration return mScale; } @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 Loading @@ -85,6 +87,12 @@ final class ExternalVibrationSession extends Vibration || usage == VibrationAttributes.USAGE_ALARM; } @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) { Loading @@ -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(); } } Loading @@ -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); } Loading services/core/java/com/android/server/vibrator/HalVibration.java +50 −31 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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; Loading @@ -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); } /** Loading @@ -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); } } } } } services/core/java/com/android/server/vibrator/SingleVibrationSession.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * 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(); } } } } services/core/java/com/android/server/vibrator/Vibration.java +25 −24 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading @@ -184,7 +184,7 @@ abstract class Vibration { @Override public long getCreateUptimeMillis() { return mCreateTime; return mCreateUptime; } @Override Loading Loading @@ -216,6 +216,7 @@ abstract class Vibration { @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale); statsLogger.writeVibrationReportedAsync(mStatsInfo); } @Override Loading services/core/java/com/android/server/vibrator/VibrationSession.java +41 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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. * Loading @@ -70,6 +92,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. */ Loading Loading
services/core/java/com/android/server/vibrator/ExternalVibrationSession.java +33 −13 Original line number Diff line number Diff line Loading @@ -55,25 +55,27 @@ final class ExternalVibrationSession extends Vibration return mScale; } @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 Loading @@ -85,6 +87,12 @@ final class ExternalVibrationSession extends Vibration || usage == VibrationAttributes.USAGE_ALARM; } @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) { Loading @@ -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(); } } Loading @@ -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); } Loading
services/core/java/com/android/server/vibrator/HalVibration.java +50 −31 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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); Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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; Loading @@ -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); } /** Loading @@ -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); } } } } }
services/core/java/com/android/server/vibrator/SingleVibrationSession.java 0 → 100644 +173 −0 Original line number Diff line number Diff line /* * 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(); } } } }
services/core/java/com/android/server/vibrator/Vibration.java +25 −24 Original line number Diff line number Diff line Loading @@ -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. */ Loading Loading @@ -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 Loading @@ -184,7 +184,7 @@ abstract class Vibration { @Override public long getCreateUptimeMillis() { return mCreateTime; return mCreateUptime; } @Override Loading Loading @@ -216,6 +216,7 @@ abstract class Vibration { @Override public void logMetrics(VibratorFrameworkStatsLogger statsLogger) { statsLogger.logVibrationAdaptiveHapticScale(mCallerInfo.uid, mAdaptiveScale); statsLogger.writeVibrationReportedAsync(mStatsInfo); } @Override Loading
services/core/java/com/android/server/vibrator/VibrationSession.java +41 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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. * Loading @@ -70,6 +92,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. */ Loading