• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.input.debug;
18 
19 import android.annotation.AnyThread;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.hardware.input.InputManager;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.util.Slog;
26 import android.view.InputDevice;
27 import android.view.WindowManager;
28 
29 import com.android.server.input.InputManagerService;
30 import com.android.server.input.TouchpadHardwareProperties;
31 import com.android.server.input.TouchpadHardwareState;
32 
33 import java.util.Objects;
34 
35 public class TouchpadDebugViewController implements InputManager.InputDeviceListener {
36 
37     private static final String TAG = "TouchpadDebugView";
38 
39     private final Context mContext;
40     private final Handler mHandler;
41 
42     @Nullable
43     private TouchpadDebugView mTouchpadDebugView;
44 
45     private final InputManagerService mInputManagerService;
46     private boolean mTouchpadVisualizerEnabled = false;
47 
TouchpadDebugViewController(Context context, Looper looper, InputManagerService inputManagerService)48     public TouchpadDebugViewController(Context context, Looper looper,
49                                        InputManagerService inputManagerService) {
50         //TODO(b/369059937): Handle multi-display scenarios
51         mContext = context;
52         mHandler = new Handler(looper);
53         mInputManagerService = inputManagerService;
54     }
55 
56     @Override
onInputDeviceAdded(int deviceId)57     public void onInputDeviceAdded(int deviceId) {
58         if (!mTouchpadVisualizerEnabled) {
59             return;
60         }
61         final InputManager inputManager = Objects.requireNonNull(
62                 mContext.getSystemService(InputManager.class));
63         InputDevice inputDevice = inputManager.getInputDevice(deviceId);
64         if (inputDevice == null || !inputDevice.supportsSource(
65                 InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
66             return;
67         }
68         showDebugView(deviceId);
69     }
70 
71     @Override
onInputDeviceRemoved(int deviceId)72     public void onInputDeviceRemoved(int deviceId) {
73         hideDebugView(deviceId);
74         if (mTouchpadDebugView == null) {
75             final InputManager inputManager = Objects.requireNonNull(
76                     mContext.getSystemService(InputManager.class));
77             for (int id : inputManager.getInputDeviceIds()) {
78                 onInputDeviceAdded(id);
79             }
80         }
81     }
82 
83     /**
84      * Switch to showing the touchpad with the given device ID
85      */
switchVisualisationToTouchpadId(int newDeviceId)86     public void switchVisualisationToTouchpadId(int newDeviceId) {
87         if (mTouchpadDebugView != null) hideDebugView(mTouchpadDebugView.getTouchpadId());
88         showDebugView(newDeviceId);
89     }
90 
91     @Override
onInputDeviceChanged(int deviceId)92     public void onInputDeviceChanged(int deviceId) {
93     }
94 
95     /**
96      * Notify the controller that the touchpad visualizer setting value has changed.
97      * This must be called from the same looper thread as {@code mHandler}.
98      */
updateTouchpadVisualizerEnabled(boolean touchpadVisualizerEnabled)99     public void updateTouchpadVisualizerEnabled(boolean touchpadVisualizerEnabled) {
100         if (mTouchpadVisualizerEnabled == touchpadVisualizerEnabled) {
101             return;
102         }
103         mTouchpadVisualizerEnabled = touchpadVisualizerEnabled;
104         final InputManager inputManager = Objects.requireNonNull(
105                 mContext.getSystemService(InputManager.class));
106         if (touchpadVisualizerEnabled) {
107             inputManager.registerInputDeviceListener(this, mHandler);
108             for (int deviceId : inputManager.getInputDeviceIds()) {
109                 onInputDeviceAdded(deviceId);
110             }
111         } else {
112             if (mTouchpadDebugView != null) {
113                 hideDebugView(mTouchpadDebugView.getTouchpadId());
114             }
115             inputManager.unregisterInputDeviceListener(this);
116         }
117     }
118 
showDebugView(int touchpadId)119     private void showDebugView(int touchpadId) {
120         if (mTouchpadDebugView != null) {
121             return;
122         }
123         final WindowManager wm = Objects.requireNonNull(
124                 mContext.getSystemService(WindowManager.class));
125 
126         TouchpadHardwareProperties touchpadHardwareProperties =
127                 mInputManagerService.getTouchpadHardwareProperties(
128                         touchpadId);
129 
130         mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
131                 touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
132         final WindowManager.LayoutParams mWindowLayoutParams =
133                 mTouchpadDebugView.getWindowLayoutParams();
134 
135         wm.addView(mTouchpadDebugView, mWindowLayoutParams);
136         Slog.d(TAG, "Touchpad debug view created.");
137 
138         if (touchpadHardwareProperties != null) {
139             Slog.d(TAG, touchpadHardwareProperties.toString());
140         } else {
141             Slog.w(TAG, "Failed to retrieve touchpad hardware properties for "
142                     + "device ID: " + touchpadId);
143         }
144     }
145 
hideDebugView(int touchpadId)146     private void hideDebugView(int touchpadId) {
147         if (mTouchpadDebugView == null || mTouchpadDebugView.getTouchpadId() != touchpadId) {
148             return;
149         }
150         final WindowManager wm = Objects.requireNonNull(
151                 mContext.getSystemService(WindowManager.class));
152         wm.removeView(mTouchpadDebugView);
153         mTouchpadDebugView = null;
154         Slog.d(TAG, "Touchpad debug view removed.");
155     }
156 
157     /**
158      * Notifies about an update in the touchpad's hardware state.
159      *
160      * @param touchpadHardwareState the hardware state of a touchpad
161      * @param deviceId              the deviceId of the touchpad that is sending the hardware state
162      */
163     @AnyThread
updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState, int deviceId)164     public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
165                                             int deviceId) {
166         mHandler.post(() -> {
167             if (mTouchpadDebugView != null) {
168                 mTouchpadDebugView.post(() -> {
169                     // hideDebugView might have been called since we posted the action (e.g. if the
170                     // developer option toggle is clicked using the same touchpad currently being
171                     // visualized, b/376018148), so we need to check for null again.
172                     if (mTouchpadDebugView != null) {
173                         mTouchpadDebugView.updateHardwareState(touchpadHardwareState,
174                                 deviceId);
175                     }
176                 });
177             }
178         });
179     }
180 
181     /**
182      * Notify the TouchpadDebugView of a new touchpad gesture.
183      */
184     @AnyThread
updateTouchpadGestureInfo(int gestureType, int deviceId)185     public void updateTouchpadGestureInfo(int gestureType, int deviceId) {
186         mHandler.post(() -> {
187             if (mTouchpadDebugView != null) {
188                 mTouchpadDebugView.post(() -> {
189                     // hideDebugView might have been called since we posted the action (e.g. if the
190                     // developer option toggle is clicked using the same touchpad currently being
191                     // visualized, b/376018148), so we need to check for null again.
192                     if (mTouchpadDebugView != null) {
193                         mTouchpadDebugView.updateGestureInfo(gestureType, deviceId);
194                     }
195                 });
196             }
197         });
198     }
199 }
200