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

Commit cb05e13b authored by Daniel Norman's avatar Daniel Norman Committed by Android (Google) Code Review
Browse files

Merge "chore(a11yInputFilter): dump received and sent events when exceptions...

Merge "chore(a11yInputFilter): dump received and sent events when exceptions in AccessibilityInputFilter" into main
parents b48bcd52 d6594160
Loading
Loading
Loading
Loading
+10 −1
Original line number Diff line number Diff line
@@ -173,7 +173,7 @@ public abstract class InputFilter extends IInputFilter.Stub {
        try {
            mHost.sendInputEvent(event, policyFlags);
        } catch (RemoteException re) {
            /* ignore */
            onSendInputEventException(re);
        }
    }

@@ -214,6 +214,15 @@ public abstract class InputFilter extends IInputFilter.Stub {
    public void onUninstalled() {
    }

    /**
     * Called when a exception is raised when the filter sends the input event.
     *
     * @param exception The exception
     */
    public void onSendInputEventException(Exception exception) {
        /* ignore */
    }

    private final class H extends Handler {
        public H(Looper looper) {
            super(looper);
+1 −0
Original line number Diff line number Diff line
@@ -296,6 +296,7 @@ flag {
    description: "Sends accessibility events in TouchExplorer#onAccessibilityEvent based on internal state to keep it consistent. This reduces test flakiness."
    bug: "295575684"
}

flag {
    name: "send_hover_events_based_on_event_stream"
    namespace: "accessibility"
+147 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.server.accessibility;

import android.util.Slog;
import android.view.InputEvent;

/**
 * Provides debugging capabilities for {@link InputEvent}s processed by an
 * {@link AccessibilityInputFilter}. This class caches recent input events
 * (both received and sent) to assist in diagnosing issues, particularly
 * when an exception occurs during event injection.
 */
public class AccessibilityInputDebugger {

    private static final String TAG = "A11yInputDebugger";

    // Determines the capacity of the recent event caches.
    private static final int RECENT_EVENTS_TO_LOG = 10;

    // Copy of the most recent received input events.
    private final InputEvent[] mRecentReceivedEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
    private int mMostRecentReceivedEventIndex = 0;

    // Copy of the most recent injected input events.
    private final InputEvent[] mRecentSentEvents = new InputEvent[RECENT_EVENTS_TO_LOG];
    private int mMostRecentSentEventIndex = 0;

    /**
     * Records an {@link InputEvent} that has been received by the accessibility input filter.
     */
    void onReceiveEvent(InputEvent event) {
        final int index = (mMostRecentReceivedEventIndex + 1) % RECENT_EVENTS_TO_LOG;
        mMostRecentReceivedEventIndex = index;
        if (mRecentReceivedEvents[index] != null) {
            mRecentReceivedEvents[index].recycle();
        }
        mRecentReceivedEvents[index] = event.copy();
    }

    /**
     * Records an {@link InputEvent} that is about to be sent (injected) by the
     * accessibility input filter.
     */
    void onSendEvent(InputEvent event) {
        final int index = (mMostRecentSentEventIndex + 1) % RECENT_EVENTS_TO_LOG;
        mMostRecentSentEventIndex = index;
        if (mRecentSentEvents[index] != null) {
            mRecentSentEvents[index].recycle();
        }
        mRecentSentEvents[index] = event.copy();
    }

    /**
     * Called when an exception occurs while sending an {@link InputEvent}. This method logs the
     * exception and dumps the cached received and sent events, to aid in debugging the cause of
     * the exception.
     */
    void onSendEventException(Exception exception) {
        Slog.w(TAG, "on accessibility send input event exception: " + exception);
        dumpCachedEvents();
    }

    /**
     * Clears all cached {@link InputEvent}s, releasing their resources.
     */
    void clearCachedEvents() {
        clearReceivedEventCaches();
        clearSentEventCaches();
    }

    private void clearReceivedEventCaches() {
        for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
            if (mRecentReceivedEvents[i] != null) {
                mRecentReceivedEvents[i].recycle();
                mRecentReceivedEvents[i] = null;
            }
        }
        mMostRecentReceivedEventIndex = 0;
    }

    private void clearSentEventCaches() {
        for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
            if (mRecentSentEvents[i] != null) {
                mRecentSentEvents[i].recycle();
                mRecentSentEvents[i] = null;
            }
        }
        mMostRecentSentEventIndex = 0;
    }

    private void dumpCachedEvents() {
        if (RECENT_EVENTS_TO_LOG == 0) {
            return;
        }

        StringBuilder message = new StringBuilder();
        // Recent received events
        message.append("\n  -- recent received events --");
        for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
            final int index = (mMostRecentReceivedEventIndex + RECENT_EVENTS_TO_LOG - i)
                    % RECENT_EVENTS_TO_LOG;
            final InputEvent event = mRecentReceivedEvents[index];
            if (event == null) {
                break;
            }
            message.append("\n  ");
            appendEvent(message, i + 1, event);
        }
        // Recent sent events
        message.append("\n  -- recent sent events --");
        for (int i = 0; i < RECENT_EVENTS_TO_LOG; i++) {
            final int index = (mMostRecentSentEventIndex + RECENT_EVENTS_TO_LOG - i)
                    % RECENT_EVENTS_TO_LOG;
            final InputEvent event = mRecentSentEvents[index];
            if (event == null) {
                break;
            }
            message.append("\n  ");
            appendEvent(message, i + 1, event);
        }
        Slog.w(TAG, message.toString());

        // Clear the caches so the future dumps will not have already printed events
        clearCachedEvents();
    }

    private static void appendEvent(StringBuilder message, int index, InputEvent event) {
        message.append(index).append(": sent at ").append(event.getEventTimeNanos());
        message.append(", ");
        message.append(event);
    }
}
+32 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.os.Build;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -251,6 +252,9 @@ public class AccessibilityInputFilter extends InputFilter implements EventStream
     */
    private MotionEvent mLastActiveDeviceMotionEvent = null;

    @Nullable
    private final AccessibilityInputDebugger mInputDebugger;

    private static MotionEvent cancelMotion(MotionEvent event) {
        if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
                || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -313,6 +317,10 @@ public class AccessibilityInputFilter extends InputFilter implements EventStream
        mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mEventHandler = eventHandler;
        mMagnificationGestureHandler = magnificationGestureHandler;
        // Enable debugger only for debug builds or when debug logging is active.
        mInputDebugger = (DEBUG || Build.isDebuggable())
                ? new AccessibilityInputDebugger()
                : null;
    }

    @Override
@@ -323,6 +331,9 @@ public class AccessibilityInputFilter extends InputFilter implements EventStream
        mInstalled = true;
        disableFeatures(/* featuresToBeEnabled= */ FLAG_FEATURE_NONE);
        enableFeatures();
        if (mInputDebugger != null) {
            mInputDebugger.clearCachedEvents();
        }
        mAms.onInputFilterInstalled(true);
        super.onInstalled();
    }
@@ -334,13 +345,15 @@ public class AccessibilityInputFilter extends InputFilter implements EventStream
        }
        mInstalled = false;
        disableFeatures(/* featuresToBeEnabled= */ FLAG_FEATURE_NONE);
        if (mInputDebugger != null) {
            mInputDebugger.clearCachedEvents();
        }
        mAms.onInputFilterInstalled(false);
        super.onUninstalled();
    }

    void onDisplayAdded(@NonNull Display display) {
        enableFeaturesForDisplayIfInstalled(display);

    }

    void onDisplayRemoved(int displayId) {
@@ -366,9 +379,27 @@ public class AccessibilityInputFilter extends InputFilter implements EventStream
            }
        }

        if (mInputDebugger != null) {
            mInputDebugger.onReceiveEvent(event);
        }
        onInputEventInternal(event, policyFlags);
    }

    @Override
    public void sendInputEvent(InputEvent event, int policyFlags) {
        if (mInputDebugger != null) {
            mInputDebugger.onSendEvent(event);
        }
        super.sendInputEvent(event, policyFlags);
    }

    @Override
    public void onSendInputEventException(Exception exception) {
        if (mInputDebugger != null) {
            mInputDebugger.onSendEventException(exception);
        }
    }

    private void onInputEventInternal(InputEvent event, int policyFlags) {
        if (mEventHandler.size() == 0) {
            if (DEBUG) Slog.d(TAG, "No mEventHandler for event " + event);
+8 −2
Original line number Diff line number Diff line
@@ -3413,7 +3413,8 @@ public class InputManagerService extends IInputManager.Stub
        }

        @Override
        public void sendInputEvent(@NonNull InputEvent event, int policyFlags) {
        public void sendInputEvent(@NonNull InputEvent event, int policyFlags)
                throws RemoteException {
            if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
                    "sendInputEvent()")) {
                throw new SecurityException(
@@ -3423,9 +3424,14 @@ public class InputManagerService extends IInputManager.Stub

            synchronized (mInputFilterLock) {
                if (!mDisconnected) {
                    mNative.injectInputEvent(event, false /* injectIntoUid */, -1 /* uid */,
                    @InputEventInjectionResult int result = mNative.injectInputEvent(
                            event, false /* injectIntoUid */, -1 /* uid */,
                            InputManager.INJECT_INPUT_EVENT_MODE_ASYNC, 0 /* timeout */,
                            policyFlags | WindowManagerPolicy.FLAG_FILTERED);
                    if (result != InputEventInjectionResult.SUCCEEDED) {
                        throw new RemoteException(
                                "Injection did not succeed, result= " + result + ".");
                    }
                }
            }
        }