Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6ac9443e authored by Ahmad Khalil's avatar Ahmad Khalil Committed by Android (Google) Code Review
Browse files

Merge "Changing Vibration class name to HalVibration."

parents 4ab33a94 36e98f2c
Loading
Loading
Loading
Loading
+219 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.Nullable;
import android.os.CombinedVibration;
import android.os.IBinder;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.util.SparseArray;

import com.android.internal.util.FrameworkStatsLog;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;

/**
 * Represents a vibration defined by a {@link CombinedVibration} that will be performed by
 * the IVibrator HAL.
 */
final class HalVibration extends Vibration {

    public final VibrationAttributes attrs;
    public final long id;
    public final int uid;
    public final int displayId;
    public final String opPkg;
    public final String reason;
    public final IBinder token;
    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();

    /** The actual effect to be played. */
    @Nullable
    private CombinedVibration mEffect;

    /**
     * The original effect that was requested. Typically these two things differ because the effect
     * was scaled based on the users vibration intensity settings.
     */
    @Nullable
    private CombinedVibration mOriginalEffect;

    /** Vibration status. */
    private Vibration.Status mStatus;

    /** Vibration runtime stats. */
    private final VibrationStats mStats = new VibrationStats();

    /** A {@link CountDownLatch} to enable waiting for completion. */
    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);

    HalVibration(IBinder token, int id, CombinedVibration effect,
            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
        this.token = token;
        this.mEffect = effect;
        this.id = id;
        this.attrs = attrs;
        this.uid = uid;
        this.displayId = displayId;
        this.opPkg = opPkg;
        this.reason = reason;
        mStatus = Vibration.Status.RUNNING;
    }

    VibrationStats stats() {
        return mStats;
    }

    /**
     * Set the {@link Status} of this vibration and reports the current system time as this
     * vibration end time, for debugging purposes.
     *
     * <p>This method will only accept given value if the current status is {@link
     * Status#RUNNING}.
     */
    public void end(EndInfo info) {
        if (hasEnded()) {
            // Vibration already ended, keep first ending status set and ignore this one.
            return;
        }
        mStatus = info.status;
        mStats.reportEnded(info.endedByUid, info.endedByUsage);
        mCompletionLatch.countDown();
    }

    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
    public void waitForEnd() throws InterruptedException {
        mCompletionLatch.await();
    }

    /**
     * Return the effect to be played when given prebaked effect id is not supported by the
     * vibrator.
     */
    @Nullable
    public VibrationEffect getFallback(int effectId) {
        return mFallbacks.get(effectId);
    }

    /**
     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
     * which might be necessary for replacement in realtime.
     */
    public void addFallback(int effectId, VibrationEffect effect) {
        mFallbacks.put(effectId, effect);
    }

    /**
     * Applied update function to the current effect held by this vibration, and to each fallback
     * effect added.
     */
    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
        CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
        if (!newEffect.equals(mEffect)) {
            if (mOriginalEffect == null) {
                mOriginalEffect = mEffect;
            }
            mEffect = newEffect;
        }
        for (int i = 0; i < mFallbacks.size(); i++) {
            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
        }
    }

    /**
     * Creates a new {@link CombinedVibration} by applying the given transformation function
     * to each {@link VibrationEffect}.
     */
    private static CombinedVibration transformCombinedEffect(
            CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
        if (combinedEffect instanceof CombinedVibration.Mono) {
            VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
            return CombinedVibration.createParallel(fn.apply(effect));
        } else if (combinedEffect instanceof CombinedVibration.Stereo) {
            SparseArray<VibrationEffect> effects =
                    ((CombinedVibration.Stereo) combinedEffect).getEffects();
            CombinedVibration.ParallelCombination combination =
                    CombinedVibration.startParallel();
            for (int i = 0; i < effects.size(); i++) {
                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
            }
            return combination.combine();
        } else if (combinedEffect instanceof CombinedVibration.Sequential) {
            List<CombinedVibration> effects =
                    ((CombinedVibration.Sequential) combinedEffect).getEffects();
            CombinedVibration.SequentialCombination combination =
                    CombinedVibration.startSequential();
            for (CombinedVibration effect : effects) {
                combination.addNext(transformCombinedEffect(effect, fn));
            }
            return combination.combine();
        } else {
            // Unknown combination, return same effect.
            return combinedEffect;
        }
    }

    /** Return true is current status is different from {@link Status#RUNNING}. */
    public boolean hasEnded() {
        return mStatus != Status.RUNNING;
    }

    /** Return true is effect is a repeating vibration. */
    public boolean isRepeating() {
        return mEffect.getDuration() == Long.MAX_VALUE;
    }

    /** Return the effect that should be played by this vibration. */
    @Nullable
    public CombinedVibration getEffect() {
        return mEffect;
    }

    /**
     * Return {@link Vibration.DebugInfo} with read-only debug information about this vibration.
     */
    public Vibration.DebugInfo getDebugInfo() {
        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
                attrs, uid, displayId, opPkg, reason);
    }

    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
        int vibrationType = isRepeating()
                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
        return new VibrationStats.StatsInfo(
                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
    }

    /**
     * Returns true if this vibration can pipeline with the specified one.
     *
     * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
     * because the cancel() call to stop the repetition will cancel a pending vibration too. This
     * can be changed if we have a use-case to reason around behavior for. It may also be nice to
     * pipeline very short vibrations together, regardless of the flag.
     */
    public boolean canPipelineWith(HalVibration vib) {
        return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && !isRepeating();
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ abstract class Step implements Comparable<Step> {
        this.startTime = startTime;
    }

    protected Vibration getVibration() {
    protected HalVibration getVibration() {
        return conductor.getVibration();
    }

+4 −190
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ 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;
@@ -27,20 +26,16 @@ import android.os.vibrator.PrimitiveSegment;
import android.os.vibrator.RampSegment;
import android.os.vibrator.StepSegment;
import android.os.vibrator.VibrationEffectSegment;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;

import com.android.internal.util.FrameworkStatsLog;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.function.Function;

/** Represents a vibration request to the vibrator service. */
final class Vibration {
/**
 * The base class for all vibrations.
 */
class Vibration {
    private static final SimpleDateFormat DEBUG_DATE_FORMAT =
            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");

@@ -85,186 +80,6 @@ final class Vibration {
        }
    }

    public final VibrationAttributes attrs;
    public final long id;
    public final int uid;
    public final int displayId;
    public final String opPkg;
    public final String reason;
    public final IBinder token;
    public final SparseArray<VibrationEffect> mFallbacks = new SparseArray<>();

    /** The actual effect to be played. */
    @Nullable
    private CombinedVibration mEffect;

    /**
     * The original effect that was requested. Typically these two things differ because the effect
     * was scaled based on the users vibration intensity settings.
     */
    @Nullable
    private CombinedVibration mOriginalEffect;

    /** Vibration status. */
    private Vibration.Status mStatus;

    /** Vibration runtime stats. */
    private final VibrationStats mStats = new VibrationStats();

    /** A {@link CountDownLatch} to enable waiting for completion. */
    private final CountDownLatch mCompletionLatch = new CountDownLatch(1);

    Vibration(IBinder token, int id, CombinedVibration effect,
            VibrationAttributes attrs, int uid, int displayId, String opPkg, String reason) {
        this.token = token;
        this.mEffect = effect;
        this.id = id;
        this.attrs = attrs;
        this.uid = uid;
        this.displayId = displayId;
        this.opPkg = opPkg;
        this.reason = reason;
        mStatus = Vibration.Status.RUNNING;
    }

    VibrationStats stats() {
        return mStats;
    }

    /**
     * Set the {@link Status} of this vibration and reports the current system time as this
     * vibration end time, for debugging purposes.
     *
     * <p>This method will only accept given value if the current status is {@link
     * Status#RUNNING}.
     */
    public void end(EndInfo info) {
        if (hasEnded()) {
            // Vibration already ended, keep first ending status set and ignore this one.
            return;
        }
        mStatus = info.status;
        mStats.reportEnded(info.endedByUid, info.endedByUsage);
        mCompletionLatch.countDown();
    }

    /** Waits indefinitely until another thread calls {@link #end} on this vibration. */
    public void waitForEnd() throws InterruptedException {
        mCompletionLatch.await();
    }

    /**
     * Return the effect to be played when given prebaked effect id is not supported by the
     * vibrator.
     */
    @Nullable
    public VibrationEffect getFallback(int effectId) {
        return mFallbacks.get(effectId);
    }

    /**
     * Add a fallback {@link VibrationEffect} to be played when given effect id is not supported,
     * which might be necessary for replacement in realtime.
     */
    public void addFallback(int effectId, VibrationEffect effect) {
        mFallbacks.put(effectId, effect);
    }

    /**
     * Applied update function to the current effect held by this vibration, and to each fallback
     * effect added.
     */
    public void updateEffects(Function<VibrationEffect, VibrationEffect> updateFn) {
        CombinedVibration newEffect = transformCombinedEffect(mEffect, updateFn);
        if (!newEffect.equals(mEffect)) {
            if (mOriginalEffect == null) {
                mOriginalEffect = mEffect;
            }
            mEffect = newEffect;
        }
        for (int i = 0; i < mFallbacks.size(); i++) {
            mFallbacks.setValueAt(i, updateFn.apply(mFallbacks.valueAt(i)));
        }
    }

    /**
     * Creates a new {@link CombinedVibration} by applying the given transformation function
     * to each {@link VibrationEffect}.
     */
    private static CombinedVibration transformCombinedEffect(
            CombinedVibration combinedEffect, Function<VibrationEffect, VibrationEffect> fn) {
        if (combinedEffect instanceof CombinedVibration.Mono) {
            VibrationEffect effect = ((CombinedVibration.Mono) combinedEffect).getEffect();
            return CombinedVibration.createParallel(fn.apply(effect));
        } else if (combinedEffect instanceof CombinedVibration.Stereo) {
            SparseArray<VibrationEffect> effects =
                    ((CombinedVibration.Stereo) combinedEffect).getEffects();
            CombinedVibration.ParallelCombination combination =
                    CombinedVibration.startParallel();
            for (int i = 0; i < effects.size(); i++) {
                combination.addVibrator(effects.keyAt(i), fn.apply(effects.valueAt(i)));
            }
            return combination.combine();
        } else if (combinedEffect instanceof CombinedVibration.Sequential) {
            List<CombinedVibration> effects =
                    ((CombinedVibration.Sequential) combinedEffect).getEffects();
            CombinedVibration.SequentialCombination combination =
                    CombinedVibration.startSequential();
            for (CombinedVibration effect : effects) {
                combination.addNext(transformCombinedEffect(effect, fn));
            }
            return combination.combine();
        } else {
            // Unknown combination, return same effect.
            return combinedEffect;
        }
    }

    /** Return true is current status is different from {@link Status#RUNNING}. */
    public boolean hasEnded() {
        return mStatus != Status.RUNNING;
    }

    /** Return true is effect is a repeating vibration. */
    public boolean isRepeating() {
        return mEffect.getDuration() == Long.MAX_VALUE;
    }

    /** Return the effect that should be played by this vibration. */
    @Nullable
    public CombinedVibration getEffect() {
        return mEffect;
    }

    /** Return {@link Vibration.DebugInfo} with read-only debug information about this vibration. */
    public Vibration.DebugInfo getDebugInfo() {
        return new Vibration.DebugInfo(mStatus, mStats, mEffect, mOriginalEffect, /* scale= */ 0,
                attrs, uid, displayId, opPkg, reason);
    }

    /** Return {@link VibrationStats.StatsInfo} with read-only metrics about this vibration. */
    public VibrationStats.StatsInfo getStatsInfo(long completionUptimeMillis) {
        int vibrationType = isRepeating()
                ? FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__REPEATED
                : FrameworkStatsLog.VIBRATION_REPORTED__VIBRATION_TYPE__SINGLE;
        return new VibrationStats.StatsInfo(
                uid, vibrationType, attrs.getUsage(), mStatus, mStats, completionUptimeMillis);
    }

    /**
     * Returns true if this vibration can pipeline with the specified one.
     *
     * <p>Note that currently, repeating vibrations can't pipeline with following vibrations,
     * because the cancel() call to stop the repetition will cancel a pending vibration too. This
     * can be changed if we have a use-case to reason around behavior for. It may also be nice to
     * pipeline very short vibrations together, regardless of the flag.
     */
    public boolean canPipelineWith(Vibration vib) {
        return uid == vib.uid && attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && vib.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT)
                && !isRepeating();
    }

    /** Immutable info passed as a signal to end a vibration. */
    static final class EndInfo {
        /** The {@link Status} to be set to the vibration when it ends with this info. */
@@ -505,5 +320,4 @@ final class Vibration {
            proto.end(token);
        }
    }

}
+3 −3
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
    // Not guarded by lock because they're not modified by this conductor, it's used here only to
    // check immutable attributes. The status and other mutable states are changed by the service or
    // by the vibrator steps.
    private final Vibration mVibration;
    private final HalVibration mVibration;
    private final SparseArray<VibratorController> mVibrators = new SparseArray<>();

    private final PriorityQueue<Step> mNextSteps = new PriorityQueue<>();
@@ -95,7 +95,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
    private int mRemainingStartSequentialEffectSteps;
    private int mSuccessfulVibratorOnSteps;

    VibrationStepConductor(Vibration vib, VibrationSettings vibrationSettings,
    VibrationStepConductor(HalVibration vib, VibrationSettings vibrationSettings,
            DeviceVibrationEffectAdapter effectAdapter,
            SparseArray<VibratorController> availableVibrators,
            VibrationThread.VibratorManagerHooks vibratorManagerHooks) {
@@ -160,7 +160,7 @@ final class VibrationStepConductor implements IBinder.DeathRecipient {
        mVibration.stats().reportStarted();
    }

    public Vibration getVibration() {
    public HalVibration getVibration() {
        // No thread assertion: immutable
        return mVibration;
    }
+2 −2
Original line number Diff line number Diff line
@@ -33,12 +33,12 @@ import com.android.internal.annotations.VisibleForTesting;
import java.util.NoSuchElementException;
import java.util.Objects;

/** Plays a {@link Vibration} in dedicated thread. */
/** Plays a {@link HalVibration} in dedicated thread. */
final class VibrationThread extends Thread {
    static final String TAG = "VibrationThread";
    static final boolean DEBUG = false;

    /** Calls into VibratorManager functionality needed for playing a {@link Vibration}. */
    /** Calls into VibratorManager functionality needed for playing a {@link HalVibration}. */
    interface VibratorManagerHooks {

        /**
Loading