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

Commit 3c9b6a96 authored by Omar Abdelmonem's avatar Omar Abdelmonem
Browse files

Add touchpad selection dropdown menu

Add a drop-down menu next to the touchpad name that,
when clicked, displays a list of all connected touchpads.
This menu allows the user to easily switch between any
available touchpad by selecting from the list.

Bug: 368239574
Test: Manual testing by verifying that the touchpad
      switch logic works as intended and that the dropdown
      menu is updated whenever a touchpad is added/removed.
Flag: com.android.hardware.input.touchpad_visualizer

Change-Id: Ia70072d681e78a9cbf998e126095b6da171939b7
parent d8e644d5
Loading
Loading
Loading
Loading
+18 −21
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
@@ -42,6 +41,7 @@ import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;

import java.util.Objects;
import java.util.function.Consumer;

public class TouchpadDebugView extends LinearLayout {
    private static final float MAX_SCREEN_WIDTH_PROPORTION = 0.4f;
@@ -54,7 +54,6 @@ public class TouchpadDebugView extends LinearLayout {
    private static final int ROUNDED_CORNER_RADIUS_DP = 24;
    private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99);
    private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169);

    /**
     * Input device ID for the touchpad that this debug view is displaying.
     */
@@ -76,24 +75,24 @@ public class TouchpadDebugView extends LinearLayout {
    private int mWindowLocationBeforeDragX;
    private int mWindowLocationBeforeDragY;
    private int mLatestGestureType = 0;
    private TouchpadSelectionView mTouchpadSelectionView;
    private TouchpadVisualizationView mTouchpadVisualizationView;
    private TextView mGestureInfoView;
    private TextView mNameView;

    @NonNull
    private TouchpadHardwareState mLastTouchpadState =
            new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                    new TouchpadFingerState[0]);
    private TouchpadVisualizationView mTouchpadVisualizationView;
    private final TouchpadHardwareProperties mTouchpadHardwareProperties;

    public TouchpadDebugView(Context context, int touchpadId,
                             TouchpadHardwareProperties touchpadHardwareProperties) {
                             TouchpadHardwareProperties touchpadHardwareProperties,
                             Consumer<Integer> touchpadSwitchHandler) {
        super(context);
        mTouchpadId = touchpadId;
        mWindowManager =
                Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
        mTouchpadHardwareProperties = touchpadHardwareProperties;
        init(context, touchpadId);
        init(context, touchpadId, touchpadSwitchHandler);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

        mWindowLayoutParams = new WindowManager.LayoutParams();
@@ -115,7 +114,8 @@ public class TouchpadDebugView extends LinearLayout {
        mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
    }

    private void init(Context context, int touchpadId) {
    private void init(Context context, int touchpadId,
                      Consumer<Integer> touchpadSwitchHandler) {
        updateScreenDimensions();
        setOrientation(VERTICAL);
        setLayoutParams(new LayoutParams(
@@ -123,18 +123,14 @@ public class TouchpadDebugView extends LinearLayout {
                LayoutParams.WRAP_CONTENT));
        setBackgroundColor(Color.TRANSPARENT);

        mNameView = new TextView(context);
        mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
        mNameView.setTextSize(TEXT_SIZE_SP);
        mNameView.setText(Objects.requireNonNull(Objects.requireNonNull(
                        mContext.getSystemService(InputManager.class))
                .getInputDevice(touchpadId)).getName());
        mNameView.setGravity(Gravity.CENTER);
        mNameView.setTextColor(Color.WHITE);
        mTouchpadSelectionView = new TouchpadSelectionView(context,
                touchpadId, touchpadSwitchHandler);
        mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
        mTouchpadSelectionView.setGravity(Gravity.CENTER);
        int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
                getResources().getDisplayMetrics());
        mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
        mNameView.setLayoutParams(
        mTouchpadSelectionView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
        mTouchpadSelectionView.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));

        mTouchpadVisualizationView = new TouchpadVisualizationView(context,
@@ -147,10 +143,11 @@ public class TouchpadDebugView extends LinearLayout {
        mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
        mGestureInfoView.setLayoutParams(
                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        //TODO(b/369061237): Handle longer text

        updateTheme(getResources().getConfiguration().uiMode);

        addView(mNameView);
        addView(mTouchpadSelectionView);
        addView(mTouchpadVisualizationView);
        addView(mGestureInfoView);

@@ -359,12 +356,12 @@ public class TouchpadDebugView extends LinearLayout {

    private void onTouchpadButtonPress() {
        Slog.d(TAG, "You clicked me!");
        mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
        mTouchpadSelectionView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
    }

    private void onTouchpadButtonRelease() {
        Slog.d(TAG, "You released the click");
        mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
        mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
    }

    /**
+12 −4
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList

    public TouchpadDebugViewController(Context context, Looper looper,
                                       InputManagerService inputManagerService) {
        //TODO(b/363979581): Handle multi-display scenarios
        //TODO(b/369059937): Handle multi-display scenarios
        mContext = context;
        mHandler = new Handler(looper);
        mInputManagerService = inputManagerService;
@@ -77,6 +77,14 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
        }
    }

    /**
     * Switch to showing the touchpad with the given device ID
     */
    public void switchVisualisationToTouchpadId(int newDeviceId) {
        if (mTouchpadDebugView != null) hideDebugView(mTouchpadDebugView.getTouchpadId());
        showDebugView(newDeviceId);
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
    }
@@ -117,7 +125,7 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
                        touchpadId);

        mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
                touchpadHardwareProperties);
                touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
        final WindowManager.LayoutParams mWindowLayoutParams =
                mTouchpadDebugView.getWindowLayoutParams();

+111 −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 com.android.server.input.debug;

import android.content.Context;
import android.graphics.Color;
import android.hardware.input.InputManager;
import android.view.Gravity;
import android.view.InputDevice;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
import android.widget.TextView;

import java.util.Objects;
import java.util.function.Consumer;

public class TouchpadSelectionView extends LinearLayout {
    private static final float TEXT_SIZE_SP = 16.0f;

    int mCurrentTouchpadId;

    public TouchpadSelectionView(Context context, int touchpadId,
                                 Consumer<Integer> touchpadSwitchHandler) {
        super(context);
        mCurrentTouchpadId = touchpadId;
        init(context, touchpadSwitchHandler);
    }

    private void init(Context context, Consumer<Integer> touchpadSwitchHandler) {
        setOrientation(HORIZONTAL);
        setLayoutParams(new LayoutParams(
                LayoutParams.WRAP_CONTENT,
                LayoutParams.WRAP_CONTENT));
        setBackgroundColor(Color.TRANSPARENT);

        TextView nameView = new TextView(context);
        nameView.setTextSize(TEXT_SIZE_SP);
        nameView.setText(getTouchpadName(mCurrentTouchpadId));
        nameView.setGravity(Gravity.LEFT);
        nameView.setTextColor(Color.WHITE);

        LayoutParams textParams = new LayoutParams(
                LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        textParams.rightMargin = 16;
        nameView.setLayoutParams(textParams);

        ImageButton arrowButton = new ImageButton(context);
        arrowButton.setImageDrawable(context.getDrawable(android.R.drawable.arrow_down_float));
        arrowButton.setForegroundGravity(Gravity.RIGHT);
        arrowButton.setBackgroundColor(Color.TRANSPARENT);
        arrowButton.setLayoutParams(new LayoutParams(
                ViewGroup.LayoutParams.WRAP_CONTENT,
                ViewGroup.LayoutParams.WRAP_CONTENT));

        arrowButton.setOnClickListener(v -> showPopupMenu(v, context, touchpadSwitchHandler));

        addView(nameView);
        addView(arrowButton);
    }

    private void showPopupMenu(View anchorView, Context context,
                               Consumer<Integer> touchpadSwitchHandler) {
        int i = 0;
        PopupMenu popupMenu = new PopupMenu(context, anchorView);

        final InputManager inputManager = Objects.requireNonNull(
                mContext.getSystemService(InputManager.class));
        for (int deviceId : inputManager.getInputDeviceIds()) {
            InputDevice inputDevice = inputManager.getInputDevice(deviceId);
            if (Objects.requireNonNull(inputDevice).supportsSource(
                    InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
                popupMenu.getMenu().add(0, deviceId, i, getTouchpadName(deviceId));
                i++;
            }
        }

        popupMenu.setOnMenuItemClickListener(item -> {
            if (item.getItemId() == mCurrentTouchpadId) {
                return false;
            }

            touchpadSwitchHandler.accept(item.getItemId());
            return true;
        });

        popupMenu.show();
    }

    private String getTouchpadName(int touchpadId) {
        return Objects.requireNonNull(Objects.requireNonNull(
                        mContext.getSystemService(InputManager.class))
                .getInputDevice(touchpadId)).getName();
    }
}
+13 −5
Original line number Diff line number Diff line
@@ -57,6 +57,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.function.Consumer;

/**
 * Build/Install/Run:
 * atest TouchpadDebugViewTest
@@ -99,10 +101,12 @@ public class TouchpadDebugViewTest {

        when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);

        Consumer<Integer> touchpadSwitchHandler = id -> {};

        mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
                new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
                        500f, 45f, 47f, -4f, 5f, (short) 10, true,
                        true).build());
                        true).build(), touchpadSwitchHandler);

        mTouchpadDebugView.measure(
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
@@ -321,26 +325,30 @@ public class TouchpadDebugViewTest {
                new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);

        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
                Color.parseColor("#769763"));

        mTouchpadDebugView.updateHardwareState(
                new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);

        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169));
        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
                Color.parseColor("#5455A9"));

        mTouchpadDebugView.updateHardwareState(
                new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);

        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
                Color.parseColor("#769763"));

        // Color should not change because hardware state of a different touchpad
        mTouchpadDebugView.updateHardwareState(
                new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
                        new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);

        assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
        assertEquals(((ColorDrawable) child.getBackground()).getColor(),
                Color.parseColor("#769763"));
    }

    @Test