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

Commit 58f1298c authored by Maitri Mangal's avatar Maitri Mangal
Browse files

Adding a global listener that will notify on user activeness events

Test: atest InputTests:InputManagerServiceTests
bug: 356412905
Flag: com.android.hardware.input.key_event_activity_detection
Change-Id: Iaeeeecaac9b63f2c6c38bf79612199cc9f88def6
parent 0a35e7b8
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.hardware.input.KeyboardLayoutSelectionResult;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.IKeyEventActivityListener;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.KeyGlyphMap;
import android.hardware.lights.Light;
@@ -213,6 +214,16 @@ interface IInputManager {

    void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);

    @EnforcePermission("LISTEN_FOR_KEY_ACTIVITY")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)")
    boolean registerKeyEventActivityListener(IKeyEventActivityListener listener);

    @EnforcePermission("LISTEN_FOR_KEY_ACTIVITY")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)")
    boolean unregisterKeyEventActivityListener(IKeyEventActivityListener listener);

    // Get the bluetooth address of an input device if known, returning null if it either is not
    // connected via bluetooth or if the address cannot be determined.
    @EnforcePermission("BLUETOOTH")
+23 −0
Original line number Diff line number Diff line
/*
 * Copyright 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 android.hardware.input;

/** @hide */
oneway interface IKeyEventActivityListener
{
    void onKeyEventActivity();
}
+37 −0
Original line number Diff line number Diff line
@@ -1776,4 +1776,41 @@ public final class InputManager {
         */
        boolean isKeyGestureSupported(@KeyGestureEvent.KeyGestureType int gestureType);
    }

    /** @hide */
    public interface KeyEventActivityListener {
        /**
         * Reports a change for user activeness.
         *
         * This listener will be triggered any time a user presses a key.
         */
        void onKeyEventActivity();
    }


    /**
     * Registers a listener for updates to key event activeness
     *
     * @param listener to be registered
     * @return true if listener registered successfully
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)
    public boolean registerKeyEventActivityListener(@NonNull KeyEventActivityListener listener) {
        return mGlobal.registerKeyEventActivityListener(listener);
    }

    /**
     * Unregisters a listener for updates to key event activeness
     *
     * @param listener to be unregistered
     * @return true if listener unregistered successfully, also returns true if
     * invoked but listener was not present
     * @hide
     */
    @RequiresPermission(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)
    public boolean unregisterKeyEventActivityListener(@NonNull KeyEventActivityListener listener) {
        return mGlobal.unregisterKeyEventActivityListener(listener);
    }

}
+65 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.hardware.SensorManager;
import android.hardware.input.InputManager.InputDeviceBatteryListener;
import android.hardware.input.InputManager.InputDeviceListener;
import android.hardware.input.InputManager.KeyGestureEventHandler;
import android.hardware.input.InputManager.KeyEventActivityListener;
import android.hardware.input.InputManager.KeyGestureEventListener;
import android.hardware.input.InputManager.KeyboardBacklightListener;
import android.hardware.input.InputManager.OnTabletModeChangedListener;
@@ -124,6 +125,13 @@ public final class InputManagerGlobal {
    @Nullable
    private IKeyGestureEventListener mKeyGestureEventListener;

    private final Object mKeyEventActivityLock = new Object();
    @GuardedBy("mKeyEventActivityLock")
    private ArrayList<KeyEventActivityListener> mKeyEventActivityListeners;
    @GuardedBy("mKeyEventActivityLock")
    @Nullable
    private IKeyEventActivityListener mKeyEventActivityListener;

    private final Object mKeyGestureEventHandlerLock = new Object();
    @GuardedBy("mKeyGestureEventHandlerLock")
    @Nullable
@@ -1257,6 +1265,63 @@ public final class InputManagerGlobal {
        }
    }

    private class LocalKeyEventActivityListener extends IKeyEventActivityListener.Stub {
        @Override
        public void onKeyEventActivity() {
            synchronized (mKeyEventActivityLock) {
                final int numListeners = mKeyEventActivityListeners.size();
                for (int i = 0; i < numListeners; i++) {
                    KeyEventActivityListener listener = mKeyEventActivityListeners.get(i);
                    listener.onKeyEventActivity();
                }
            }
        }
    }

    boolean registerKeyEventActivityListener(@NonNull KeyEventActivityListener listener) {
        Objects.requireNonNull(listener, "listener should not be null");
        boolean success = false;
        synchronized (mKeyEventActivityLock) {
            if (mKeyEventActivityListener == null) {
                mKeyEventActivityListeners = new ArrayList<>();
                mKeyEventActivityListener = new LocalKeyEventActivityListener();

                try {
                    success = mIm.registerKeyEventActivityListener(mKeyEventActivityListener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            if (mKeyEventActivityListeners.contains(listener)) {
                throw new IllegalArgumentException("Listener has already been registered!");
            }
            mKeyEventActivityListeners.add(listener);
            return success;
        }
    }

    boolean unregisterKeyEventActivityListener(@NonNull KeyEventActivityListener listener) {
        Objects.requireNonNull(listener, "listener should not be null");

        boolean success = true;
        synchronized (mKeyEventActivityLock) {
            if (mKeyEventActivityListeners == null) {
                return success;
            }
            mKeyEventActivityListeners.remove(listener);
            if (mKeyEventActivityListeners.isEmpty()) {
                try {
                    success = mIm.unregisterKeyEventActivityListener(mKeyEventActivityListener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mKeyEventActivityListeners = null;
                mKeyEventActivityListener = null;
            }
        }
        return success;
    }

    /**
     * Sets the keyboard layout override for the specified input device. This will set the
     * keyboard layout as the default for the input device irrespective of the underlying IME
+84 −3
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.server.policy.WindowManagerPolicy.ACTION_PASS_TO_USER;

@@ -61,6 +62,7 @@ import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.IInputDevicesChangedListener;
import android.hardware.input.IInputManager;
import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.IKeyEventActivityListener;
import android.hardware.input.IKeyGestureEventListener;
import android.hardware.input.IKeyGestureHandler;
import android.hardware.input.IKeyboardBacklightListener;
@@ -89,11 +91,13 @@ import android.os.InputEventInjectionResult;
import android.os.InputEventInjectionSync;
import android.os.Looper;
import android.os.Message;
import android.os.PermissionEnforcer;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.VibrationEffect;
import android.os.vibrator.StepSegment;
@@ -280,6 +284,16 @@ public class InputManagerService extends IInputManager.Stub
    @GuardedBy("mAssociationsLock")
    private final Map<String, Integer> mRuntimeAssociations = new ArrayMap<>();

    final Object mKeyEventActivityLock = new Object();
    @GuardedBy("mKeyEventActivityLock")
    private List<IKeyEventActivityListener> mKeyEventActivityListenersToNotify =
            new ArrayList<>();

    // Rate limit for key event activity detection. Prevent the listener from being notified
    // too frequently.
    private static final long KEY_EVENT_ACTIVITY_RATE_LIMIT_INTERVAL_MS = 1000;
    private long mLastKeyEventActivityTimeMs = 0;

    // The associations of input devices to displays by port. Maps from {InputDevice#mName} (String)
    // to {DisplayInfo#uniqueId} (String) so that events from the Input Device go to a
    // specific display.
@@ -484,13 +498,16 @@ public class InputManagerService extends IInputManager.Stub
    }

    public InputManagerService(Context context) {
        this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}));
        this(new Injector(context, DisplayThread.get().getLooper(), new UEventManager() {}),
                context.getSystemService(PermissionEnforcer.class));
    }

    @VisibleForTesting
    InputManagerService(Injector injector) {
    InputManagerService(Injector injector, PermissionEnforcer permissionEnforcer) {
        // The static association map is accessed by both java and native code, so it must be
        // initialized before initializing the native service.
        super(permissionEnforcer);

        mStaticAssociations = loadStaticInputPortAssociations();

        mContext = injector.getContext();
@@ -2509,9 +2526,73 @@ public class InputManagerService extends IInputManager.Stub
        return true;
    }

    @EnforcePermission(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)
    @Override // Binder Call
    public boolean registerKeyEventActivityListener(@NonNull IKeyEventActivityListener listener) {
        super.registerKeyEventActivityListener_enforcePermission();
        Objects.requireNonNull(listener, "listener must not be null");
        return InputManagerService.this.registerKeyEventActivityListenerInternal(listener);
    }

    @EnforcePermission(android.Manifest.permission.LISTEN_FOR_KEY_ACTIVITY)
    @Override // Binder Call
    public boolean unregisterKeyEventActivityListener(@NonNull IKeyEventActivityListener listener) {
        super.unregisterKeyEventActivityListener_enforcePermission();
        Objects.requireNonNull(listener, "listener must not be null");
        return InputManagerService.this.unregisterKeyEventActivityListenerInternal(listener);
    }

    /**
     * Registers a listener for updates to key event activeness
     */
    private boolean registerKeyEventActivityListenerInternal(IKeyEventActivityListener listener) {
        synchronized (mKeyEventActivityLock) {
            if (!mKeyEventActivityListenersToNotify.contains(listener)) {
                mKeyEventActivityListenersToNotify.add(listener);
                return true;
            }
        }
        return false;
    }

    /**
     * Unregisters a listener for updates to key event activeness
     */
    private boolean unregisterKeyEventActivityListenerInternal(IKeyEventActivityListener listener) {
        synchronized (mKeyEventActivityLock) {
            return mKeyEventActivityListenersToNotify.removeIf(existingListener ->
                    existingListener.asBinder() == listener.asBinder());
        }
    }

    private void notifyKeyActivityListeners(KeyEvent event) {
        long currentTimeMs = SystemClock.uptimeMillis();
        if (keyEventActivityDetection()
                && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0
                && currentTimeMs - mLastKeyEventActivityTimeMs
                >= KEY_EVENT_ACTIVITY_RATE_LIMIT_INTERVAL_MS) {
            List<IKeyEventActivityListener> keyEventActivityListeners;
            synchronized (mKeyEventActivityLock) {
                keyEventActivityListeners = List.copyOf(mKeyEventActivityListenersToNotify);
            }
            for (IKeyEventActivityListener listener : keyEventActivityListeners) {
                try {
                    listener.onKeyEventActivity();
                } catch (RemoteException e) {
                    Slog.i(TAG,
                            "Could Not Notify Listener due to Remote Exception: " + e);
                    unregisterKeyEventActivityListener(listener);
                }
            }
            mLastKeyEventActivityTimeMs = currentTimeMs;
        }
    }

    // Native callback.
    @SuppressWarnings("unused")
    private int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
    @VisibleForTesting
    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
        notifyKeyActivityListeners(event);
        synchronized (mFocusEventDebugViewLock) {
            if (mFocusEventDebugView != null) {
                mFocusEventDebugView.reportKeyEvent(event);
Loading