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

Commit 9213b9f5 authored by Demon000's avatar Demon000 Committed by Luca Stefani
Browse files

FODCircleView: rewrite and simplify implementation

Change-Id: Ie0859cc0e5b558b9446db5ec915c1f9f6608adad
parent 112d466a
Loading
Loading
Loading
Loading
+143 −276
Original line number Diff line number Diff line
@@ -24,19 +24,15 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.biometrics.BiometricSourceType;
import android.os.Handler;
import android.os.IHwBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.provider.Settings;
import android.view.Display;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.widget.ImageView;

@@ -51,15 +47,14 @@ import java.util.NoSuchElementException;
import java.util.Timer;
import java.util.TimerTask;

public class FODCircleView extends ImageView implements OnTouchListener {
public class FODCircleView extends ImageView {
    private final int mPositionX;
    private final int mPositionY;
    private final int mWidth;
    private final int mHeight;
    private final int mSize;
    private final int mDreamingMaxOffset;
    private final int mNavigationBarSize;
    private final boolean mShouldBoostBrightness;
    private final Paint mPaintFingerprint = new Paint();
    private final Paint mPaintShow = new Paint();
    private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
    private final WindowManager mWindowManager;

@@ -67,16 +62,11 @@ public class FODCircleView extends ImageView implements OnTouchListener {

    private int mDreamingOffsetX;
    private int mDreamingOffsetY;
    private int mNavigationBarSize;

    private boolean mIsBouncer;
    private boolean mIsDreaming;
    private boolean mIsInsideCircle;
    private boolean mIsPressed;
    private boolean mIsPulsing;
    private boolean mIsScreenOn;
    private boolean mIsViewAdded;
    private boolean mIsRemoving;
    private boolean mIsShowing;
    private boolean mIsCircleShowing;

    private Handler mHandler;

@@ -86,26 +76,12 @@ public class FODCircleView extends ImageView implements OnTouchListener {
            new IFingerprintInscreenCallback.Stub() {
        @Override
        public void onFingerDown() {
            mIsInsideCircle = true;

            mHandler.post(() -> {
                setDim(true);
                setImageDrawable(null);

                invalidate();
            });
            mHandler.post(() -> showCircle());
        }

        @Override
        public void onFingerUp() {
            mIsInsideCircle = false;

            mHandler.post(() -> {
                setDim(false);
                setImageResource(R.drawable.fod_icon_default);

                invalidate();
            });
            mHandler.post(() -> hideCircle());
        }
    };

@@ -114,60 +90,21 @@ public class FODCircleView extends ImageView implements OnTouchListener {
    private KeyguardUpdateMonitorCallback mMonitorCallback = new KeyguardUpdateMonitorCallback() {
        @Override
        public void onDreamingStateChanged(boolean dreaming) {
            super.onDreamingStateChanged(dreaming);
            mIsDreaming = dreaming;
            mIsInsideCircle = false;
            updateAlpha();

            if (dreaming) {
                mBurnInProtectionTimer = new Timer();
                mBurnInProtectionTimer.schedule(new BurnInProtectionTask(), 0, 60 * 1000);
            } else if (mBurnInProtectionTimer != null) {
                mBurnInProtectionTimer.cancel();
            }

            if (mIsViewAdded) {
                resetPosition();
                invalidate();
            }
        }

        @Override
        public void onScreenTurnedOff() {
            super.onScreenTurnedOff();
            mIsInsideCircle = false;
        }

        @Override
        public void onStartedGoingToSleep(int why) {
            super.onStartedGoingToSleep(why);
            mIsInsideCircle = false;
        }

        @Override
        public void onFinishedGoingToSleep(int why) {
            super.onFinishedGoingToSleep(why);
        }

        @Override
        public void onStartedWakingUp() {
            super.onStartedWakingUp();
        }

        @Override
        public void onScreenTurnedOn() {
            super.onScreenTurnedOn();
            mIsScreenOn = true;
            mIsInsideCircle = false;
        }

        @Override
        public void onKeyguardVisibilityChanged(boolean showing) {
            super.onKeyguardVisibilityChanged(showing);
            mIsInsideCircle = false;
        }

        @Override
        public void onKeyguardBouncerChanged(boolean isBouncer) {
            mIsBouncer = isBouncer;

            if (isBouncer) {
                hide();
            } else if (mUpdateMonitor.isFingerprintDetectionRunning()) {
@@ -176,69 +113,57 @@ public class FODCircleView extends ImageView implements OnTouchListener {
        }

        @Override
        public void onStrongAuthStateChanged(int userId) {
            super.onStrongAuthStateChanged(userId);
        }

        @Override
        public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
            super.onBiometricAuthenticated(userId, biometricSourceType);
            mIsInsideCircle = false;
        }

        @Override
        public void onBiometricRunningStateChanged(boolean running,
                BiometricSourceType biometricSourceType) {
            super.onBiometricRunningStateChanged(running, biometricSourceType);
            if (running) {
                show();
            } else {
                hide();
            }
        public void onScreenTurnedOff() {
            hideCircle();
        }
    };

    public FODCircleView(Context context) {
        super(context);

        IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
        if (daemon == null) {
            throw new RuntimeException("Unable to get IFingerprintInscreen");
        }

        try {
            mShouldBoostBrightness = daemon.shouldBoostBrightness();
            mPositionX = daemon.getPositionX();
            mPositionY = daemon.getPositionY();
            mSize = daemon.getSize();
        } catch (RemoteException e) {
            throw new RuntimeException("Failed to retrieve FOD circle position or size");
        }

        Resources res = context.getResources();

        mPaintFingerprint.setAntiAlias(true);
        mPaintFingerprint.setColor(res.getColor(R.color.config_fodColor));

        setImageResource(R.drawable.fod_icon_default);

        mPaintShow.setAntiAlias(true);
        mPaintShow.setColor(res.getColor(R.color.config_fodColor));

        setOnTouchListener(this);

        mWindowManager = context.getSystemService(WindowManager.class);

        mNavigationBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size);

        try {
            IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
            if (daemon == null) {
                throw new RuntimeException("Unable to get IFingerprintInscreen");
            }
            mPositionX = daemon.getPositionX();
            mPositionY = daemon.getPositionY();
            mWidth = daemon.getSize();
            mHeight = mWidth; // We do not expect mWidth != mHeight
        mDreamingMaxOffset = (int) (mSize * 0.1f);

            mShouldBoostBrightness = daemon.shouldBoostBrightness();
        } catch (NoSuchElementException | RemoteException e) {
            throw new RuntimeException(e);
        }
        mHandler = new Handler(Looper.getMainLooper());

        if (mPositionX < 0 || mPositionY < 0 || mWidth < 0 || mHeight < 0) {
            throw new RuntimeException("Invalid FOD circle position or size.");
        }
        mParams.height = mSize;
        mParams.width = mSize;
        mParams.format = PixelFormat.TRANSLUCENT;

        mDreamingMaxOffset = (int) (mWidth * 0.1f);
        mParams.setTitle("Fingerprint on display");
        mParams.packageName = "android";
        mParams.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_DIM_BEHIND |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        mParams.gravity = Gravity.TOP | Gravity.LEFT;

        mHandler = new Handler(Looper.getMainLooper());
        mWindowManager.addView(this, mParams);

        updatePosition();
        hide();

        mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
        mUpdateMonitor.registerCallback(mMonitorCallback);
@@ -248,195 +173,145 @@ public class FODCircleView extends ImageView implements OnTouchListener {
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mIsInsideCircle) {
            canvas.drawCircle(mWidth / 2, mHeight / 2, (float) (mWidth / 2.0f), mPaintFingerprint);
        if (mIsCircleShowing) {
            canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2.0f, mPaintFingerprint);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        // onLayout is a good time to call the HAL because dim layer
        // added by setDim() should have come into effect
        // the HAL is expected (if supported) to set the screen brightness
        // to maximum / minimum immediately when called
        if (mIsInsideCircle) {
            if (mIsDreaming) {
                setAlpha(1.0f);
            }
            if (!mIsPressed) {
                IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
                if (daemon != null) {
                    try {
                        daemon.onPress();
                    } catch (RemoteException e) {
                        // do nothing
                    }
                }
                mIsPressed = true;
            }
        } else {
            setAlpha(mIsDreaming ? 0.5f : 1.0f);
            if (mIsPressed) {
                IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
                if (daemon != null) {
                    try {
                        daemon.onRelease();
                    } catch (RemoteException e) {
                        // do nothing
                    }
                }
                mIsPressed = false;
            }

            if (mIsRemoving) {
                mIsRemoving = false;
                mWindowManager.removeView(this);
            }
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getAxisValue(MotionEvent.AXIS_X);
        float y = event.getAxisValue(MotionEvent.AXIS_Y);

        boolean newInside = (x > 0 && x < mWidth) && (y > 0 && y < mWidth);
        boolean newIsInside = (x > 0 && x < mSize) && (y > 0 && y < mSize);

        if (event.getAction() == MotionEvent.ACTION_UP) {
            newInside = false;
            setDim(false);
            setImageResource(R.drawable.fod_icon_default);
        }

        if (newInside == mIsInsideCircle) {
            return mIsInsideCircle;
        if (event.getAction() == MotionEvent.ACTION_DOWN && newIsInside) {
            showCircle();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            hideCircle();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_MOVE) {
            return true;
        }

        mIsInsideCircle = newInside;

        invalidate();

        if (!mIsInsideCircle) {
            setImageResource(R.drawable.fod_icon_default);
        return false;
    }

        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            setDim(true);
            setImageDrawable(null);
    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        updatePosition();
    }

        return true;
    public IFingerprintInscreen getFingerprintInScreenDaemon() {
        if (mFingerprintInscreenDaemon == null) {
            try {
                mFingerprintInscreenDaemon = IFingerprintInscreen.getService();
                if (mFingerprintInscreenDaemon != null) {
                    mFingerprintInscreenDaemon.setCallback(mFingerprintInscreenCallback);
                    mFingerprintInscreenDaemon.asBinder().linkToDeath((cookie) -> {
                        mFingerprintInscreenDaemon = null;
                    }, 0);
                }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        if (mIsViewAdded) {
            resetPosition();
            mWindowManager.updateViewLayout(this, mParams);
            } catch (NoSuchElementException | RemoteException e) {
                // do nothing
            }
        }
        return mFingerprintInscreenDaemon;
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();

    public void dispatchPress() {
        IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
        if (daemon != null) {
        try {
                daemon.onHideFODView();
            daemon.onPress();
        } catch (RemoteException e) {
            // do nothing
        }
    }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

    public void dispatchRelease() {
        IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
        if (daemon != null) {
        try {
                daemon.onShowFODView();
            daemon.onRelease();
        } catch (RemoteException e) {
            // do nothing
        }
    }
    }

    public synchronized IFingerprintInscreen getFingerprintInScreenDaemon() {
        if (mFingerprintInscreenDaemon == null) {
    public void dispatchShow() {
        IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
        try {
                mFingerprintInscreenDaemon = IFingerprintInscreen.getService();
                if (mFingerprintInscreenDaemon != null) {
                    mFingerprintInscreenDaemon.setCallback(mFingerprintInscreenCallback);
                    mFingerprintInscreenDaemon.asBinder().linkToDeath((cookie) -> {
                        mFingerprintInscreenDaemon = null;
                    }, 0);
                }
            } catch (NoSuchElementException | RemoteException e) {
            daemon.onShowFODView();
        } catch (RemoteException e) {
            // do nothing
        }
    }
        return mFingerprintInscreenDaemon;
    }

    public void show() {
        if (mIsRemoving) {
            // Last removal hasn't been finished yet
            mIsRemoving = false;
            mWindowManager.removeView(this);
    public void dispatchHide() {
        IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
        try {
            daemon.onHideFODView();
        } catch (RemoteException e) {
            // do nothing
        }

        if (mIsViewAdded) {
            return;
    }

        if (mIsBouncer) {
            return;
        }
    public void showCircle() {
        mIsCircleShowing = true;

        resetPosition();
        setKeepScreenOn(true);

        mParams.height = mWidth;
        mParams.width = mHeight;
        mParams.format = PixelFormat.TRANSLUCENT;
        setDim(true);
        updateAlpha();
        dispatchPress();

        mParams.setTitle("Fingerprint on display");
        mParams.packageName = "android";
        mParams.type = WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
        mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
                WindowManager.LayoutParams.FLAG_DIM_BEHIND |
                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        mParams.gravity = Gravity.TOP | Gravity.LEFT;
        setImageDrawable(null);
        invalidate();
    }

    public void hideCircle() {
        mIsCircleShowing = false;

        setImageResource(R.drawable.fod_icon_default);
        invalidate();

        mWindowManager.addView(this, mParams);
        mIsViewAdded = true;
        dispatchRelease();

        mIsPressed = false;
        setDim(false);
        updateAlpha();

        setKeepScreenOn(false);
    }

    public void hide() {
        if (!mIsViewAdded) {
    public void show() {
        if (mIsBouncer) {
            // Ignore show calls when Keyguard pin screen is being shown
            return;
        }

        mIsInsideCircle = false;
        mIsViewAdded = false;
        // Postpone removal to next re-layout to avoid blinking
        mIsRemoving = true;
        setDim(false);
        invalidate();
        mIsShowing = true;

        dispatchShow();
        setVisibility(View.VISIBLE);
    }

    private void resetPosition() {
    public void hide() {
        mIsShowing = false;

        setVisibility(View.GONE);
        hideCircle();
        dispatchHide();
    }

    private void updateAlpha() {
        if (mIsCircleShowing) {
            setAlpha(1.0f);
        } else {
            setAlpha(mIsDreaming ? 0.5f : 1.0f);
        }
    }

    private void updatePosition() {
        Display defaultDisplay = mWindowManager.getDefaultDisplay();

        Point size = new Point();
@@ -454,10 +329,10 @@ public class FODCircleView extends ImageView implements OnTouchListener {
                break;
            case Surface.ROTATION_180:
                mParams.x = mPositionX;
                mParams.y = size.y - mPositionY - mHeight;
                mParams.y = size.y - mPositionY - mSize;
                break;
            case Surface.ROTATION_270:
                mParams.x = size.x - mPositionY - mWidth - mNavigationBarSize;
                mParams.x = size.x - mPositionY - mSize - mNavigationBarSize;
                mParams.y = mPositionX;
                break;
            default:
@@ -469,10 +344,8 @@ public class FODCircleView extends ImageView implements OnTouchListener {
            mParams.y += mDreamingOffsetY;
        }

        if (mIsViewAdded) {
        mWindowManager.updateViewLayout(this, mParams);
    }
    }

    private void setDim(boolean dim) {
        if (dim) {
@@ -481,51 +354,45 @@ public class FODCircleView extends ImageView implements OnTouchListener {
            int dimAmount = 0;

            IFingerprintInscreen daemon = getFingerprintInScreenDaemon();
            if (daemon != null) {
            try {
                dimAmount = daemon.getDimAmount(curBrightness);
            } catch (RemoteException e) {
                // do nothing
            }
            }

            if (mShouldBoostBrightness) {
                mParams.screenBrightness = 1.0f;
            }

            mParams.dimAmount = ((float) dimAmount) / 255.0f;
            mParams.dimAmount = dimAmount / 255.0f;
        } else {
            mParams.screenBrightness = 0.0f;
            mParams.dimAmount = 0.0f;
        }

        try {
        mWindowManager.updateViewLayout(this, mParams);
        } catch (IllegalArgumentException e) {
            // do nothing
        }
    }

    private class BurnInProtectionTask extends TimerTask {
        @Override
        public void run() {
            // It is fine to modify the variables here because
            // no other thread will be modifying it
            long now = System.currentTimeMillis() / 1000 / 60;

            mDreamingOffsetX = (int) (now % (mDreamingMaxOffset * 4));
            if (mDreamingOffsetX > mDreamingMaxOffset * 2) {
                mDreamingOffsetX = mDreamingMaxOffset * 4 - mDreamingOffsetX;
            }

            // Let y to be not synchronized with x, so that we get maximum movement
            mDreamingOffsetY = (int) ((now + mDreamingMaxOffset / 3) % (mDreamingMaxOffset * 2));
            if (mDreamingOffsetY > mDreamingMaxOffset * 2) {
                mDreamingOffsetY = mDreamingMaxOffset * 4 - mDreamingOffsetY;
            }

            mDreamingOffsetX -= mDreamingMaxOffset;
            mDreamingOffsetY -= mDreamingMaxOffset;
            if (mIsViewAdded) {
                mHandler.post(() -> resetPosition());
            }

            mHandler.post(() -> updatePosition());
        }
    };
}