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

Commit 72a6bece authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Android (Google) Code Review
Browse files

Merge "Add API to register listener for Sticky modifier state" into main

parents e7b1638d b93107c0
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.hardware.input.IInputDeviceBatteryListener;
import android.hardware.input.IInputDeviceBatteryState;
import android.hardware.input.IKeyboardBacklightListener;
import android.hardware.input.IKeyboardBacklightState;
import android.hardware.input.IStickyModifierStateListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.TouchCalibration;
import android.os.CombinedVibration;
@@ -241,4 +242,14 @@ interface IInputManager {
    void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener);

    HostUsiVersion getHostUsiVersionFromDisplayConfig(int displayId);

    @EnforcePermission("MONITOR_STICKY_MODIFIER_STATE")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
    void registerStickyModifierStateListener(IStickyModifierStateListener listener);

    @EnforcePermission("MONITOR_STICKY_MODIFIER_STATE")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
            + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
    void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
}
+26 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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 IStickyModifierStateListener {

    /**
     * Called when the sticky modifier state is changed when A11y Sticky keys feature is enabled
     */
    void onStickyModifierStateChanged(int modifierState, int lockedModifierState);
}
+55 −0
Original line number Diff line number Diff line
@@ -1296,6 +1296,42 @@ public final class InputManager {
        mGlobal.unregisterKeyboardBacklightListener(listener);
    }

    /**
     * Registers a Sticky modifier state change listener to be notified about {@link
     * StickyModifierState} changes.
     *
     * @param executor an executor on which the callback will be called
     * @param listener the {@link StickyModifierStateListener}
     * @throws IllegalArgumentException if {@code listener} has already been registered previously.
     * @throws NullPointerException     if {@code listener} or {@code executor} is null.
     * @hide
     * @see #unregisterStickyModifierStateListener(StickyModifierStateListener)
     */
    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
    public void registerStickyModifierStateListener(@NonNull Executor executor,
            @NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
            return;
        }
        mGlobal.registerStickyModifierStateListener(executor, listener);
    }

    /**
     * Unregisters a previously added Sticky modifier state change listener.
     *
     * @param listener the {@link StickyModifierStateListener}
     * @hide
     * @see #registerStickyModifierStateListener(Executor, StickyModifierStateListener)
     */
    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
    public void unregisterStickyModifierStateListener(
            @NonNull StickyModifierStateListener listener) {
        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
            return;
        }
        mGlobal.unregisterStickyModifierStateListener(listener);
    }

    /**
     * A callback used to be notified about battery state changes for an input device. The
     * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
@@ -1378,4 +1414,23 @@ public final class InputManager {
        void onKeyboardBacklightChanged(
                int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
    }

    /**
     * A callback used to be notified about sticky modifier state changes when A11y Sticky keys
     * feature is enabled.
     *
     * @see #registerStickyModifierStateListener(Executor, StickyModifierStateListener)
     * @see #unregisterStickyModifierStateListener(StickyModifierStateListener)
     * @hide
     */
    public interface StickyModifierStateListener {
        /**
         * Called when the sticky modifier state changes.
         * This method will be called once after the listener is successfully registered to provide
         * the initial modifier state.
         *
         * @param state the new sticky modifier state, never null.
         */
        void onStickyModifierStateChanged(@NonNull StickyModifierState state);
    }
}
+162 −0
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.hardware.input.InputManager.InputDeviceBatteryListener;
import android.hardware.input.InputManager.InputDeviceListener;
import android.hardware.input.InputManager.KeyboardBacklightListener;
import android.hardware.input.InputManager.OnTabletModeChangedListener;
import android.hardware.input.InputManager.StickyModifierStateListener;
import android.hardware.lights.Light;
import android.hardware.lights.LightState;
import android.hardware.lights.LightsManager;
@@ -52,6 +53,7 @@ import android.view.InputDevice;
import android.view.InputEvent;
import android.view.InputMonitor;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.PointerIcon;

import com.android.internal.annotations.GuardedBy;
@@ -100,6 +102,14 @@ public final class InputManagerGlobal {
    @GuardedBy("mKeyboardBacklightListenerLock")
    @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;

    private final Object mStickyModifierStateListenerLock = new Object();
    @GuardedBy("mStickyModifierStateListenerLock")
    @Nullable
    private ArrayList<StickyModifierStateListenerDelegate> mStickyModifierStateListeners;
    @GuardedBy("mStickyModifierStateListenerLock")
    @Nullable
    private IStickyModifierStateListener mStickyModifierStateListener;

    // InputDeviceSensorManager gets notified synchronously from the binder thread when input
    // devices change, so it must be synchronized with the input device listeners.
    @GuardedBy("mInputDeviceListeners")
@@ -905,6 +915,158 @@ public final class InputManagerGlobal {
        }
    }

    private static final class StickyModifierStateListenerDelegate {
        final InputManager.StickyModifierStateListener mListener;
        final Executor mExecutor;

        StickyModifierStateListenerDelegate(StickyModifierStateListener listener,
                Executor executor) {
            mListener = listener;
            mExecutor = executor;
        }

        void notifyStickyModifierStateChange(int modifierState, int lockedModifierState) {
            mExecutor.execute(() ->
                    mListener.onStickyModifierStateChanged(
                            new LocalStickyModifierState(modifierState, lockedModifierState)));
        }
    }

    private class LocalStickyModifierStateListener extends IStickyModifierStateListener.Stub {

        @Override
        public void onStickyModifierStateChanged(int modifierState, int lockedModifierState) {
            synchronized (mStickyModifierStateListenerLock) {
                if (mStickyModifierStateListeners == null) return;
                final int numListeners = mStickyModifierStateListeners.size();
                for (int i = 0; i < numListeners; i++) {
                    mStickyModifierStateListeners.get(i)
                            .notifyStickyModifierStateChange(modifierState, lockedModifierState);
                }
            }
        }
    }

    // Implementation of the android.hardware.input.StickyModifierState interface used to report
    // the sticky modifier state via the StickyModifierStateListener interfaces.
    private static final class LocalStickyModifierState extends StickyModifierState {

        private final int mModifierState;
        private final int mLockedModifierState;

        LocalStickyModifierState(int modifierState, int lockedModifierState) {
            mModifierState = modifierState;
            mLockedModifierState = lockedModifierState;
        }

        @Override
        public boolean isShiftModifierOn() {
            return (mModifierState & KeyEvent.META_SHIFT_ON) != 0;
        }

        @Override
        public boolean isShiftModifierLocked() {
            return (mLockedModifierState & KeyEvent.META_SHIFT_ON) != 0;
        }

        @Override
        public boolean isCtrlModifierOn() {
            return (mModifierState & KeyEvent.META_CTRL_ON) != 0;
        }

        @Override
        public boolean isCtrlModifierLocked() {
            return (mLockedModifierState & KeyEvent.META_CTRL_ON) != 0;
        }

        @Override
        public boolean isMetaModifierOn() {
            return (mModifierState & KeyEvent.META_META_ON) != 0;
        }

        @Override
        public boolean isMetaModifierLocked() {
            return (mLockedModifierState & KeyEvent.META_META_ON) != 0;
        }

        @Override
        public boolean isAltModifierOn() {
            return (mModifierState & KeyEvent.META_ALT_LEFT_ON) != 0;
        }

        @Override
        public boolean isAltModifierLocked() {
            return (mLockedModifierState & KeyEvent.META_ALT_LEFT_ON) != 0;
        }

        @Override
        public boolean isAltGrModifierOn() {
            return (mModifierState & KeyEvent.META_ALT_RIGHT_ON) != 0;
        }

        @Override
        public boolean isAltGrModifierLocked() {
            return (mLockedModifierState & KeyEvent.META_ALT_RIGHT_ON) != 0;
        }
    }

    /**
     * @see InputManager#registerStickyModifierStateListener(Executor, StickyModifierStateListener)
     */
    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
    void registerStickyModifierStateListener(@NonNull Executor executor,
            @NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
        Objects.requireNonNull(executor, "executor should not be null");
        Objects.requireNonNull(listener, "listener should not be null");

        synchronized (mStickyModifierStateListenerLock) {
            if (mStickyModifierStateListener == null) {
                mStickyModifierStateListeners = new ArrayList<>();
                mStickyModifierStateListener = new LocalStickyModifierStateListener();

                try {
                    mIm.registerStickyModifierStateListener(mStickyModifierStateListener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            final int numListeners = mStickyModifierStateListeners.size();
            for (int i = 0; i < numListeners; i++) {
                if (mStickyModifierStateListeners.get(i).mListener == listener) {
                    throw new IllegalArgumentException("Listener has already been registered!");
                }
            }
            StickyModifierStateListenerDelegate delegate =
                    new StickyModifierStateListenerDelegate(listener, executor);
            mStickyModifierStateListeners.add(delegate);
        }
    }

    /**
     * @see InputManager#unregisterStickyModifierStateListener(StickyModifierStateListener)
     */
    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
    void unregisterStickyModifierStateListener(
            @NonNull StickyModifierStateListener listener) {
        Objects.requireNonNull(listener, "listener should not be null");

        synchronized (mStickyModifierStateListenerLock) {
            if (mStickyModifierStateListeners == null) {
                return;
            }
            mStickyModifierStateListeners.removeIf((delegate) -> delegate.mListener == listener);
            if (mStickyModifierStateListeners.isEmpty()) {
                try {
                    mIm.unregisterStickyModifierStateListener(mStickyModifierStateListener);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
                mStickyModifierStateListeners = null;
                mStickyModifierStateListener = null;
            }
        }
    }

    /**
     * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
     */
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright 2024 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;

/**
 * The StickyModifierState class is a representation of a modifier state when A11y Sticky keys
 * feature is enabled
 *
 * @hide
 */
public abstract class StickyModifierState {

    /**
     * Represents whether current sticky modifier state includes 'Shift' modifier.
     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Shift' modifier in
     * its metaState.
     *
     * @return whether Shift modifier key is on.
     */
    public abstract boolean isShiftModifierOn();

    /**
     * Represents whether current sticky modifier state includes 'Shift' modifier, and it is
     * locked.
     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Shift'
     * modifier in its metaState and this state will remain sticky (will not be cleared), until
     * user presses 'Shift' key again to clear the locked state.
     *
     * @return whether Shift modifier key is locked.
     */
    public abstract boolean isShiftModifierLocked();

    /**
     * Represents whether current sticky modifier state includes 'Ctrl' modifier.
     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Ctrl' modifier in
     * its metaState.
     *
     * @return whether Ctrl modifier key is on.
     */
    public abstract boolean isCtrlModifierOn();

    /**
     * Represents whether current sticky modifier state includes 'Ctrl' modifier, and it is
     * locked.
     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Ctrl'
     * modifier in its metaState and this state will remain sticky (will not be cleared), until
     * user presses 'Ctrl' key again to clear the locked state.
     *
     * @return whether Ctrl modifier key is locked.
     */
    public abstract boolean isCtrlModifierLocked();

    /**
     * Represents whether current sticky modifier state includes 'Meta' modifier.
     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Meta' modifier in
     * its metaState.
     *
     * @return whether Meta modifier key is on.
     */
    public abstract boolean isMetaModifierOn();

    /**
     * Represents whether current sticky modifier state includes 'Meta' modifier, and it is
     * locked.
     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Meta'
     * modifier in its metaState and this state will remain sticky (will not be cleared), until
     * user presses 'Meta' key again to clear the locked state.
     *
     * @return whether Meta modifier key is locked.
     */
    public abstract boolean isMetaModifierLocked();

    /**
     * Represents whether current sticky modifier state includes 'Alt' modifier.
     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Alt' modifier in
     * its metaState.
     *
     * @return whether Alt modifier key is on.
     */
    public abstract boolean isAltModifierOn();

    /**
     * Represents whether current sticky modifier state includes 'Alt' modifier, and it is
     * locked.
     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Alt'
     * modifier in its metaState and this state will remain sticky (will not be cleared), until
     * user presses 'Alt' key again to clear the locked state.
     *
     * @return whether Alt modifier key is locked.
     */
    public abstract boolean isAltModifierLocked();

    /**
     * Represents whether current sticky modifier state includes 'AltGr' modifier.
     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'AltGr' modifier in
     * its metaState.
     *
     * @return whether AltGr modifier key is on.
     */
    public abstract boolean isAltGrModifierOn();

    /**
     * Represents whether current sticky modifier state includes 'AltGr' modifier, and it is
     * locked.
     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'AltGr'
     * modifier in its metaState and this state will remain sticky (will not be cleared), until
     * user presses 'AltGr' key again to clear the locked state.
     *
     * @return whether AltGr modifier key is locked.
     */
    public abstract boolean isAltGrModifierLocked();
}
Loading