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

Commit 1bd6aac4 authored by Lais Andrade's avatar Lais Andrade
Browse files

Play any composition in VibrationThread

Remove VibratorOnStep and create a individual step for each IVibrator
method that can be used to turn on the vibrator (on, perform, compose or
composePwle).

Playing compositions now is implemented in the same was as playing a
waveform with setAmplitude calls is implemented: a sequence of steps.

Bug: 167947076
Test: VibrationThreadTest
Change-Id: I0c0c031770b1a01ab5a74b1af15b2c3904c2d6a4
parent a4117461
Loading
Loading
Loading
Loading
+395 −216

File changed.

Preview size limit exceeded, changes collapsed.

+16 −8
Original line number Diff line number Diff line
@@ -189,12 +189,18 @@ final class VibratorController {
     * callback to {@link OnVibrationCompleteListener}.
     *
     * <p>This will affect the state of {@link #isVibrating()}.
     *
     * @return The positive duration of the vibration started, if successful, zero if the vibrator
     * do not support the input or a negative number if the operation failed.
     */
    public void on(long milliseconds, long vibrationId) {
    public long on(long milliseconds, long vibrationId) {
        synchronized (mLock) {
            mNativeWrapper.on(milliseconds, vibrationId);
            long duration = mNativeWrapper.on(milliseconds, vibrationId);
            if (duration > 0) {
                notifyVibratorOnLocked();
            }
            return duration;
        }
    }

    /**
@@ -203,7 +209,8 @@ final class VibratorController {
     *
     * <p>This will affect the state of {@link #isVibrating()}.
     *
     * @return The duration of the effect playing, or 0 if unsupported.
     * @return The positive duration of the vibration started, if successful, zero if the vibrator
     * do not support the input or a negative number if the operation failed.
     */
    public long on(PrebakedSegment prebaked, long vibrationId) {
        synchronized (mLock) {
@@ -222,7 +229,8 @@ final class VibratorController {
     *
     * <p>This will affect the state of {@link #isVibrating()}.
     *
     * @return The duration of the effect playing, or 0 if unsupported.
     * @return The positive duration of the vibration started, if successful, zero if the vibrator
     * do not support the input or a negative number if the operation failed.
     */
    public long on(PrimitiveSegment[] primitives, long vibrationId) {
        if (!mVibratorInfo.hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
@@ -327,7 +335,7 @@ final class VibratorController {
         */
        private static native long getNativeFinalizer();
        private static native boolean isAvailable(long nativePtr);
        private static native void on(long nativePtr, long milliseconds, long vibrationId);
        private static native long on(long nativePtr, long milliseconds, long vibrationId);
        private static native void off(long nativePtr);
        private static native void setAmplitude(long nativePtr, float amplitude);
        private static native int[] getSupportedEffects(long nativePtr);
@@ -365,8 +373,8 @@ final class VibratorController {
        }

        /** Turns vibrator on for given time. */
        public void on(long milliseconds, long vibrationId) {
            on(mNativePtr, milliseconds, vibrationId);
        public long on(long milliseconds, long vibrationId) {
            return on(mNativePtr, milliseconds, vibrationId);
        }

        /** Turns vibrator off. */
+100 −68
Original line number Diff line number Diff line
@@ -1394,15 +1394,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                while ((nextArg = peekNextArg()) != null) {
                    switch (nextArg) {
                        case "-f":
                            getNextArgRequired(); // consume the -f argument;
                            getNextArgRequired(); // consume "-f"
                            force = true;
                            break;
                        case "-d":
                            getNextArgRequired(); // consume the -d argument;
                            getNextArgRequired(); // consume "-d"
                            description = getNextArgRequired();
                            break;
                        default:
                            // Not a common option, finish reading.
                            // nextArg is not a common option, finish reading.
                            return;
                    }
                }
@@ -1456,14 +1456,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {

        private int runMono() {
            CommonOptions commonOptions = new CommonOptions();
            VibrationEffect effect = nextEffect();
            if (effect == null) {
                return 0;
            }

            CombinedVibrationEffect combinedEffect = CombinedVibrationEffect.createSynced(effect);
            CombinedVibrationEffect effect = CombinedVibrationEffect.createSynced(nextEffect());
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combinedEffect, attrs,
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, effect, attrs,
                    commonOptions.description, mToken);
            return 0;
        }
@@ -1474,10 +1469,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                    CombinedVibrationEffect.startSynced();
            while ("-v".equals(getNextOption())) {
                int vibratorId = Integer.parseInt(getNextArgRequired());
                VibrationEffect effect = nextEffect();
                if (effect != null) {
                    combination.addVibrator(vibratorId, effect);
                }
                combination.addVibrator(vibratorId, nextEffect());
            }
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
@@ -1487,19 +1479,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {

        private int runSequential() {
            CommonOptions commonOptions = new CommonOptions();

            CombinedVibrationEffect.SequentialCombination combination =
                    CombinedVibrationEffect.startSequential();
            while ("-v".equals(getNextOption())) {
                int vibratorId = Integer.parseInt(getNextArgRequired());
                int delay = 0;
                if ("-w".equals(getNextOption())) {
                    delay = Integer.parseInt(getNextArgRequired());
                }
                VibrationEffect effect = nextEffect();
                if (effect != null) {
                    combination.addNext(vibratorId, effect, delay);
                }
                combination.addNext(vibratorId, nextEffect());
            }
            VibrationAttributes attrs = createVibrationAttributes(commonOptions);
            vibrate(Binder.getCallingUid(), SHELL_PACKAGE_NAME, combination.combine(), attrs,
@@ -1512,87 +1496,129 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
            return 0;
        }

        @Nullable
        private VibrationEffect nextEffect() {
            String effectType = getNextArgRequired();
            if ("oneshot".equals(effectType)) {
                return nextOneShot();
            VibrationEffect.Composition composition = VibrationEffect.startComposition();
            String nextArg;

            while ((nextArg = peekNextArg()) != null) {
                if ("oneshot".equals(nextArg)) {
                    addOneShotToComposition(composition);
                } else if ("waveform".equals(nextArg)) {
                    addWaveformToComposition(composition);
                } else if ("prebaked".equals(nextArg)) {
                    addPrebakedToComposition(composition);
                } else if ("primitives".equals(nextArg)) {
                    addPrimitivesToComposition(composition);
                } else {
                    // nextArg is not an effect, finish reading.
                    break;
                }
            if ("waveform".equals(effectType)) {
                return nextWaveform();
            }
            if ("prebaked".equals(effectType)) {
                return nextPrebaked();

            return composition.compose();
        }
            if ("composed".equals(effectType)) {
                return nextComposed();

        private void addOneShotToComposition(VibrationEffect.Composition composition) {
            boolean hasAmplitude = false;
            int delay = 0;

            getNextArgRequired(); // consume "oneshot"
            String nextOption;
            while ((nextOption = getNextOption()) != null) {
                if ("-a".equals(nextOption)) {
                    hasAmplitude = true;
                } else if ("-w".equals(nextOption)) {
                    delay = Integer.parseInt(getNextArgRequired());
                }
            return null;
            }

        private VibrationEffect nextOneShot() {
            boolean hasAmplitude = "-a".equals(getNextOption());
            long duration = Long.parseLong(getNextArgRequired());
            int amplitude = hasAmplitude ? Integer.parseInt(getNextArgRequired())
                    : VibrationEffect.DEFAULT_AMPLITUDE;
            return VibrationEffect.createOneShot(duration, amplitude);
            composition.addEffect(VibrationEffect.createOneShot(duration, amplitude), delay);
        }

        private VibrationEffect nextWaveform() {
        private void addWaveformToComposition(VibrationEffect.Composition composition) {
            boolean hasAmplitudes = false;
            int repeat = -1;
            int delay = 0;

            String nextOption = getNextOption();
            while (nextOption != null) {
            getNextArgRequired(); // consume "waveform"
            String nextOption;
            while ((nextOption = getNextOption()) != null) {
                if ("-a".equals(nextOption)) {
                    hasAmplitudes = true;
                } else if ("-r".equals(nextOption)) {
                    repeat = Integer.parseInt(getNextArgRequired());
                } else if ("-w".equals(nextOption)) {
                    delay = Integer.parseInt(getNextArgRequired());
                }
                nextOption = getNextOption();
            }
            List<Long> durations = new ArrayList<>();
            List<Integer> amplitudes = new ArrayList<>();
            VibrationEffect waveform;

            String nextArg;
            while ((nextArg = peekNextArg()) != null && !"-v".equals(nextArg)) {
                durations.add(Long.parseLong(getNextArgRequired()));
            while ((nextArg = peekNextArg()) != null) {
                try {
                    durations.add(Long.parseLong(nextArg));
                    getNextArgRequired(); // consume the duration
                } catch (NumberFormatException e) {
                    // nextArg is not a duration, finish reading.
                    break;
                }
                if (hasAmplitudes) {
                    amplitudes.add(Integer.parseInt(getNextArgRequired()));
                }
            }

            long[] durationArray = durations.stream().mapToLong(Long::longValue).toArray();
            if (!hasAmplitudes) {
                return VibrationEffect.createWaveform(durationArray, repeat);
            if (hasAmplitudes) {
                int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
                waveform = VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
            } else {
                waveform = VibrationEffect.createWaveform(durationArray, repeat);
            }

            int[] amplitudeArray = amplitudes.stream().mapToInt(Integer::intValue).toArray();
            return VibrationEffect.createWaveform(durationArray, amplitudeArray, repeat);
            composition.addEffect(waveform, delay);
        }

        private void addPrebakedToComposition(VibrationEffect.Composition composition) {
            boolean shouldFallback = false;
            int delay = 0;

            getNextArgRequired(); // consume "prebaked"
            String nextOption;
            while ((nextOption = getNextOption()) != null) {
                if ("-b".equals(nextOption)) {
                    shouldFallback = true;
                } else if ("-w".equals(nextOption)) {
                    delay = Integer.parseInt(getNextArgRequired());
                }
            }

        private VibrationEffect nextPrebaked() {
            boolean shouldFallback = "-b".equals(getNextOption());
            int effectId = Integer.parseInt(getNextArgRequired());
            return VibrationEffect.get(effectId, shouldFallback);
            composition.addEffect(VibrationEffect.get(effectId, shouldFallback), delay);
        }

        private VibrationEffect nextComposed() {
            VibrationEffect.Composition composition = VibrationEffect.startComposition();
        private void addPrimitivesToComposition(VibrationEffect.Composition composition) {
            getNextArgRequired(); // consume "primitives"
            String nextArg;
            while ((nextArg = peekNextArg()) != null) {
                int delay = 0;
                if ("-w".equals(nextArg)) {
                    getNextArgRequired(); // consume the -w option
                    getNextArgRequired(); // consume "-w"
                    delay = Integer.parseInt(getNextArgRequired());
                } else if ("-v".equals(nextArg)) {
                    // Starting next vibrator, this composed effect if finished.
                    nextArg = peekNextArg();
                }
                try {
                    composition.addPrimitive(Integer.parseInt(nextArg), /* scale= */ 1, delay);
                    getNextArgRequired(); // consume the primitive id
                } catch (NumberFormatException | NullPointerException e) {
                    // nextArg is not describing a primitive, leave it to be consumed by outer loops
                    break;
                }
                int primitiveId = Integer.parseInt(getNextArgRequired());
                composition.addPrimitive(primitiveId, /* scale= */ 1f, delay);
            }
            return composition.compose();
        }

        private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
@@ -1615,38 +1641,44 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
                pw.println("  list");
                pw.println("    Prints the id of device vibrators. This does not include any ");
                pw.println("    connected input device.");
                pw.println("  synced [options] <effect>");
                pw.println("  synced [options] <effect>...");
                pw.println("    Vibrates effect on all vibrators in sync.");
                pw.println("  combined [options] (-v <vibrator-id> <effect>)...");
                pw.println("  combined [options] (-v <vibrator-id> <effect>...)...");
                pw.println("    Vibrates different effects on each vibrator in sync.");
                pw.println("  sequential [options] (-v <vibrator-id> [-w <delay>] <effect>)...");
                pw.println("  sequential [options] (-v <vibrator-id> <effect>...)...");
                pw.println("    Vibrates different effects on each vibrator in sequence.");
                pw.println("  cancel");
                pw.println("    Cancels any active vibration");
                pw.println("");
                pw.println("Effect commands:");
                pw.println("  oneshot [-a] <duration> [<amplitude>]");
                pw.println("  oneshot [-w delay] [-a] <duration> [<amplitude>]");
                pw.println("    Vibrates for duration milliseconds; ignored when device is on ");
                pw.println("    DND (Do Not Disturb) mode; touch feedback strength user setting ");
                pw.println("    will be used to scale amplitude.");
                pw.println("    If -w is provided, the effect will be played after the specified");
                pw.println("    wait time in milliseconds.");
                pw.println("    If -a is provided, the command accepts a second argument for ");
                pw.println("    amplitude, in a scale of 1-255.");
                pw.println("  waveform [-r <index>] [-a] (<duration> [<amplitude>])...");
                pw.println("  waveform [-w delay] [-r index] [-a] (<duration> [<amplitude>])...");
                pw.println("    Vibrates for durations and amplitudes in list; ignored when ");
                pw.println("    device is on DND (Do Not Disturb) mode; touch feedback strength ");
                pw.println("    user setting will be used to scale amplitude.");
                pw.println("    If -w is provided, the effect will be played after the specified");
                pw.println("    wait time in milliseconds.");
                pw.println("    If -r is provided, the waveform loops back to the specified");
                pw.println("    index (e.g. 0 loops from the beginning)");
                pw.println("    If -a is provided, the command accepts duration-amplitude pairs;");
                pw.println("    otherwise, it accepts durations only and alternates off/on");
                pw.println("    Duration is in milliseconds; amplitude is a scale of 1-255.");
                pw.println("  prebaked [-b] <effect-id>");
                pw.println("  prebaked [-w delay] [-b] <effect-id>");
                pw.println("    Vibrates with prebaked effect; ignored when device is on DND ");
                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
                pw.println("    will be used to scale amplitude.");
                pw.println("    If -w is provided, the effect will be played after the specified");
                pw.println("    wait time in milliseconds.");
                pw.println("    If -b is provided, the prebaked fallback effect will be played if");
                pw.println("    the device doesn't support the given effect-id.");
                pw.println("  composed [-w <delay>] <primitive-id>...");
                pw.println("  primitives ([-w delay] <primitive-id>)...");
                pw.println("    Vibrates with a composed effect; ignored when device is on DND ");
                pw.println("    (Do Not Disturb) mode; touch feedback strength user setting ");
                pw.println("    will be used to scale primitive intensities.");
+8 −7
Original line number Diff line number Diff line
@@ -150,15 +150,16 @@ static jboolean vibratorIsAvailable(JNIEnv* env, jclass /* clazz */, jlong ptr)
    return wrapper->hal()->ping().isOk() ? JNI_TRUE : JNI_FALSE;
}

static void vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs,
static jlong vibratorOn(JNIEnv* env, jclass /* clazz */, jlong ptr, jlong timeoutMs,
                        jlong vibrationId) {
    VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
    if (wrapper == nullptr) {
        ALOGE("vibratorOn failed because native wrapper was not initialized");
        return;
        return -1;
    }
    auto callback = wrapper->createCallback(vibrationId);
    wrapper->hal()->on(std::chrono::milliseconds(timeoutMs), callback);
    auto result = wrapper->hal()->on(std::chrono::milliseconds(timeoutMs), callback);
    return result.isOk() ? timeoutMs : (result.isUnsupported() ? 0 : -1);
}

static void vibratorOff(JNIEnv* env, jclass /* clazz */, jlong ptr) {
@@ -234,7 +235,7 @@ static jlong vibratorPerformEffect(JNIEnv* env, jclass /* clazz */, jlong ptr, j
    aidl::EffectStrength effectStrength = static_cast<aidl::EffectStrength>(strength);
    auto callback = wrapper->createCallback(vibrationId);
    auto result = wrapper->hal()->performEffect(effectType, effectStrength, callback);
    return result.isOk() ? result.value().count() : -1;
    return result.isOk() ? result.value().count() : (result.isUnsupported() ? 0 : -1);
}

static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
@@ -252,7 +253,7 @@ static jlong vibratorPerformComposedEffect(JNIEnv* env, jclass /* clazz */, jlon
    }
    auto callback = wrapper->createCallback(vibrationId);
    auto result = wrapper->hal()->performComposedEffect(effects, callback);
    return result.isOk() ? result.value().count() : -1;
    return result.isOk() ? result.value().count() : (result.isUnsupported() ? 0 : -1);
}

static jlong vibratorGetCapabilities(JNIEnv* env, jclass /* clazz */, jlong ptr) {
@@ -311,7 +312,7 @@ static const JNINativeMethod method_table[] = {
         (void*)vibratorNativeInit},
        {"getNativeFinalizer", "()J", (void*)vibratorGetNativeFinalizer},
        {"isAvailable", "(J)Z", (void*)vibratorIsAvailable},
        {"on", "(JJJ)V", (void*)vibratorOn},
        {"on", "(JJJ)J", (void*)vibratorOn},
        {"off", "(J)V", (void*)vibratorOff},
        {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
        {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
+2 −1
Original line number Diff line number Diff line
@@ -74,11 +74,12 @@ final class FakeVibratorControllerProvider {
        }

        @Override
        public void on(long milliseconds, long vibrationId) {
        public long on(long milliseconds, long vibrationId) {
            mEffectSegments.add(new StepSegment(VibrationEffect.DEFAULT_AMPLITUDE,
                    /* frequency= */ 0, (int) milliseconds));
            applyLatency();
            scheduleListener(milliseconds, vibrationId);
            return milliseconds;
        }

        @Override
Loading