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

Commit 678a1252 authored by Jeff Brown's avatar Jeff Brown
Browse files

Split up the event synthesis code by source.

The goal is to better encapsulate this code to make it easier
to maintain and to facilitate some upcoming changes.

Some of the variables have been renamed but the logic is unchanged.

Bug: 8583760
Change-Id: I45501f7dabebcb938e42c386291d615d088a4c8c
parent d7094ea2
Loading
Loading
Loading
Loading
+0 −298
Original line number Diff line number Diff line
/*
 * Copyright (C) 2012 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 android.view;

import android.app.SearchManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;

/**
 * This class creates DPAD events from TouchNavigation events.
 * 
 * @see ViewRootImpl
 */

//TODO: Make this class an internal class of ViewRootImpl.java
class SimulatedDpad {

    private static final String TAG = "SimulatedDpad";

    // Maximum difference in milliseconds between the down and up of a touch
    // event for it to be considered a tap
    // TODO:Read this value from a configuration file
    private static final int MAX_TAP_TIME = 250;
    // Where the cutoff is for determining an edge swipe
    private static final float EDGE_SWIPE_THRESHOLD = 0.9f;
    private static final int MSG_FLICK = 313;
    // TODO: Pass touch slop from the input device
    private static final int TOUCH_SLOP = 30;
    // The position of the previous TouchNavigation event
    private float mLastTouchNavigationXPosition;
    private float mLastTouchNavigationYPosition;
    // Where the Touch Navigation was initially pressed
    private float mTouchNavigationEnterXPosition;
    private float mTouchNavigationEnterYPosition;
    // When the most recent ACTION_HOVER_ENTER occurred
    private long mLastTouchNavigationStartTimeMs = 0;
    // When the most recent direction key was sent
    private long mLastTouchNavigationKeySendTimeMs = 0;
    // When the most recent touch event of any type occurred
    private long mLastTouchNavigationEventTimeMs = 0;
    // Did the swipe begin in a valid region
    private boolean mEdgeSwipePossible;

    private final Context mContext;

    // How quickly keys were sent;
    private int mKeySendRateMs = 0;
    private int mLastKeySent;
    // Last movement in device screen pixels
    private float mLastMoveX = 0;
    private float mLastMoveY = 0;
    // Offset from the initial touch. Gets reset as direction keys are sent.
    private float mAccumulatedX;
    private float mAccumulatedY;

    // Change in position allowed during tap events
    private float mTouchSlop;
    private float mTouchSlopSquared;
    // Has the TouchSlop constraint been invalidated
    private boolean mAlwaysInTapRegion = true;

    // Information from the most recent event.
    // Used to determine what device sent the event during a fling.
    private int mLastSource;
    private int mLastMetaState;
    private int mLastDeviceId;

    // TODO: Currently using screen dimensions tuned to a Galaxy Nexus, need to
    // read this from a config file instead
    private int mDistancePerTick;
    private int mDistancePerTickSquared;
    // Highest rate that the flinged events can occur at before dying out
    private int mMaxRepeatDelay;
    // The square of the minimum distance needed for a flick to register
    private int mMinFlickDistanceSquared;
    // How quickly the repeated events die off
    private float mFlickDecay;

    public SimulatedDpad(Context context) {
        mDistancePerTick = SystemProperties.getInt("persist.vr_dist_tick", 64);
        mDistancePerTickSquared = mDistancePerTick * mDistancePerTick;
        mMaxRepeatDelay = SystemProperties.getInt("persist.vr_repeat_delay", 300);
        mMinFlickDistanceSquared = SystemProperties.getInt("persist.vr_min_flick", 20);
        mMinFlickDistanceSquared *= mMinFlickDistanceSquared;
        mFlickDecay = Float.parseFloat(SystemProperties.get(
                "persist.sys.vr_flick_decay", "1.3"));
        mTouchSlop = TOUCH_SLOP;
        mTouchSlopSquared = mTouchSlop * mTouchSlop;

        mContext = context;
    }

    private final Handler mHandler = new Handler(true /*async*/) {
            @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_FLICK: {
                    final long time = SystemClock.uptimeMillis();
                    ViewRootImpl viewroot = (ViewRootImpl) msg.obj;
                    // Send the key
                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
                            KeyEvent.ACTION_DOWN, msg.arg2, 0, mLastMetaState,
                            mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));
                    viewroot.enqueueInputEvent(new KeyEvent(time, time,
                            KeyEvent.ACTION_UP, msg.arg2, 0, mLastMetaState,
                            mLastDeviceId, 0, KeyEvent.FLAG_FALLBACK, mLastSource));

                    // Increase the delay by the decay factor and resend
                    final int delay = (int) Math.ceil(mFlickDecay * msg.arg1);
                    if (delay <= mMaxRepeatDelay) {
                        Message msgCopy = Message.obtain(msg);
                        msgCopy.arg1 = delay;
                        msgCopy.setAsynchronous(true);
                        mHandler.sendMessageDelayed(msgCopy, delay);
                    }
                    break;
                }
            }
        }
    };

    public void updateTouchNavigation(ViewRootImpl viewroot, MotionEvent event,
            boolean synthesizeNewKeys) {
        if (!synthesizeNewKeys) {
            mHandler.removeMessages(MSG_FLICK);
        }
        InputDevice device = event.getDevice();
        if (device == null) {
            return;
        }
        // Store what time the TouchNavigation event occurred
        final long time = SystemClock.uptimeMillis();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastTouchNavigationStartTimeMs = time;
                mAlwaysInTapRegion = true;
                mTouchNavigationEnterXPosition = event.getX();
                mTouchNavigationEnterYPosition = event.getY();
                mAccumulatedX = 0;
                mAccumulatedY = 0;
                mLastMoveX = 0;
                mLastMoveY = 0;
                if (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
                        * EDGE_SWIPE_THRESHOLD < event.getY()) {
                    // Did the swipe begin in a valid region
                    mEdgeSwipePossible = true;
                }
                // Clear any flings
                if (synthesizeNewKeys) {
                    mHandler.removeMessages(MSG_FLICK);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                // Determine whether the move is slop or an intentional move
                float deltaX = event.getX() - mTouchNavigationEnterXPosition;
                float deltaY = event.getY() - mTouchNavigationEnterYPosition;
                if (mTouchSlopSquared < deltaX * deltaX + deltaY * deltaY) {
                    mAlwaysInTapRegion = false;
                }
                // Checks if the swipe has crossed the midpoint
                // and if our swipe gesture is complete
                if (event.getY() < (device.getMotionRange(MotionEvent.AXIS_Y).getMax()
                        * .5) && mEdgeSwipePossible) {
                    mEdgeSwipePossible = false;

                    Intent intent =
                            ((SearchManager)mContext.getSystemService(Context.SEARCH_SERVICE))
                            .getAssistIntent(mContext, false, UserHandle.USER_CURRENT_OR_SELF);
                    if (intent != null) {
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        try {
                            mContext.startActivity(intent);
                        } catch (ActivityNotFoundException e){
                            Log.e(TAG, "Could not start search activity");
                        }
                    } else {
                        Log.e(TAG, "Could not find a search activity");
                    }
                }
                // Find the difference in position between the two most recent
                // TouchNavigation events
                mLastMoveX = event.getX() - mLastTouchNavigationXPosition;
                mLastMoveY = event.getY() - mLastTouchNavigationYPosition;
                mAccumulatedX += mLastMoveX;
                mAccumulatedY += mLastMoveY;
                float mAccumulatedXSquared = mAccumulatedX * mAccumulatedX;
                float mAccumulatedYSquared = mAccumulatedY * mAccumulatedY;
                // Determine if we've moved far enough to send a key press
                if (mAccumulatedXSquared > mDistancePerTickSquared ||
                        mAccumulatedYSquared > mDistancePerTickSquared) {
                    float dominantAxis;
                    float sign;
                    boolean isXAxis;
                    int key;
                    int repeatCount = 0;
                    // Determine dominant axis
                    if (mAccumulatedXSquared > mAccumulatedYSquared) {
                        dominantAxis = mAccumulatedX;
                        isXAxis = true;
                    } else {
                        dominantAxis = mAccumulatedY;
                        isXAxis = false;
                    }
                    // Determine sign of axis
                    sign = (dominantAxis > 0) ? 1 : -1;
                    // Determine key to send
                    if (isXAxis) {
                        key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_RIGHT :
                                KeyEvent.KEYCODE_DPAD_LEFT;
                    } else {
                        key = (sign == 1) ? KeyEvent.KEYCODE_DPAD_DOWN : KeyEvent.KEYCODE_DPAD_UP;
                    }
                    // Send key until maximum distance constraint is satisfied
                    while (dominantAxis * dominantAxis > mDistancePerTickSquared) {
                        repeatCount++;
                        dominantAxis -= sign * mDistancePerTick;
                        if (synthesizeNewKeys) {
                            viewroot.enqueueInputEvent(new KeyEvent(time, time,
                                    KeyEvent.ACTION_DOWN, key, 0, event.getMetaState(),
                                    event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
                                    event.getSource()));
                            viewroot.enqueueInputEvent(new KeyEvent(time, time,
                                    KeyEvent.ACTION_UP, key, 0, event.getMetaState(),
                                    event.getDeviceId(), 0, KeyEvent.FLAG_FALLBACK,
                                    event.getSource()));
                        }
                    }
                    // Save new axis values
                    mAccumulatedX = isXAxis ? dominantAxis : 0;
                    mAccumulatedY = isXAxis ? 0 : dominantAxis;

                    mLastKeySent = key;
                    mKeySendRateMs = (int) (time - mLastTouchNavigationKeySendTimeMs) / repeatCount;
                    mLastTouchNavigationKeySendTimeMs = time;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (time - mLastTouchNavigationStartTimeMs < MAX_TAP_TIME && mAlwaysInTapRegion) {
                    if (synthesizeNewKeys) {
                        viewroot.enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs,
                                    time, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, 0,
                                    event.getMetaState(), event.getDeviceId(), 0,
                                    KeyEvent.FLAG_FALLBACK, event.getSource()));
                        viewroot.enqueueInputEvent(new KeyEvent(mLastTouchNavigationStartTimeMs,
                                    time, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, 0,
                                    event.getMetaState(), event.getDeviceId(), 0,
                                    KeyEvent.FLAG_FALLBACK, event.getSource()));
                    }
                } else {
                    float xMoveSquared = mLastMoveX * mLastMoveX;
                    float yMoveSquared = mLastMoveY * mLastMoveY;
                    // Determine whether the last gesture was a fling.
                    if (mMinFlickDistanceSquared <= xMoveSquared + yMoveSquared &&
                            time - mLastTouchNavigationEventTimeMs <= MAX_TAP_TIME &&
                            mKeySendRateMs <= mMaxRepeatDelay && mKeySendRateMs > 0) {
                        mLastDeviceId = event.getDeviceId();
                        mLastSource = event.getSource();
                        mLastMetaState = event.getMetaState();

                        if (synthesizeNewKeys) {
                            Message message = Message.obtain(mHandler, MSG_FLICK,
                                    mKeySendRateMs, mLastKeySent, viewroot);
                            message.setAsynchronous(true);
                            mHandler.sendMessageDelayed(message, mKeySendRateMs);
                        }
                    }
                }
                mEdgeSwipePossible = false;
                break;
        }

        // Store touch event position and time
        mLastTouchNavigationEventTimeMs = time;
        mLastTouchNavigationXPosition = event.getX();
        mLastTouchNavigationYPosition = event.getY();
    }
}
+561 −271

File changed.

Preview size limit exceeded, changes collapsed.