Add InputEvent Compatibility Processor
Depending on the SDK version of applications, there may need to be changes made to InputEvents for compatibility purposes. Here, we add a InputEventCompatProcessor to refactor all compatibility adjustments to InputEvents. The compatibility processor is instantiated in ViewRootImpl using reflection, where the class name of the processor is defined in config.xml as `config_inputEventCompatProcessorClassName`. This allows for overlays to change the compatibility processor that is used for the build. The processor has two methods for processing the InputEvent before and after it is sent into the InputStages of the input pipeline in ViewRootImpl. InputEvents that are changed by the processor are marked with `FLAG_MODIFIED_FOR_COMPATIBILITY` so that they are sent back to the processor before they are finished. Bug: 119264233 Test: manual Change-Id: Ie0dc4665cb677a7ab2cc627791a0f72cdfce1cde
This commit is contained in:
81
core/java/android/view/InputEventCompatProcessor.java
Normal file
81
core/java/android/view/InputEventCompatProcessor.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Compatibility processor for InputEvents that allows events to be adjusted before and
|
||||
* after it is sent to the application.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
public class InputEventCompatProcessor {
|
||||
|
||||
protected Context mContext;
|
||||
protected int mTargetSdkVersion;
|
||||
|
||||
/** List of events to be used to return the processed events */
|
||||
private List<InputEvent> mProcessedEvents;
|
||||
|
||||
public InputEventCompatProcessor(Context context) {
|
||||
mContext = context;
|
||||
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
|
||||
mProcessedEvents = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the InputEvent for compatibility before it is sent to the app, allowing for the
|
||||
* generation of more than one event if necessary.
|
||||
*
|
||||
* @param e The InputEvent to process
|
||||
* @return The list of adjusted events, or null if no adjustments are needed. Do not keep a
|
||||
* reference to the output as the list is reused.
|
||||
*/
|
||||
public List<InputEvent> processInputEventForCompatibility(InputEvent e) {
|
||||
if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
|
||||
mProcessedEvents.clear();
|
||||
MotionEvent motion = (MotionEvent) e;
|
||||
final int mask =
|
||||
MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
|
||||
final int buttonState = motion.getButtonState();
|
||||
final int compatButtonState = (buttonState & mask) >> 4;
|
||||
if (compatButtonState != 0) {
|
||||
motion.setButtonState(buttonState | compatButtonState);
|
||||
}
|
||||
mProcessedEvents.add(motion);
|
||||
return mProcessedEvents;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the InputEvent for compatibility before it is finished by calling
|
||||
* InputEventReceiver#finishInputEvent().
|
||||
*
|
||||
* @param e The InputEvent to process
|
||||
* @return The InputEvent to finish, or null if it should not be finished
|
||||
*/
|
||||
public InputEvent processInputEventBeforeFinish(InputEvent e) {
|
||||
// No changes needed
|
||||
return e;
|
||||
}
|
||||
}
|
||||
@@ -124,6 +124,7 @@ import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
@@ -543,6 +544,8 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
private boolean mNeedsRendererSetup;
|
||||
|
||||
private final InputEventCompatProcessor mInputCompatProcessor;
|
||||
|
||||
/**
|
||||
* Consistency verifier for debugging purposes.
|
||||
*/
|
||||
@@ -598,6 +601,25 @@ public final class ViewRootImpl implements ViewParent,
|
||||
mChoreographer = Choreographer.getInstance();
|
||||
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
|
||||
|
||||
String processorOverrideName = context.getResources().getString(
|
||||
R.string.config_inputEventCompatProcessorOverrideClassName);
|
||||
if (processorOverrideName.isEmpty()) {
|
||||
// No compatibility processor override, using default.
|
||||
mInputCompatProcessor = new InputEventCompatProcessor(context);
|
||||
} else {
|
||||
InputEventCompatProcessor compatProcessor = null;
|
||||
try {
|
||||
final Class<? extends InputEventCompatProcessor> klass =
|
||||
(Class<? extends InputEventCompatProcessor>) Class.forName(
|
||||
processorOverrideName);
|
||||
compatProcessor = klass.getConstructor(Context.class).newInstance(context);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Unable to create the InputEventCompatProcessor. ", e);
|
||||
} finally {
|
||||
mInputCompatProcessor = compatProcessor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sCompatibilityDone) {
|
||||
sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;
|
||||
|
||||
@@ -7166,6 +7188,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
public static final int FLAG_FINISHED_HANDLED = 1 << 3;
|
||||
public static final int FLAG_RESYNTHESIZED = 1 << 4;
|
||||
public static final int FLAG_UNHANDLED = 1 << 5;
|
||||
public static final int FLAG_MODIFIED_FOR_COMPATIBILITY = 1 << 6;
|
||||
|
||||
public QueuedInputEvent mNext;
|
||||
|
||||
@@ -7258,7 +7281,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
@UnsupportedAppUsage
|
||||
void enqueueInputEvent(InputEvent event,
|
||||
InputEventReceiver receiver, int flags, boolean processImmediately) {
|
||||
adjustInputEventForCompatibility(event);
|
||||
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
|
||||
|
||||
// Always enqueue the input event in order, regardless of its time stamp.
|
||||
@@ -7361,7 +7383,22 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
if (q.mReceiver != null) {
|
||||
boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;
|
||||
q.mReceiver.finishInputEvent(q.mEvent, handled);
|
||||
boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0;
|
||||
if (modified) {
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish");
|
||||
InputEvent processedEvent;
|
||||
try {
|
||||
processedEvent =
|
||||
mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent);
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
|
||||
}
|
||||
if (processedEvent != null) {
|
||||
q.mReceiver.finishInputEvent(processedEvent, handled);
|
||||
}
|
||||
} else {
|
||||
q.mReceiver.finishInputEvent(q.mEvent, handled);
|
||||
}
|
||||
} else {
|
||||
q.mEvent.recycleIfNeededAfterDispatch();
|
||||
}
|
||||
@@ -7369,19 +7406,6 @@ public final class ViewRootImpl implements ViewParent,
|
||||
recycleQueuedInputEvent(q);
|
||||
}
|
||||
|
||||
private void adjustInputEventForCompatibility(InputEvent e) {
|
||||
if (mTargetSdkVersion < Build.VERSION_CODES.M && e instanceof MotionEvent) {
|
||||
MotionEvent motion = (MotionEvent) e;
|
||||
final int mask =
|
||||
MotionEvent.BUTTON_STYLUS_PRIMARY | MotionEvent.BUTTON_STYLUS_SECONDARY;
|
||||
final int buttonState = motion.getButtonState();
|
||||
final int compatButtonState = (buttonState & mask) >> 4;
|
||||
if (compatButtonState != 0) {
|
||||
motion.setButtonState(buttonState | compatButtonState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isTerminalInputEvent(InputEvent event) {
|
||||
if (event instanceof KeyEvent) {
|
||||
final KeyEvent keyEvent = (KeyEvent)event;
|
||||
@@ -7452,7 +7476,28 @@ public final class ViewRootImpl implements ViewParent,
|
||||
|
||||
@Override
|
||||
public void onInputEvent(InputEvent event) {
|
||||
enqueueInputEvent(event, this, 0, true);
|
||||
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
|
||||
List<InputEvent> processedEvents;
|
||||
try {
|
||||
processedEvents =
|
||||
mInputCompatProcessor.processInputEventForCompatibility(event);
|
||||
} finally {
|
||||
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
|
||||
}
|
||||
if (processedEvents != null) {
|
||||
if (processedEvents.isEmpty()) {
|
||||
// InputEvent consumed by mInputCompatProcessor
|
||||
finishInputEvent(event, true);
|
||||
} else {
|
||||
for (int i = 0; i < processedEvents.size(); i++) {
|
||||
enqueueInputEvent(
|
||||
processedEvents.get(i), this,
|
||||
QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
enqueueInputEvent(event, this, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -3592,4 +3592,9 @@
|
||||
<!-- Component name for default assistant on this device -->
|
||||
<string name="config_defaultAssistantComponentName">#+UNSET</string>
|
||||
|
||||
<!-- Class name for the InputEvent compatibility processor override.
|
||||
Empty string means use the default compatibility processor
|
||||
(android.view.InputEventCompatProcessor). -->
|
||||
<string name="config_inputEventCompatProcessorOverrideClassName" translatable="false"></string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -301,6 +301,7 @@
|
||||
<java-symbol type="bool" name="config_enableWallpaperService" />
|
||||
<java-symbol type="bool" name="config_checkWallpaperAtBoot" />
|
||||
<java-symbol type="string" name="config_wallpaperManagerServiceName" />
|
||||
<java-symbol type="string" name="config_inputEventCompatProcessorOverrideClassName" />
|
||||
<java-symbol type="bool" name="config_enableUpdateableTimeZoneRules" />
|
||||
<java-symbol type="bool" name="config_timeZoneRulesUpdateTrackingEnabled" />
|
||||
<java-symbol type="string" name="config_timeZoneRulesUpdaterPackage" />
|
||||
|
||||
Reference in New Issue
Block a user