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

Commit 18dd5f0d authored by Patrick Scott's avatar Patrick Scott
Browse files

Improve the Vibrator service by keeping track of multiple vibration requests.

There are 2 types of vibrations: simple and repeated. Simple vibrations run for
a given length of time while repeated patterns run until canceled or the calling
process dies.

If a vibration is currently running and another request is issued, the newer
request always takes precedence unless the current vibration is a simple one and
the time left is longer than the new request.

If a repeating vibration is running and a new request overrides that vibration,
the current vibration is pushed onto a stack. Once the new vibration completes,
the previous vibration resumes. IBinder tokens are used to identify Vibration
requests which means that multiple calls to Vibrator.vibrate with the same
Vibrator object will override previous vibrations on that object.
parent f8e3ba5b
Loading
Loading
Loading
Loading
+2 −2
Original line number Original line Diff line number Diff line
@@ -20,9 +20,9 @@ package android.os;
interface IHardwareService
interface IHardwareService
{
{
    // Vibrator support
    // Vibrator support
    void vibrate(long milliseconds);
    void vibrate(long milliseconds, IBinder token);
    void vibratePattern(in long[] pattern, int repeat, IBinder token);
    void vibratePattern(in long[] pattern, int repeat, IBinder token);
    void cancelVibrate();
    void cancelVibrate(IBinder token);
    
    
    // flashlight support
    // flashlight support
    boolean getFlashlightEnabled();
    boolean getFlashlightEnabled();
+4 −3
Original line number Original line Diff line number Diff line
@@ -24,6 +24,7 @@ package android.os;
public class Vibrator
public class Vibrator
{
{
    IHardwareService mService;
    IHardwareService mService;
    private final Binder mToken = new Binder();


    /** @hide */
    /** @hide */
    public Vibrator()
    public Vibrator()
@@ -40,7 +41,7 @@ public class Vibrator
    public void vibrate(long milliseconds)
    public void vibrate(long milliseconds)
    {
    {
        try {
        try {
            mService.vibrate(milliseconds);
            mService.vibrate(milliseconds, mToken);
        } catch (RemoteException e) {
        } catch (RemoteException e) {
        }
        }
    }
    }
@@ -65,7 +66,7 @@ public class Vibrator
        // anyway
        // anyway
        if (repeat < pattern.length) {
        if (repeat < pattern.length) {
            try {
            try {
                mService.vibratePattern(pattern, repeat, new Binder());
                mService.vibratePattern(pattern, repeat, mToken);
            } catch (RemoteException e) {
            } catch (RemoteException e) {
            }
            }
        } else {
        } else {
@@ -79,7 +80,7 @@ public class Vibrator
    public void cancel()
    public void cancel()
    {
    {
        try {
        try {
            mService.cancelVibrate();
            mService.cancelVibrate(mToken);
        } catch (RemoteException e) {
        } catch (RemoteException e) {
        }
        }
    }
    }
+173 −74
Original line number Original line Diff line number Diff line
@@ -37,6 +37,9 @@ import android.os.Binder;
import android.os.SystemClock;
import android.os.SystemClock;
import android.util.Log;
import android.util.Log;


import java.util.LinkedList;
import java.util.ListIterator;

public class HardwareService extends IHardwareService.Stub {
public class HardwareService extends IHardwareService.Stub {
    private static final String TAG = "HardwareService";
    private static final String TAG = "HardwareService";


@@ -50,9 +53,62 @@ public class HardwareService extends IHardwareService.Stub {
    static final int LIGHT_FLASH_NONE = 0;
    static final int LIGHT_FLASH_NONE = 0;
    static final int LIGHT_FLASH_TIMED = 1;
    static final int LIGHT_FLASH_TIMED = 1;


    private final LinkedList<Vibration> mVibrations;
    private Vibration mCurrentVibration;

    private boolean mAttentionLightOn;
    private boolean mAttentionLightOn;
    private boolean mPulsing;
    private boolean mPulsing;


    private class Vibration implements IBinder.DeathRecipient {
        private final IBinder mToken;
        private final long    mTimeout;
        private final long    mStartTime;
        private final long[]  mPattern;
        private final int     mRepeat;

        Vibration(IBinder token, long millis) {
            this(token, millis, null, 0);
        }

        Vibration(IBinder token, long[] pattern, int repeat) {
            this(token, 0, pattern, repeat);
        }

        private Vibration(IBinder token, long millis, long[] pattern,
                int repeat) {
            mToken = token;
            mTimeout = millis;
            mStartTime = SystemClock.uptimeMillis();
            mPattern = pattern;
            mRepeat = repeat;
        }

        public void binderDied() {
            synchronized (mVibrations) {
                mVibrations.remove(this);
                if (this == mCurrentVibration) {
                    doCancelVibrateLocked();
                    startNextVibrationLocked();
                }
            }
        }

        public boolean hasLongerTimeout(long millis) {
            if (mTimeout == 0) {
                // This is a pattern, return false to play the simple
                // vibration.
                return false;
            }
            if ((mStartTime + mTimeout)
                    < (SystemClock.uptimeMillis() + millis)) {
                // If this vibration will end before the time passed in, let
                // the new vibration play.
                return false;
            }
            return true;
        }
    }

    HardwareService(Context context) {
    HardwareService(Context context) {
        // Reset the hardware to a default state, in case this is a runtime
        // Reset the hardware to a default state, in case this is a runtime
        // restart instead of a fresh boot.
        // restart instead of a fresh boot.
@@ -66,6 +122,8 @@ public class HardwareService extends IHardwareService.Stub {
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        mWakeLock.setReferenceCounted(true);
        mWakeLock.setReferenceCounted(true);


        mVibrations = new LinkedList<Vibration>();

        mBatteryStats = BatteryStatsService.getService();
        mBatteryStats = BatteryStatsService.getService();
        
        
        IntentFilter filter = new IntentFilter();
        IntentFilter filter = new IntentFilter();
@@ -78,13 +136,24 @@ public class HardwareService extends IHardwareService.Stub {
        super.finalize();
        super.finalize();
    }
    }


    public void vibrate(long milliseconds) {
    public void vibrate(long milliseconds, IBinder token) {
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
                != PackageManager.PERMISSION_GRANTED) {
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Requires VIBRATE permission");
            throw new SecurityException("Requires VIBRATE permission");
        }
        }
        doCancelVibrate();
        if (mCurrentVibration != null
        vibratorOn(milliseconds);
                && mCurrentVibration.hasLongerTimeout(milliseconds)) {
            // Ignore this vibration since the current vibration will play for
            // longer than milliseconds.
            return;
        }
        Vibration vib = new Vibration(token, milliseconds);
        synchronized (mVibrations) {
            removeVibrationLocked(token);
            doCancelVibrateLocked();
            mCurrentVibration = vib;
            startVibrationLocked(vib);
        }
    }
    }


    private boolean isAll0(long[] pattern) {
    private boolean isAll0(long[] pattern) {
@@ -121,34 +190,25 @@ public class HardwareService extends IHardwareService.Stub {
                return;
                return;
            }
            }


            synchronized (this) {
            Vibration vib = new Vibration(token, pattern, repeat);
                Death death = new Death(token);
            try {
            try {
                    token.linkToDeath(death, 0);
                token.linkToDeath(vib, 0);
            } catch (RemoteException e) {
            } catch (RemoteException e) {
                return;
                return;
            }
            }


                Thread oldThread = mThread;
            synchronized (mVibrations) {

                removeVibrationLocked(token);
                if (oldThread != null) {
                doCancelVibrateLocked();
                    // stop the old one
                if (repeat >= 0) {
                    synchronized (mThread) {
                    mVibrations.addFirst(vib);
                        mThread.mDone = true;
                    startNextVibrationLocked();
                        mThread.notify();
                } else {
                    }
                    // A negative repeat means that this pattern is not meant
                }
                    // to repeat. Treat it like a simple vibration.

                    mCurrentVibration = vib;
                if (mDeath != null) {
                    startVibrationLocked(vib);
                    mToken.unlinkToDeath(mDeath, 0);
                }
                }

                mDeath = death;
                mToken = token;

                // start the new thread
                mThread = new VibrateThread(pattern, repeat);
                mThread.start();
            }
            }
        }
        }
        finally {
        finally {
@@ -156,7 +216,7 @@ public class HardwareService extends IHardwareService.Stub {
        }
        }
    }
    }


    public void cancelVibrate() {
    public void cancelVibrate(IBinder token) {
        mContext.enforceCallingOrSelfPermission(
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.VIBRATE,
                android.Manifest.permission.VIBRATE,
                "cancelVibrate");
                "cancelVibrate");
@@ -164,7 +224,13 @@ public class HardwareService extends IHardwareService.Stub {
        // so wakelock calls will succeed
        // so wakelock calls will succeed
        long identity = Binder.clearCallingIdentity();
        long identity = Binder.clearCallingIdentity();
        try {
        try {
            doCancelVibrate();
            synchronized (mVibrations) {
                final Vibration vib = removeVibrationLocked(token);
                if (vib == mCurrentVibration) {
                    doCancelVibrateLocked();
                    startNextVibrationLocked();
                }
            }
        }
        }
        finally {
        finally {
            Binder.restoreCallingIdentity(identity);
            Binder.restoreCallingIdentity(identity);
@@ -277,8 +343,17 @@ public class HardwareService extends IHardwareService.Stub {
        }
        }
    };
    };


    private void doCancelVibrate() {
    private final Runnable mVibrationRunnable = new Runnable() {
        synchronized (this) {
        public void run() {
            synchronized (mVibrations) {
                doCancelVibrateLocked();
                startNextVibrationLocked();
            }
        }
    };

    // Lock held on mVibrations
    private void doCancelVibrateLocked() {
        if (mThread != null) {
        if (mThread != null) {
            synchronized (mThread) {
            synchronized (mThread) {
                mThread.mDone = true;
                mThread.mDone = true;
@@ -287,17 +362,55 @@ public class HardwareService extends IHardwareService.Stub {
            mThread = null;
            mThread = null;
        }
        }
        vibratorOff();
        vibratorOff();
        mH.removeCallbacks(mVibrationRunnable);
    }

    // Lock held on mVibrations
    private void startNextVibrationLocked() {
        if (mVibrations.size() <= 0) {
            return;
        }
        mCurrentVibration = mVibrations.getFirst();
        startVibrationLocked(mCurrentVibration);
    }

    // Lock held on mVibrations
    private void startVibrationLocked(final Vibration vib) {
        if (vib.mTimeout != 0) {
            vibratorOn(vib.mTimeout);
            mH.postDelayed(mVibrationRunnable, vib.mTimeout);
        } else {
            // mThread better be null here. doCancelVibrate should always be
            // called before startNextVibrationLocked or startVibrationLocked.
            mThread = new VibrateThread(vib);
            mThread.start();
        }
        }
    }
    }


    // Lock held on mVibrations
    private Vibration removeVibrationLocked(IBinder token) {
        ListIterator<Vibration> iter = mVibrations.listIterator(0);
        while (iter.hasNext()) {
            Vibration vib = iter.next();
            if (vib.mToken == token) {
                iter.remove();
                return vib;
            }
        }
        // We might be looking for a simple vibration which is only stored in
        // mCurrentVibration.
        if (mCurrentVibration != null && mCurrentVibration.mToken == token) {
            return mCurrentVibration;
        }
        return null;
    }

    private class VibrateThread extends Thread {
    private class VibrateThread extends Thread {
        long[] mPattern;
        final Vibration mVibration;
        int mRepeat;
        boolean mDone;
        boolean mDone;
    
    
        VibrateThread(long[] pattern, int repeat) {
        VibrateThread(Vibration vib) {
            mPattern = pattern;
            mVibration = vib;
            mRepeat = repeat;
            mWakeLock.acquire();
            mWakeLock.acquire();
        }
        }


@@ -323,8 +436,9 @@ public class HardwareService extends IHardwareService.Stub {
            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
            Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_DISPLAY);
            synchronized (this) {
            synchronized (this) {
                int index = 0;
                int index = 0;
                long[] pattern = mPattern;
                long[] pattern = mVibration.mPattern;
                int len = pattern.length;
                int len = pattern.length;
                int repeat = mVibration.mRepeat;
                long duration = 0;
                long duration = 0;


                while (!mDone) {
                while (!mDone) {
@@ -347,50 +461,37 @@ public class HardwareService extends IHardwareService.Stub {
                            HardwareService.this.vibratorOn(duration);
                            HardwareService.this.vibratorOn(duration);
                        }
                        }
                    } else {
                    } else {
                        if (mRepeat < 0) {
                        if (repeat < 0) {
                            break;
                            break;
                        } else {
                        } else {
                            index = mRepeat;
                            index = repeat;
                            duration = 0;
                            duration = 0;
                        }
                        }
                    }
                    }
                }
                }
                if (mDone) {
                    // make sure vibrator is off if we were cancelled.
                    // otherwise, it will turn off automatically 
                    // when the last timeout expires.
                    HardwareService.this.vibratorOff();
                }
                mWakeLock.release();
                mWakeLock.release();
            }
            }
            synchronized (HardwareService.this) {
            synchronized (mVibrations) {
                if (mThread == this) {
                if (mThread == this) {
                    mThread = null;
                    mThread = null;
                }
                }
            }
                if (!mDone) {
        }
                    // If this vibration finished naturally, start the next
    };
                    // vibration.

                    mVibrations.remove(mVibration);
    private class Death implements IBinder.DeathRecipient {
                    startNextVibrationLocked();
        IBinder mMe;

        Death(IBinder me) {
            mMe = me;
        }

        public void binderDied() {
            synchronized (HardwareService.this) {
                if (mMe == mToken) {
                    doCancelVibrate();
                }
                }
                }
            }
            }
        }
        }
    };


    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
    BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
            if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                doCancelVibrate();
                synchronized (mVibrations) {
                    doCancelVibrateLocked();
                    mVibrations.clear();
                }
            }
            }
        }
        }
    };
    };
@@ -407,8 +508,6 @@ public class HardwareService extends IHardwareService.Stub {
    private final IBatteryStats mBatteryStats;
    private final IBatteryStats mBatteryStats;
    
    
    volatile VibrateThread mThread;
    volatile VibrateThread mThread;
    volatile Death mDeath;
    volatile IBinder mToken;


    private int mNativePointer;
    private int mNativePointer;


+2 −2
Original line number Original line Diff line number Diff line
@@ -46,7 +46,7 @@ public class HardwareServicePermissionTest extends TestCase {
     */
     */
    public void testVibrate() throws RemoteException {
    public void testVibrate() throws RemoteException {
        try {
        try {
            mHardwareService.vibrate(2000);
            mHardwareService.vibrate(2000, new Binder());
            fail("vibrate did not throw SecurityException as expected");
            fail("vibrate did not throw SecurityException as expected");
        } catch (SecurityException e) {
        } catch (SecurityException e) {
            // expected
            // expected
@@ -77,7 +77,7 @@ public class HardwareServicePermissionTest extends TestCase {
     */
     */
    public void testCancelVibrate() throws RemoteException {
    public void testCancelVibrate() throws RemoteException {
        try {
        try {
            mHardwareService.cancelVibrate();
            mHardwareService.cancelVibrate(new Binder());
            fail("cancelVibrate did not throw SecurityException as expected");
            fail("cancelVibrate did not throw SecurityException as expected");
        } catch (SecurityException e) {
        } catch (SecurityException e) {
            // expected
            // expected