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

Commit e320282b authored by Lais Andrade's avatar Lais Andrade
Browse files

Extract input device vibrators code from VibratorService

This code will also be migrated to VibratorManagerService, to give
preference to input devices over the devices vibrators when the user
setting enables it.

This need to remain a server-side code so permissions/setting will be
applied to system vibrations even when they are redirected to input
devices (including vibration scaling).

Bug: 167946816
Bug: 131311651
Test: atest FrameworksServiceTests:VibratorServiceTest
      atest FrameworksServiceTests:InputDeviceVibratorsTest
Change-Id: I6c61c3f20e4d82d44af136917d3755d9f6edd31f
parent 2902a868
Loading
Loading
Loading
Loading
+17 −1
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.view.PointerIcon;
import android.view.VerifiedInputEvent;
import android.view.WindowManager.LayoutParams;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;

@@ -268,6 +269,21 @@ public final class InputManager {
        mIm = im;
    }

    /**
     * Gets an instance of the input manager.
     *
     * @return The input manager instance.
     *
     * @hide
     */
    @VisibleForTesting
    public static InputManager resetInstance(IInputManager inputManagerService) {
        synchronized (InputManager.class) {
            sInstance = new InputManager(inputManagerService);
            return sInstance;
        }
    }

    /**
     * Gets an instance of the input manager.
     *
@@ -1428,7 +1444,7 @@ public final class InputManager {

        @Override
        public boolean hasAmplitudeControl() {
            return false;
            return true;
        }

        /**
+8 −2
Original line number Diff line number Diff line
@@ -29,6 +29,8 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.Vibrator;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -423,9 +425,13 @@ public final class InputDevice implements Parcelable {
        }
    };

    // Called by native code.
    /**
     * Called by native code
     * @hide
     */
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    private InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
    @VisibleForTesting
    public InputDevice(int id, int generation, int controllerNumber, String name, int vendorId,
            int productId, String descriptor, boolean isExternal, int sources, int keyboardType,
            KeyCharacterMap keyCharacterMap, boolean hasVibrator, boolean hasMicrophone,
            boolean hasButtonUnderPad) {
+70 −123
Original line number Diff line number Diff line
@@ -29,7 +29,6 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.res.Resources;
import android.hardware.input.InputManager;
import android.hardware.vibrator.IVibrator;
import android.os.BatteryStats;
import android.os.Binder;
@@ -60,13 +59,13 @@ import android.os.WorkSource;
import android.util.Slog;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InputDevice;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.vibrator.InputDeviceDelegate;
import com.android.server.vibrator.VibrationScaler;
import com.android.server.vibrator.VibrationSettings;

@@ -82,8 +81,8 @@ import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class VibratorService extends IVibratorService.Stub
        implements InputManager.InputDeviceListener {
/** System implementation of {@link IVibratorService}. */
public class VibratorService extends IVibratorService.Stub {
    private static final String TAG = "VibratorService";
    private static final SimpleDateFormat DEBUG_DATE_FORMAT =
            new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
@@ -122,19 +121,13 @@ public class VibratorService extends IVibratorService.Stub
    private final IBatteryStats mBatteryStatsService;
    private final String mSystemUiPackage;
    private PowerManagerInternal mPowerManagerInternal;
    private InputManager mIm;
    private VibrationSettings mVibrationSettings;
    private VibrationScaler mVibrationScaler;
    private InputDeviceDelegate mInputDeviceDelegate;

    private final NativeWrapper mNativeWrapper;
    private volatile VibrateWaveformThread mThread;

    // mInputDeviceVibrators lock should be acquired after mLock, if both are
    // to be acquired
    private final ArrayList<Vibrator> mInputDeviceVibrators = new ArrayList<>();
    private boolean mVibrateInputDevicesSetting; // guarded by mInputDeviceVibrators
    private boolean mInputDeviceListenerRegistered; // guarded by mInputDeviceVibrators

    @GuardedBy("mLock")
    private Vibration mCurrentVibration;
    private int mCurVibUid = -1;
@@ -343,6 +336,7 @@ public class VibratorService extends IVibratorService.Stub
        public enum Status {
            RUNNING,
            FINISHED,
            FORWARDED_TO_INPUT_DEVICES,
            CANCELLED,
            ERROR_APP_OPS,
            IGNORED,
@@ -577,9 +571,9 @@ public class VibratorService extends IVibratorService.Stub
    public void systemReady() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "VibratorService#systemReady");
        try {
            mIm = mContext.getSystemService(InputManager.class);
            mVibrationSettings = new VibrationSettings(mContext, mH);
            mVibrationScaler = new VibrationScaler(mContext, mVibrationSettings);
            mInputDeviceDelegate = new InputDeviceDelegate(mContext, mH);

            mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
            mPowerManagerInternal.registerLowPowerModeObserver(
@@ -707,12 +701,8 @@ public class VibratorService extends IVibratorService.Stub

    @Override // Binder call
    public boolean hasAmplitudeControl() {
        synchronized (mInputDeviceVibrators) {
            // Input device vibrators don't support amplitude controls yet, but are still used over
            // the system vibrator when connected.
            return hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL)
                    && mInputDeviceVibrators.isEmpty();
        }
        // Input device vibrators always support amplitude controls.
        return mInputDeviceDelegate.isAvailable() || hasCapability(IVibrator.CAP_AMPLITUDE_CONTROL);
    }

    @Override // Binder call
@@ -1059,10 +1049,8 @@ public class VibratorService extends IVibratorService.Stub
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                doVibratorOn(vib);
            } else if (vib.effect instanceof VibrationEffect.Waveform) {
                // mThread better be null here. doCancelVibrate should always be
                // called before startNextVibrationLocked or startVibrationLocked.
                mThread = new VibrateWaveformThread(vib);
                mThread.start();
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                doVibratorWaveformEffectLocked(vib);
            } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                doVibratorPrebakedEffectLocked(vib);
@@ -1193,7 +1181,8 @@ public class VibratorService extends IVibratorService.Stub

    private void updateVibrators() {
        synchronized (mLock) {
            boolean devicesUpdated = updateInputDeviceVibratorsLocked();
            boolean devicesUpdated = mInputDeviceDelegate.updateInputDeviceVibrators(
                    mVibrationSettings.shouldVibrateInputDevices());
            boolean lowPowerModeUpdated = updateLowPowerModeLocked();

            if (devicesUpdated || lowPowerModeUpdated) {
@@ -1205,41 +1194,6 @@ public class VibratorService extends IVibratorService.Stub
        }
    }

    private boolean updateInputDeviceVibratorsLocked() {
        boolean changed = false;
        boolean vibrateInputDevices = mVibrationSettings.shouldVibrateInputDevices();
        if (vibrateInputDevices != mVibrateInputDevicesSetting) {
            changed = true;
            mVibrateInputDevicesSetting = vibrateInputDevices;
        }

        if (mVibrateInputDevicesSetting) {
            if (!mInputDeviceListenerRegistered) {
                mInputDeviceListenerRegistered = true;
                mIm.registerInputDeviceListener(this, mH);
            }
        } else {
            if (mInputDeviceListenerRegistered) {
                mInputDeviceListenerRegistered = false;
                mIm.unregisterInputDeviceListener(this);
            }
        }

        mInputDeviceVibrators.clear();
        if (mVibrateInputDevicesSetting) {
            int[] ids = mIm.getInputDeviceIds();
            for (int i = 0; i < ids.length; i++) {
                InputDevice device = mIm.getInputDevice(ids[i]);
                Vibrator vibrator = device.getVibrator();
                if (vibrator.hasVibrator()) {
                    mInputDeviceVibrators.add(vibrator);
                }
            }
            return true;
        }
        return changed;
    }

    private boolean updateLowPowerModeLocked() {
        boolean lowPowerMode = mPowerManagerInternal
                .getLowPowerState(ServiceType.VIBRATION).batterySaverEnabled;
@@ -1268,58 +1222,37 @@ public class VibratorService extends IVibratorService.Stub
        }
    }

    @Override
    public void onInputDeviceAdded(int deviceId) {
        updateVibrators();
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
        updateVibrators();
    }

    @Override
    public void onInputDeviceRemoved(int deviceId) {
        updateVibrators();
    }

    private boolean doVibratorExists() {
        // For now, we choose to ignore the presence of input devices that have vibrators
        // when reporting whether the device has a vibrator.  Applications often use this
        // information to decide whether to enable certain features so they expect the
        // result of hasVibrator() to be constant.  For now, just report whether
        // the device has a built-in vibrator.
        //synchronized (mInputDeviceVibrators) {
        //    return !mInputDeviceVibrators.isEmpty() || vibratorExists();
        //}
        return mNativeWrapper.vibratorExists();
    }

    private void doVibratorOn(Vibration vib) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
        try {
            synchronized (mInputDeviceVibrators) {
                final VibrationEffect.OneShot oneShot = vib.effect.resolve(
                        mDefaultVibrationAmplitude);
            final VibrationEffect.OneShot oneShot = vib.effect.resolve(mDefaultVibrationAmplitude);
            if (DEBUG) {
                Slog.d(TAG, "Turning vibrator on for " + oneShot.getDuration() + " ms"
                        + " with amplitude " + oneShot.getAmplitude() + ".");
            }
                noteVibratorOnLocked(vib.uid, oneShot.getDuration());
                final int vibratorCount = mInputDeviceVibrators.size();
                if (vibratorCount != 0) {
                    for (int i = 0; i < vibratorCount; i++) {
                        mInputDeviceVibrators.get(i).vibrate(vib.uid, vib.opPkg, oneShot,
                                vib.reason, vib.attrs);
                    }
            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                    vib.uid, vib.opPkg, oneShot, vib.reason, vib.attrs);
            if (inputDevicesAvailable) {
                // The set current vibration is no longer being played by this service, so drop it.
                mCurrentVibration = null;
                endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
            } else {
                noteVibratorOnLocked(vib.uid, oneShot.getDuration());
                // Note: ordering is important here! Many haptic drivers will reset their
                // amplitude when enabled, so we always have to enable first, then set the
                // amplitude.
                mNativeWrapper.vibratorOn(oneShot.getDuration(), vib.id);
                doVibratorSetAmplitude(oneShot.getAmplitude());
            }
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
@@ -1334,19 +1267,34 @@ public class VibratorService extends IVibratorService.Stub
    private void doVibratorOff() {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOff");
        try {
            synchronized (mInputDeviceVibrators) {
            if (DEBUG) {
                Slog.d(TAG, "Turning vibrator off.");
            }
            noteVibratorOffLocked();
                final int vibratorCount = mInputDeviceVibrators.size();
                if (vibratorCount != 0) {
                    for (int i = 0; i < vibratorCount; i++) {
                        mInputDeviceVibrators.get(i).cancel();
                    }
                } else {
            boolean inputDevicesAvailable = mInputDeviceDelegate.cancelVibrateIfAvailable();
            if (!inputDevicesAvailable) {
                mNativeWrapper.vibratorOff();
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
        }
    }

    @GuardedBy("mLock")
    private void doVibratorWaveformEffectLocked(Vibration vib) {
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorWaveformEffectLocked");
        try {
            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                    vib.uid, vib.opPkg, vib.effect, vib.reason, vib.attrs);
            if (inputDevicesAvailable) {
                // The set current vibration is no longer being played by this service, so drop it.
                mCurrentVibration = null;
                endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
            } else {
                // mThread better be null here. doCancelVibrate should always be
                // called before startNextVibrationLocked or startVibrationLocked.
                mThread = new VibrateWaveformThread(vib);
                mThread.start();
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -1358,12 +1306,9 @@ public class VibratorService extends IVibratorService.Stub
        Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorPrebakedEffectLocked");
        try {
            final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
            final boolean usingInputDeviceVibrators;
            synchronized (mInputDeviceVibrators) {
                usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
            }
            // Input devices don't support prebaked effect, so skip trying it with them.
            if (!usingInputDeviceVibrators) {
            // Input devices don't support prebaked effect, so skip trying it with them and allow
            // fallback to be attempted.
            if (!mInputDeviceDelegate.isAvailable()) {
                long duration = mNativeWrapper.vibratorPerformEffect(
                        prebaked.getId(), prebaked.getEffectStrength(), vib.id);
                if (duration > 0) {
@@ -1401,15 +1346,17 @@ public class VibratorService extends IVibratorService.Stub

        try {
            final VibrationEffect.Composed composed = (VibrationEffect.Composed) vib.effect;
            final boolean usingInputDeviceVibrators;
            synchronized (mInputDeviceVibrators) {
                usingInputDeviceVibrators = !mInputDeviceVibrators.isEmpty();
            }
            // Input devices don't support composed effect, so skip trying it with them.
            if (usingInputDeviceVibrators || !hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
                endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
            boolean inputDevicesAvailable = mInputDeviceDelegate.vibrateIfAvailable(
                    vib.uid, vib.opPkg, composed, vib.reason, vib.attrs);
            if (inputDevicesAvailable) {
                // The set current vibration is no longer being played by this service, so drop it.
                mCurrentVibration = null;
                endVibrationLocked(vib, VibrationInfo.Status.FORWARDED_TO_INPUT_DEVICES);
                return;
            } else if (!hasCapability(IVibrator.CAP_COMPOSE_EFFECTS)) {
                // The set current vibration is not actually playing, so drop it.
                mCurrentVibration = null;
                endVibrationLocked(vib, VibrationInfo.Status.IGNORED_UNSUPPORTED);
                return;
            }

+171 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.content.Context;
import android.hardware.input.InputManager;
import android.os.Handler;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.SparseArray;
import android.view.InputDevice;

import com.android.internal.annotations.GuardedBy;

/** Delegates vibrations to all connected {@link InputDevice} with available {@link Vibrator}. */
// TODO(b/159207608): Make this package-private once vibrator services are moved to this package
public final class InputDeviceDelegate implements InputManager.InputDeviceListener {
    private static final String TAG = "InputDeviceDelegate";

    private final Object mLock = new Object();
    private final Handler mHandler;
    private final InputManager mInputManager;

    @GuardedBy("mLock")
    private final SparseArray<Vibrator> mInputDeviceVibrators = new SparseArray<>();

    /**
     * Flag updated via {@link #updateInputDeviceVibrators(boolean)}, holding the value of {@link
     * android.provider.Settings.System#VIBRATE_INPUT_DEVICES}.
     */
    @GuardedBy("mLock")
    private boolean mShouldVibrateInputDevices;

    public InputDeviceDelegate(Context context, Handler handler) {
        mHandler = handler;
        mInputManager = context.getSystemService(InputManager.class);
    }

    @Override
    public void onInputDeviceAdded(int deviceId) {
        updateInputDevice(deviceId);
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
        updateInputDevice(deviceId);
    }

    @Override
    public void onInputDeviceRemoved(int deviceId) {
        synchronized (mLock) {
            mInputDeviceVibrators.remove(deviceId);
        }
    }

    /**
     * Return {@code true} is there are input devices with vibrators available and vibrations should
     * be delegated to them.
     */
    public boolean isAvailable() {
        synchronized (mLock) {
            // mInputDeviceVibrators is cleared when settings are disabled, so this check is enough.
            return mInputDeviceVibrators.size() > 0;
        }
    }

    /**
     * Vibrate all {@link InputDevice} with {@link Vibrator} available using given effect.
     *
     * @return {@link #isAvailable()}
     */
    public boolean vibrateIfAvailable(int uid, String opPkg, VibrationEffect effect,
            String reason, VibrationAttributes attrs) {
        synchronized (mLock) {
            for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
                mInputDeviceVibrators.valueAt(i).vibrate(uid, opPkg, effect, reason, attrs);
            }
            return mInputDeviceVibrators.size() > 0;
        }
    }

    /**
     * Cancel vibration on all {@link InputDevice} with {@link Vibrator} available.
     *
     * @return {@link #isAvailable()}
     */
    public boolean cancelVibrateIfAvailable() {
        synchronized (mLock) {
            for (int i = 0; i < mInputDeviceVibrators.size(); i++) {
                mInputDeviceVibrators.valueAt(i).cancel();
            }
            return mInputDeviceVibrators.size() > 0;
        }
    }

    /**
     * Updates the list of {@link InputDevice} vibrators based on the {@link
     * VibrationSettings#shouldVibrateInputDevices()} setting current value and the
     * devices currently available in {@link InputManager#getInputDeviceIds()}.
     *
     * @return true if there was any change in input devices available or related settings.
     */
    public boolean updateInputDeviceVibrators(boolean vibrateInputDevices) {
        synchronized (mLock) {
            if (vibrateInputDevices == mShouldVibrateInputDevices) {
                // No need to update if settings haven't changed.
                return false;
            }

            mShouldVibrateInputDevices = vibrateInputDevices;
            mInputDeviceVibrators.clear();

            if (vibrateInputDevices) {
                // Register the listener first so any device added/updated/removed after the call to
                // getInputDeviceIds() will trigger the callbacks (which will wait on the lock for
                // this loop to finish).
                mInputManager.registerInputDeviceListener(this, mHandler);

                for (int deviceId : mInputManager.getInputDeviceIds()) {
                    InputDevice device = mInputManager.getInputDevice(deviceId);
                    if (device == null) {
                        continue;
                    }
                    Vibrator vibrator = device.getVibrator();
                    if (vibrator.hasVibrator()) {
                        mInputDeviceVibrators.put(device.getId(), vibrator);
                    }
                }
            } else {
                mInputManager.unregisterInputDeviceListener(this);
            }
        }

        return true;
    }

    private void updateInputDevice(int deviceId) {
        synchronized (mLock) {
            if (!mShouldVibrateInputDevices) {
                // No need to keep this device vibrator if setting is off.
                return;
            }
            InputDevice device = mInputManager.getInputDevice(deviceId);
            if (device == null) {
                mInputDeviceVibrators.remove(deviceId);
                return;
            }
            Vibrator vibrator = device.getVibrator();
            if (vibrator.hasVibrator()) {
                mInputDeviceVibrators.put(deviceId, vibrator);
            } else {
                mInputDeviceVibrators.remove(deviceId);
            }
        }
    }
}
+96 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading