blob: 15f5aa52470455377ed12123d1d257cb30f049ed [file] [log] [blame]
/*
* Copyright (C) 2017 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.quickstep;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_MASK;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_POINTER_INDEX_SHIFT;
import static com.android.quickstep.TouchConsumer.INTERACTION_QUICK_SCRUB;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Log;
import android.view.Choreographer;
import android.view.MotionEvent;
import com.android.systemui.shared.system.ChoreographerCompat;
import java.util.ArrayList;
/**
* Helper class for batching input events
*/
@TargetApi(Build.VERSION_CODES.O)
public class MotionEventQueue {
private static final String TAG = "MotionEventQueue";
private static final int ACTION_VIRTUAL = ACTION_MASK - 1;
private static final int ACTION_QUICK_SCRUB_START =
ACTION_VIRTUAL | (1 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_SCRUB_PROGRESS =
ACTION_VIRTUAL | (2 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_SCRUB_END =
ACTION_VIRTUAL | (3 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_RESET =
ACTION_VIRTUAL | (4 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_DEFER_INIT =
ACTION_VIRTUAL | (5 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_SHOW_OVERVIEW_FROM_ALT_TAB =
ACTION_VIRTUAL | (6 << ACTION_POINTER_INDEX_SHIFT);
private static final int ACTION_QUICK_STEP =
ACTION_VIRTUAL | (7 << ACTION_POINTER_INDEX_SHIFT);
private final EventArray mEmptyArray = new EventArray();
private final Object mExecutionLock = new Object();
// We use two arrays and swap the current index when one array is being consumed
private final EventArray[] mArrays = new EventArray[] {new EventArray(), new EventArray()};
private int mCurrentIndex = 0;
private final Runnable mMainFrameCallback = this::frameCallbackForMainChoreographer;
private final Runnable mInterimFrameCallback = this::frameCallbackForInterimChoreographer;
private final Choreographer mMainChoreographer;
private final TouchConsumer mConsumer;
private Choreographer mInterimChoreographer;
private Choreographer mCurrentChoreographer;
private Runnable mCurrentRunnable;
public MotionEventQueue(Choreographer choreographer, TouchConsumer consumer) {
mMainChoreographer = choreographer;
mConsumer = consumer;
mCurrentChoreographer = mMainChoreographer;
mCurrentRunnable = mMainFrameCallback;
setInterimChoreographer(consumer.getIntrimChoreographer(this));
}
public void setInterimChoreographer(Choreographer choreographer) {
synchronized (mExecutionLock) {
synchronized (mArrays) {
setInterimChoreographerLocked(choreographer);
ChoreographerCompat.postInputFrame(mCurrentChoreographer, mCurrentRunnable);
}
}
}
private void setInterimChoreographerLocked(Choreographer choreographer) {
mInterimChoreographer = choreographer;
if (choreographer == null) {
mCurrentChoreographer = mMainChoreographer;
mCurrentRunnable = mMainFrameCallback;
} else {
mCurrentChoreographer = mInterimChoreographer;
mCurrentRunnable = mInterimFrameCallback;
}
}
public void queue(MotionEvent event) {
mConsumer.preProcessMotionEvent(event);
queueNoPreProcess(event);
}
private void queueNoPreProcess(MotionEvent event) {
synchronized (mArrays) {
EventArray array = mArrays[mCurrentIndex];
if (array.isEmpty()) {
ChoreographerCompat.postInputFrame(mCurrentChoreographer, mCurrentRunnable);
}
int eventAction = event.getAction();
if (eventAction == ACTION_MOVE && array.lastEventAction == ACTION_MOVE) {
// Replace and recycle the last event
array.set(array.size() - 1, event).recycle();
} else {
array.add(event);
array.lastEventAction = eventAction;
}
}
}
private void frameCallbackForMainChoreographer() {
runFor(mMainChoreographer);
}
private void frameCallbackForInterimChoreographer() {
runFor(mInterimChoreographer);
}
private void runFor(Choreographer caller) {
synchronized (mExecutionLock) {
EventArray array = swapAndGetCurrentArray(caller);
int size = array.size();
for (int i = 0; i < size; i++) {
MotionEvent event = array.get(i);
if (event.getActionMasked() == ACTION_VIRTUAL) {
switch (event.getAction()) {
case ACTION_QUICK_SCRUB_START:
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
case ACTION_QUICK_SCRUB_PROGRESS:
mConsumer.onQuickScrubProgress(event.getX());
break;
case ACTION_QUICK_SCRUB_END:
mConsumer.onQuickScrubEnd();
break;
case ACTION_RESET:
mConsumer.reset();
break;
case ACTION_DEFER_INIT:
mConsumer.deferInit();
break;
case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB:
mConsumer.onShowOverviewFromAltTab();
mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB);
break;
case ACTION_QUICK_STEP:
mConsumer.onQuickStep(event);
break;
default:
Log.e(TAG, "Invalid virtual event: " + event.getAction());
}
} else {
mConsumer.accept(event);
}
event.recycle();
}
array.clear();
array.lastEventAction = ACTION_CANCEL;
}
}
private EventArray swapAndGetCurrentArray(Choreographer caller) {
synchronized (mArrays) {
if (caller != mCurrentChoreographer) {
return mEmptyArray;
}
EventArray current = mArrays[mCurrentIndex];
mCurrentIndex = mCurrentIndex ^ 1;
return current;
}
}
private void queueVirtualAction(int action, float progress) {
queueNoPreProcess(MotionEvent.obtain(0, 0, action, progress, 0, 0));
}
public void onQuickScrubStart() {
queueVirtualAction(ACTION_QUICK_SCRUB_START, 0);
}
public void onOverviewShownFromAltTab() {
queueVirtualAction(ACTION_SHOW_OVERVIEW_FROM_ALT_TAB, 0);
}
public void onQuickScrubProgress(float progress) {
queueVirtualAction(ACTION_QUICK_SCRUB_PROGRESS, progress);
}
public void onQuickScrubEnd() {
queueVirtualAction(ACTION_QUICK_SCRUB_END, 0);
}
public void onQuickStep(MotionEvent event) {
event.setAction(ACTION_QUICK_STEP);
queueNoPreProcess(event);
}
public void reset() {
queueVirtualAction(ACTION_RESET, 0);
}
public void deferInit() {
queueVirtualAction(ACTION_DEFER_INIT, 0);
}
public TouchConsumer getConsumer() {
return mConsumer;
}
private static class EventArray extends ArrayList<MotionEvent> {
public int lastEventAction = ACTION_CANCEL;
public EventArray() {
super(4);
}
}
}