/*
|
* Copyright (C) 2018 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.launcher3.touch;
|
|
import android.graphics.PointF;
|
import android.util.Log;
|
import android.util.Pair;
|
import android.util.SparseArray;
|
import android.view.MotionEvent;
|
import android.view.MotionEvent.PointerCoords;
|
import android.view.MotionEvent.PointerProperties;
|
|
import java.util.function.Consumer;
|
|
/**
|
* To minimize the size of the MotionEvent, historic events are not copied and passed via the
|
* listener.
|
*/
|
public class TouchEventTranslator {
|
|
private static final String TAG = "TouchEventTranslator";
|
private static final boolean DEBUG = false;
|
|
private class DownState {
|
long timeStamp;
|
float downX;
|
float downY;
|
public DownState(long timeStamp, float downX, float downY) {
|
this.timeStamp = timeStamp;
|
this.downX = downX;
|
this.downY = downY;
|
}
|
};
|
private final DownState ZERO = new DownState(0, 0f, 0f);
|
|
private final Consumer<MotionEvent> mListener;
|
|
private final SparseArray<DownState> mDownEvents;
|
private final SparseArray<PointF> mFingers;
|
|
private final SparseArray<Pair<PointerProperties[], PointerCoords[]>> mCache;
|
|
public TouchEventTranslator(Consumer<MotionEvent> listener) {
|
mDownEvents = new SparseArray<>();
|
mFingers = new SparseArray<>();
|
mCache = new SparseArray<>();
|
|
mListener = listener;
|
}
|
|
public void reset() {
|
mDownEvents.clear();
|
mFingers.clear();
|
}
|
|
public float getDownX() {
|
return mDownEvents.get(0).downX;
|
}
|
|
public float getDownY() {
|
return mDownEvents.get(0).downY;
|
}
|
|
public void setDownParameters(int idx, MotionEvent e) {
|
DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx));
|
mDownEvents.append(idx, ev);
|
}
|
|
public void dispatchDownEvents(MotionEvent ev) {
|
for(int i = 0; i < ev.getPointerCount() && i < mDownEvents.size(); i++) {
|
int pid = ev.getPointerId(i);
|
put(pid, i, ev.getX(i), 0, mDownEvents.get(i).timeStamp, ev);
|
}
|
}
|
|
public void processMotionEvent(MotionEvent ev) {
|
if (DEBUG) {
|
printSamples(TAG + " processMotionEvent", ev);
|
}
|
int index = ev.getActionIndex();
|
float x = ev.getX(index);
|
float y = ev.getY(index) - mDownEvents.get(index, ZERO).downY;
|
switch (ev.getActionMasked()) {
|
case MotionEvent.ACTION_POINTER_DOWN:
|
int pid = ev.getPointerId(index);
|
if(mFingers.get(pid, null) != null) {
|
for(int i=0; i < ev.getPointerCount(); i++) {
|
pid = ev.getPointerId(i);
|
position(pid, x, y);
|
}
|
generateEvent(ev.getAction(), ev);
|
} else {
|
put(pid, index, x, y, ev);
|
}
|
break;
|
case MotionEvent.ACTION_MOVE:
|
for(int i=0; i < ev.getPointerCount(); i++) {
|
pid = ev.getPointerId(i);
|
position(pid, x, y);
|
}
|
generateEvent(ev.getAction(), ev);
|
break;
|
case MotionEvent.ACTION_POINTER_UP:
|
case MotionEvent.ACTION_UP:
|
pid = ev.getPointerId(index);
|
lift(pid, index, x, y, ev);
|
break;
|
case MotionEvent.ACTION_CANCEL:
|
cancel(ev);
|
break;
|
default:
|
Log.v(TAG, "Didn't process ");
|
printSamples(TAG, ev);
|
|
}
|
}
|
|
private TouchEventTranslator put(int id, int index, float x, float y, MotionEvent ev) {
|
return put(id, index, x, y, ev.getEventTime(), ev);
|
}
|
|
private TouchEventTranslator put(int id, int index, float x, float y, long ms, MotionEvent ev) {
|
checkFingerExistence(id, false);
|
boolean isInitialDown = (mFingers.size() == 0);
|
|
mFingers.put(id, new PointF(x, y));
|
int n = mFingers.size();
|
|
if (mCache.get(n) == null) {
|
PointerProperties[] properties = new PointerProperties[n];
|
PointerCoords[] coords = new PointerCoords[n];
|
for (int i = 0; i < n; i++) {
|
properties[i] = new PointerProperties();
|
coords[i] = new PointerCoords();
|
}
|
mCache.put(n, new Pair(properties, coords));
|
}
|
|
int action;
|
if (isInitialDown) {
|
action = MotionEvent.ACTION_DOWN;
|
} else {
|
action = MotionEvent.ACTION_POINTER_DOWN;
|
// Set the id of the changed pointer.
|
action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
}
|
generateEvent(action, ms, ev);
|
return this;
|
}
|
|
public TouchEventTranslator position(int id, float x, float y) {
|
checkFingerExistence(id, true);
|
mFingers.get(id).set(x, y);
|
return this;
|
}
|
|
private TouchEventTranslator lift(int id, int index, MotionEvent ev) {
|
checkFingerExistence(id, true);
|
boolean isFinalUp = (mFingers.size() == 1);
|
int action;
|
if (isFinalUp) {
|
action = MotionEvent.ACTION_UP;
|
} else {
|
action = MotionEvent.ACTION_POINTER_UP;
|
// Set the id of the changed pointer.
|
action |= index << MotionEvent.ACTION_POINTER_INDEX_SHIFT;
|
}
|
generateEvent(action, ev);
|
mFingers.remove(id);
|
return this;
|
}
|
|
private TouchEventTranslator lift(int id, int index, float x, float y, MotionEvent ev) {
|
checkFingerExistence(id, true);
|
mFingers.get(id).set(x, y);
|
return lift(id, index, ev);
|
}
|
|
public TouchEventTranslator cancel(MotionEvent ev) {
|
generateEvent(MotionEvent.ACTION_CANCEL, ev);
|
mFingers.clear();
|
return this;
|
}
|
|
private void checkFingerExistence(int id, boolean shouldExist) {
|
if (shouldExist != (mFingers.get(id, null) != null)) {
|
throw new IllegalArgumentException(
|
shouldExist ? "Finger does not exist" : "Finger already exists");
|
}
|
}
|
|
|
/**
|
* Used to debug MotionEvents being sent/received.
|
*/
|
public void printSamples(String msg, MotionEvent ev) {
|
System.out.printf("%s %s", msg, MotionEvent.actionToString(ev.getActionMasked()));
|
final int pointerCount = ev.getPointerCount();
|
System.out.printf("#%d/%d", ev.getActionIndex(), pointerCount);
|
System.out.printf(" t=%d:", ev.getEventTime());
|
for (int p = 0; p < pointerCount; p++) {
|
System.out.printf(" id=%d: (%f,%f)",
|
ev.getPointerId(p), ev.getX(p), ev.getY(p));
|
}
|
System.out.println();
|
}
|
|
private void generateEvent(int action, MotionEvent ev) {
|
generateEvent(action, ev.getEventTime(), ev);
|
}
|
|
private void generateEvent(int action, long ms, MotionEvent ev) {
|
Pair<PointerProperties[], PointerCoords[]> state = getFingerState();
|
MotionEvent event = MotionEvent.obtain(
|
mDownEvents.get(0).timeStamp,
|
ms,
|
action,
|
state.first.length,
|
state.first,
|
state.second,
|
ev.getMetaState(),
|
ev.getButtonState() /* buttonState */,
|
ev.getXPrecision() /* xPrecision */,
|
ev.getYPrecision() /* yPrecision */,
|
ev.getDeviceId(),
|
ev.getEdgeFlags(),
|
ev.getSource(),
|
ev.getFlags() /* flags */);
|
if (DEBUG) {
|
printSamples(TAG + " generateEvent", event);
|
}
|
if (event.getPointerId(event.getActionIndex()) < 0) {
|
printSamples(TAG + "generateEvent", event);
|
throw new IllegalStateException(event.getActionIndex() + " not found in MotionEvent");
|
}
|
mListener.accept(event);
|
event.recycle();
|
}
|
|
/**
|
* Returns the description of the fingers' state expected by MotionEvent.
|
*/
|
private Pair<PointerProperties[], PointerCoords[]> getFingerState() {
|
int nFingers = mFingers.size();
|
|
Pair<PointerProperties[], PointerCoords[]> result = mCache.get(nFingers);
|
PointerProperties[] properties = result.first;
|
PointerCoords[] coordinates = result.second;
|
|
int index = 0;
|
for (int i = 0; i < mFingers.size(); i++) {
|
int id = mFingers.keyAt(i);
|
PointF location = mFingers.get(id);
|
|
PointerProperties property = properties[i];
|
property.id = id;
|
property.toolType = MotionEvent.TOOL_TYPE_FINGER;
|
properties[index] = property;
|
|
PointerCoords coordinate = coordinates[i];
|
coordinate.x = location.x;
|
coordinate.y = location.y;
|
coordinate.pressure = 1.0f;
|
coordinates[index] = coordinate;
|
|
index++;
|
}
|
return mCache.get(nFingers);
|
}
|
}
|