Loading quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java 0 → 100644 +115 −0 Original line number Diff line number Diff line /* * 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.uioverrides; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import android.os.RemoteException; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.touch.TouchEventTranslator; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.systemui.shared.recents.ISystemUiProxy; /** * TouchController for handling touch events that get sent to the StatusBar. Once the * Once the event delta y passes the touch slop, the events start getting forwarded. * All events are offset by initial Y value of the pointer. */ public class StatusBarTouchController implements TouchController { private static final String TAG = "StatusBarController"; protected final Launcher mLauncher; protected final TouchEventTranslator mTranslator; private final float mTouchSlop; private ISystemUiProxy mSysUiProxy; /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/ private boolean mCanIntercept; public StatusBarTouchController(Launcher l) { mLauncher = l; mTouchSlop = ViewConfiguration.get(l).getScaledTouchSlop(); mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev)); } private void dispatchTouchEvent(MotionEvent ev) { try { if (mSysUiProxy != null) { mSysUiProxy.onStatusBarMotionEvent(ev); } } catch (RemoteException e) { Log.e(TAG, "Remote exception on sysUiProxy.", e); } } @Override public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == ACTION_DOWN) { mCanIntercept = canInterceptTouch(ev); if (!mCanIntercept) { return false; } mTranslator.reset(); mTranslator.setDownParameters(0, ev); } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { // Check!! should only set it only when threshold is not entered. mTranslator.setDownParameters(ev.getActionIndex(), ev); } if (!mCanIntercept) { return false; } if (action == ACTION_MOVE) { float dy = ev.getY() - mTranslator.getDownY(); if (dy > mTouchSlop) { mTranslator.dispatchDownEvents(ev); mTranslator.processMotionEvent(ev); return true; } } return false; } @Override public final boolean onControllerTouchEvent(MotionEvent ev) { mTranslator.processMotionEvent(ev); return true; } private boolean canInterceptTouch(MotionEvent ev) { if (!mLauncher.isInState(LauncherState.NORMAL)) { return false; } else { // For NORMAL state, only listen if the event originated above the navbar height DeviceProfile dp = mLauncher.getDeviceProfile(); if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) { return false; } } mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy(); return mSysUiProxy != null; } } quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +17 −14 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.TouchController; import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.RecentsModel; Loading @@ -52,6 +53,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.zip.Deflater; public class UiFactory { Loading @@ -59,24 +61,25 @@ public class UiFactory { public static TouchController[] createTouchControllers(Launcher launcher) { boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher) .isSwipeUpGestureEnabled(); if (!swipeUpEnabled) { return new TouchController[] { launcher.getDragController(), new OverviewToAllAppsTouchController(launcher), new LauncherTaskViewController(launcher)}; ArrayList<TouchController> list = new ArrayList<>(); list.add(launcher.getDragController()); if (!swipeUpEnabled || launcher.getDeviceProfile().isVerticalBarLayout()) { list.add(new OverviewToAllAppsTouchController(launcher)); } if (launcher.getDeviceProfile().isVerticalBarLayout()) { return new TouchController[] { launcher.getDragController(), new OverviewToAllAppsTouchController(launcher), new LandscapeEdgeSwipeController(launcher), new LauncherTaskViewController(launcher)}; list.add(new LandscapeEdgeSwipeController(launcher)); } else { return new TouchController[] { launcher.getDragController(), new PortraitStatesTouchController(launcher), new LauncherTaskViewController(launcher)}; list.add(new PortraitStatesTouchController(launcher)); } if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE && !launcher.getDeviceProfile().isMultiWindowMode && !launcher.getDeviceProfile().isVerticalBarLayout()) { list.add(new StatusBarTouchController(launcher)); } list.add(new LauncherTaskViewController(launcher)); return list.toArray(new TouchController[list.size()]); } public static void setOnTouchControllersChangedListener(Context context, Runnable listener) { Loading src/com/android/launcher3/config/BaseFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ abstract class BaseFlags { // Feature flag to enable moving the QSB on the 0th screen of the workspace. public static final boolean QSB_ON_FIRST_SCREEN = true; //Feature flag to enable pulling down navigation shade from workspace. public static final boolean PULL_DOWN_STATUS_BAR = true; // When enabled the all-apps icon is not added to the hotseat. public static final boolean NO_ALL_APPS_ICON = true; Loading src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +1 −1 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * 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. Loading src/com/android/launcher3/touch/TouchEventTranslator.java 0 → 100644 +274 −0 Original line number Diff line number Diff line /* * 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.util.SparseLongArray; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.launcher3.Utilities.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 downY; public DownState(long timeStamp, float downY) { this.timeStamp = timeStamp; this.downY = downY; } }; private final DownState ZERO = new DownState(0, 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 getDownY() { return mDownEvents.get(0).downY; } public void setDownParameters(int idx, MotionEvent e) { DownState ev = new DownState(e.getEventTime(), 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, 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, 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, float x, float y, MotionEvent ev) { return put(id, x, y, ev.getEventTime(), ev); } private TouchEventTranslator put(int id, 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 |= id << 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.getPointerId(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); } 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); } } Loading
quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java 0 → 100644 +115 −0 Original line number Diff line number Diff line /* * 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.uioverrides; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_MOVE; import android.os.RemoteException; import android.util.Log; import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.launcher3.DeviceProfile; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.touch.TouchEventTranslator; import com.android.launcher3.util.TouchController; import com.android.quickstep.RecentsModel; import com.android.systemui.shared.recents.ISystemUiProxy; /** * TouchController for handling touch events that get sent to the StatusBar. Once the * Once the event delta y passes the touch slop, the events start getting forwarded. * All events are offset by initial Y value of the pointer. */ public class StatusBarTouchController implements TouchController { private static final String TAG = "StatusBarController"; protected final Launcher mLauncher; protected final TouchEventTranslator mTranslator; private final float mTouchSlop; private ISystemUiProxy mSysUiProxy; /* If {@code false}, this controller should not handle the input {@link MotionEvent}.*/ private boolean mCanIntercept; public StatusBarTouchController(Launcher l) { mLauncher = l; mTouchSlop = ViewConfiguration.get(l).getScaledTouchSlop(); mTranslator = new TouchEventTranslator((MotionEvent ev)-> dispatchTouchEvent(ev)); } private void dispatchTouchEvent(MotionEvent ev) { try { if (mSysUiProxy != null) { mSysUiProxy.onStatusBarMotionEvent(ev); } } catch (RemoteException e) { Log.e(TAG, "Remote exception on sysUiProxy.", e); } } @Override public final boolean onControllerInterceptTouchEvent(MotionEvent ev) { int action = ev.getActionMasked(); if (action == ACTION_DOWN) { mCanIntercept = canInterceptTouch(ev); if (!mCanIntercept) { return false; } mTranslator.reset(); mTranslator.setDownParameters(0, ev); } else if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { // Check!! should only set it only when threshold is not entered. mTranslator.setDownParameters(ev.getActionIndex(), ev); } if (!mCanIntercept) { return false; } if (action == ACTION_MOVE) { float dy = ev.getY() - mTranslator.getDownY(); if (dy > mTouchSlop) { mTranslator.dispatchDownEvents(ev); mTranslator.processMotionEvent(ev); return true; } } return false; } @Override public final boolean onControllerTouchEvent(MotionEvent ev) { mTranslator.processMotionEvent(ev); return true; } private boolean canInterceptTouch(MotionEvent ev) { if (!mLauncher.isInState(LauncherState.NORMAL)) { return false; } else { // For NORMAL state, only listen if the event originated above the navbar height DeviceProfile dp = mLauncher.getDeviceProfile(); if (ev.getY() > (mLauncher.getDragLayer().getHeight() - dp.getInsets().bottom)) { return false; } } mSysUiProxy = RecentsModel.INSTANCE.get(mLauncher).getSystemUiProxy(); return mSysUiProxy != null; } }
quickstep/src/com/android/launcher3/uioverrides/UiFactory.java +17 −14 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import com.android.launcher3.LauncherStateManager; import com.android.launcher3.LauncherStateManager.StateHandler; import com.android.launcher3.Utilities; import com.android.launcher3.anim.AnimatorPlaybackController; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.TouchController; import com.android.quickstep.OverviewInteractionState; import com.android.quickstep.RecentsModel; Loading @@ -52,6 +53,7 @@ import com.android.systemui.shared.system.WindowManagerWrapper; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.zip.Deflater; public class UiFactory { Loading @@ -59,24 +61,25 @@ public class UiFactory { public static TouchController[] createTouchControllers(Launcher launcher) { boolean swipeUpEnabled = OverviewInteractionState.INSTANCE.get(launcher) .isSwipeUpGestureEnabled(); if (!swipeUpEnabled) { return new TouchController[] { launcher.getDragController(), new OverviewToAllAppsTouchController(launcher), new LauncherTaskViewController(launcher)}; ArrayList<TouchController> list = new ArrayList<>(); list.add(launcher.getDragController()); if (!swipeUpEnabled || launcher.getDeviceProfile().isVerticalBarLayout()) { list.add(new OverviewToAllAppsTouchController(launcher)); } if (launcher.getDeviceProfile().isVerticalBarLayout()) { return new TouchController[] { launcher.getDragController(), new OverviewToAllAppsTouchController(launcher), new LandscapeEdgeSwipeController(launcher), new LauncherTaskViewController(launcher)}; list.add(new LandscapeEdgeSwipeController(launcher)); } else { return new TouchController[] { launcher.getDragController(), new PortraitStatesTouchController(launcher), new LauncherTaskViewController(launcher)}; list.add(new PortraitStatesTouchController(launcher)); } if (FeatureFlags.PULL_DOWN_STATUS_BAR && Utilities.IS_DEBUG_DEVICE && !launcher.getDeviceProfile().isMultiWindowMode && !launcher.getDeviceProfile().isVerticalBarLayout()) { list.add(new StatusBarTouchController(launcher)); } list.add(new LauncherTaskViewController(launcher)); return list.toArray(new TouchController[list.size()]); } public static void setOnTouchControllersChangedListener(Context context, Runnable listener) { Loading
src/com/android/launcher3/config/BaseFlags.java +4 −0 Original line number Diff line number Diff line Loading @@ -36,6 +36,10 @@ abstract class BaseFlags { // Feature flag to enable moving the QSB on the 0th screen of the workspace. public static final boolean QSB_ON_FIRST_SCREEN = true; //Feature flag to enable pulling down navigation shade from workspace. public static final boolean PULL_DOWN_STATUS_BAR = true; // When enabled the all-apps icon is not added to the hotseat. public static final boolean NO_ALL_APPS_ICON = true; Loading
src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +1 −1 Original line number Diff line number Diff line /* * Copyright (C) 2019 The Android Open Source Project * 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. Loading
src/com/android/launcher3/touch/TouchEventTranslator.java 0 → 100644 +274 −0 Original line number Diff line number Diff line /* * 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.util.SparseLongArray; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; import com.android.launcher3.Utilities.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 downY; public DownState(long timeStamp, float downY) { this.timeStamp = timeStamp; this.downY = downY; } }; private final DownState ZERO = new DownState(0, 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 getDownY() { return mDownEvents.get(0).downY; } public void setDownParameters(int idx, MotionEvent e) { DownState ev = new DownState(e.getEventTime(), 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, 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, 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, float x, float y, MotionEvent ev) { return put(id, x, y, ev.getEventTime(), ev); } private TouchEventTranslator put(int id, 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 |= id << 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.getPointerId(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); } 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); } }